AzSHCI.CloudDeploymentTool.psm1
<#############################################################
# # # Copyright (C) Microsoft Corporation. All rights reserved. # # # #############################################################> Import-Module $PSScriptRoot\Classes\reporting.psm1 -Force -DisableNameChecking -Global $global:RPAPIVersion="2024-04-01" function Invoke-AzStackHCIEnvironmentPreparator { [CmdletBinding(DefaultParametersetName = 'AZContext')] param ( [Parameter(Mandatory = $true, HelpMessage = "Azure Subscription Id for HCI Cluster Deployment")] [string] $SubscriptionID, [Parameter(Mandatory = $true, HelpMessage = "Azure Resource group used for HCI Cluster Deployment")] [string] $ResourceGroup, # AzureCloud , AzureUSGovernment , AzureChinaCloud [Parameter(Mandatory = $true, HelpMessage = "Azure Cloud type used for HCI Cluster Deployment. Valid values are : AzureCloud , AzureUSGovernment , AzureChinaCloud")] [string] $Cloud, [Parameter(Mandatory = $true, HelpMessage = "Azure Region used for HCI Cluster Deployment")] [string] $Region, [Parameter(Mandatory = $false, HelpMessage = "Azure Stack HCI Cluster Name for Registration")] [string] $ClusterName, [Parameter(Mandatory = $true, HelpMessage = "Local Admin Credentials Required for deployment")] [System.Management.Automation.PSCredential] $LocalAdminCredentials, [Parameter(Mandatory = $true, HelpMessage = "Cloud Admin Credentials Required for deployment")] [System.Management.Automation.PSCredential] $DomainAdminCredentials, [Parameter(Mandatory = $true, HelpMessage = "Arc Node ids required for cloud based deployment")] [string[]] $ArcNodeIds, [Parameter(Mandatory = $false, HelpMessage = "Return PSObject result.")] [System.Collections.Hashtable] $Tag, [Parameter(Mandatory = $false, HelpMessage = "Directory path for log and report output")] [string]$OutputPath, [Parameter(Mandatory = $false)] [Switch] $Force, [Parameter(Mandatory = $false, HelpMessage = "Prefix to uniquely identify a storage account and a keyvault")] [string] $Prefix, [Parameter(Mandatory = $false)] [System.Collections.Hashtable] $SBESecrets ) try { $script:ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' $DebugPreference = "Continue" Set-AzStackHciOutputPath -Path $OutputPath if(CheckIfScriptIsRunByAdministrator){ Log-Info -Message "Script is run as administrator, so enabling" -ConsoleOut [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 3072; } else{ throw "This script should be executed in administrator mode or above" } $contextStatus = CheckIfAzContextIsSetOrNot if($contextStatus) { Log-Info -Message "Az Context is set, so proceeding" -ConsoleOut } else { throw "Az Context is not set , so cannot proceed with environment preparation, please run Connect-AzAccount and retry" } if ($null -eq $ClusterName) { Log-Info -Message "Obtained cluster name is null, so getting the cluster Name from the answer file" -ConsoleOut $ClusterName = GetClusterNameFromAnswerFile -AnswerFilePath $AnswerFilePath Log-Info -Message "Obtained cluster name from answer file is $ClusterName" -ConsoleOut } Log-Info -Message "Starting AzStackHci Deployment Initialization" -ConsoleOut CreateResourceGroupIfNotExists -ResourceGroupName $ResourceGroup -Region $Region Log-Info -Message "Registering Resource providers step" -ConsoleOut RegisterRequiredResourceProviders Log-Info -Message "Creating cluster and assigning permissions for ARC machines" -ConsoleOut CreateClusterAndAssignRoles -SubscriptionID $SubscriptionID -ResourceGroup $ResourceGroup -Region $Region -ClusterName $ClusterName Log-Info -Message "Creating storage cloud for witness" -ConsoleOut CreateStorageAccountForCloudDeployment -ResourceGroup $ResourceGroup -Region $Region -ClusterName $ClusterName -Prefix $Prefix Log-Info -Message "Creating key vault and adding the secrets" -ConsoleOut if ($null -ne $SBESecrets) { Log-Info -Message "SBE Secrets is not null" -ConsoleOut } CreateKeyVaultAndAddSecrets -SubscriptionID $SubscriptionID -ResourceGroup $ResourceGroup -Region $Region -LocalAdminCredentials $LocalAdminCredentials -DomainAdminCredentials $DomainAdminCredentials -ClusterName $ClusterName -Prefix $Prefix -SBESecrets $SBESecrets Log-Info -Message "Trying to assign the rbac permissions on the Arc Machines" -ConsoleOut AssignPermissionsToArcMachines -ArcMachineIds $ArcNodeIds -ResourceGroup $ResourceGroup Log-Info -Message "Successfully assigned the rbac permission on the Arc Machines" -ConsoleOut Log-Info -Message "Successfully prepared the environment with cluster, storage account and kv" -ConsoleOut } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error $cmdletFailed = $true throw $_ } finally { $Script:ErrorActionPreference = 'SilentlyContinue' Write-AzStackHciArcInstallerFooter -invocation $MyInvocation -Failed:$cmdletFailed -PassThru:$PassThru $DebugPreference = "Stop" } } function Invoke-AzStackHCIEnvironmentPreparatorForClusterUpgrade { [CmdletBinding(DefaultParametersetName = 'AZContext')] param ( [Parameter(Mandatory = $true, HelpMessage = "Azure Subscription Id for HCI Cluster Deployment")] [string] $SubscriptionID, [Parameter(Mandatory = $true, HelpMessage = "Azure Resource group used for HCI Cluster Deployment")] [string] $ResourceGroup, # AzureCloud , AzureUSGovernment , AzureChinaCloud [Parameter(Mandatory = $true, HelpMessage = "Azure Cloud type used for HCI Cluster Deployment. Valid values are : AzureCloud , AzureUSGovernment , AzureChinaCloud")] [string] $Cloud, [Parameter(Mandatory = $true, HelpMessage = "Azure Region used for HCI Cluster Deployment")] [string] $Region, [Parameter(Mandatory = $false, HelpMessage = "Azure Stack HCI Cluster Name for Registration")] [string] $ClusterName, [Parameter(Mandatory = $true, HelpMessage = "Local Admin Credentials Required for deployment")] [System.Management.Automation.PSCredential] $LocalAdminCredentials, [Parameter(Mandatory = $true, HelpMessage = "Cloud Admin Credentials Required for deployment")] [System.Management.Automation.PSCredential] $DomainAdminCredentials, [Parameter(Mandatory = $true, HelpMessage = "Arc Node ids required for cloud based deployment")] [string[]] $ArcNodeIds, [Parameter(Mandatory = $false, HelpMessage = "Return PSObject result.")] [System.Collections.Hashtable] $Tag, [Parameter(Mandatory = $false, HelpMessage = "Directory path for log and report output")] [string]$OutputPath, [Parameter(Mandatory = $false)] [Switch] $Force, [Parameter(Mandatory = $false, HelpMessage = "Prefix to uniquely identify a storage account and a keyvault")] [string] $Prefix, [Parameter(Mandatory = $false)] [System.Collections.Hashtable] $SBESecrets ) try { $script:ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' $DebugPreference = "Continue" Set-AzStackHciOutputPath -Path $OutputPath if(CheckIfScriptIsRunByAdministrator){ Log-Info -Message "Script is run as administrator, so enabling" -ConsoleOut [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 3072; } else{ throw "This script should be executed in administrator mode or above" } $contextStatus = CheckIfAzContextIsSetOrNot if($contextStatus) { Log-Info -Message "Az Context is set, so proceeding" -ConsoleOut } else { throw "Az Context is not set , so cannot proceed with environment preparation, please run Connect-AzAccount and retry" } if ($null -eq $ClusterName) { Log-Info -Message "Obtained cluster name is null, so getting the cluster Name from the answer file" -ConsoleOut $ClusterName = GetClusterNameFromAnswerFile -AnswerFilePath $AnswerFilePath Log-Info -Message "Obtained cluster name from answer file is $ClusterName" -ConsoleOut } Log-Info -Message "Starting AzStackHci Deployment Initialization" -ConsoleOut CreateResourceGroupIfNotExists -ResourceGroupName $ResourceGroup -Region $Region Log-Info -Message "Registering Resource providers step" -ConsoleOut RegisterRequiredResourceProviders Log-Info -Message "Creating key vault and adding the secrets" -ConsoleOut if ($null -ne $SBESecrets) { Log-Info -Message "SBE Secrets is not null" -ConsoleOut } CreateKeyVaultAndAddSecretsForClusterUpgrade -SubscriptionID $SubscriptionID -ResourceGroup $ResourceGroup -Region $Region -LocalAdminCredentials $LocalAdminCredentials -DomainAdminCredentials $DomainAdminCredentials -ClusterName $ClusterName -Prefix $Prefix -SBESecrets $SBESecrets Log-Info -Message "Trying to assign the rbac permissions on the Arc Machines" -ConsoleOut AssignPermissionsToArcMachines -ArcMachineIds $ArcNodeIds -ResourceGroup $ResourceGroup Log-Info -Message "Successfully assigned the rbac permission on the Arc Machines" -ConsoleOut Log-Info -Message "Successfully prepared the environment with cluster, storage account and kv" -ConsoleOut } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error $cmdletFailed = $true throw $_ } finally { $Script:ErrorActionPreference = 'SilentlyContinue' Write-AzStackHciArcInstallerFooter -invocation $MyInvocation -Failed:$cmdletFailed -PassThru:$PassThru $DebugPreference = "Stop" } } function Invoke-AzStackHCIEnvironmentValidator { [CmdletBinding(DefaultParametersetName = 'AZContext')] param ( [Parameter(Mandatory = $true, HelpMessage = "Azure Subscription Id for HCI Cluster Deployment")] [string] $SubscriptionID, [Parameter(Mandatory = $true, HelpMessage = "Azure Resource group used for HCI Cluster Deployment")] [string] $ResourceGroup, [Parameter(Mandatory = $false, HelpMessage = "Azure Stack HCI Cluster Name for Registration")] [string] $ClusterName, [Parameter(Mandatory = $true, HelpMessage = "Arc Node ids required for cloud based deployment")] [string[]] $ArcNodeIds, [Parameter(Mandatory = $true, HelpMessage = "Answer file path required for deployment")] [string] $AnswerFilePath, [Parameter(Mandatory = $false, HelpMessage = "Return PSObject result.")] [System.Collections.Hashtable] $Tag, [Parameter(Mandatory = $false, HelpMessage = "Directory path for log and report output")] [string]$OutputPath, [Parameter(Mandatory = $false)] [Switch] $Force, [Parameter(Mandatory = $false, HelpMessage = "Prefix to uniquely identify a storage account and a keyvault")] [string] $Prefix, [Parameter(Mandatory = $false)] [System.Collections.Hashtable] $SBESecrets ) try { $script:ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' $DebugPreference = "Continue" Set-AzStackHciOutputPath -Path $OutputPath if(CheckIfScriptIsRunByAdministrator){ Log-Info -Message "Script is run as administrator, so enabling" -ConsoleOut [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 3072; } else{ throw "This script should be executed in administrator mode or above" } $contextStatus = CheckIfAzContextIsSetOrNot if($contextStatus) { Log-Info -Message "Az Context is set, so proceeding" -ConsoleOut } else { throw "Az Context is not set , so cannot proceed with environment validation, please run Connect-AzAccount and retry" } $preview = $false # ENV Variable for choosing deployment preview arm template CHOOSEDEPLOYMENTPREVIEWTEMPLATE $preview = [System.Convert]::ToBoolean($env:CHOOSEDEPLOYMENTPREVIEWTEMPLATE) if ($null -eq $ClusterName) { Log-Info -Message "Obtained cluster name is null, so getting the cluster Name from the answer file" -ConsoleOut $ClusterName = GetClusterNameFromAnswerFile -AnswerFilePath $AnswerFilePath Log-Info -Message "Obtained cluster name from answer file is $ClusterName" -ConsoleOut } Log-Info -Message "Starting Deployment Settings Validation Operation" -ConsoleOut $storageAccountName = GetStorageAccountName -ClusterName $ClusterName -Prefix $Prefix $KVName = GetKeyVaultName -ClusterName $ClusterName -Prefix $Prefix $deploymentSettingsObject = Get-Content $AnswerFilePath | ConvertFrom-Json if ($null -eq $deploymentSettingsObject){ throw "Deployment Settings Object cannot be null" } Log-Info -Message "Deployment Settings Object obtained is $deploymentSettingsObject" -ConsoleOut $kvResource = Get-AzResource -Name $KVName -ResourceType "Microsoft.KeyVault/vaults" -ResourceGroupName $ResourceGroup $kvVaultUri = $kvResource.Properties.vaultUri Log-Info -Message "Key Vault Uri obtained is $kvVaultUri" -ConsoleOut if ($null -ne $SBESecrets) { $sbeCredetailsList = CreateSBECredentialsList -SBESecrets $SBESecrets -kvVaultUri $kvVaultUri } # Will Trigger Validate first $deploymentSettingsParameters = ReplaceDeploymentSettingsParametersTemplateWithActualValues -deploymentSettingsObject $deploymentSettingsObject -clusterName $ClusterName -arcNodeResourceIds $ArcNodeIds -storageAccountName $storageAccountName -secretsLocation $kvVaultUri -sbeCredentialsList $sbeCredetailsList -preview $preview -Prefix $Prefix if ($null -eq $deploymentSettingsParameters){ throw "Deployment Settings Parameters cannot be null" } $deploymentSettingsParameters.parameters.deploymentMode.value = "Validate" Log-Info -Message "Deployment settings parameters obtained is $deploymentSettingsParameters" -ConsoleOut $deploymentSettingsParametersJson = $deploymentSettingsParameters | ConvertTo-Json -Depth 100 Log-Info -Message "Deployment Settings Parameters to json is $deploymentSettingsParametersJson" -ConsoleOut $updatedDeploymentSettingsParametersFilePath = (Join-Path -Path $env:TEMP -ChildPath "\DeploymentSettingsReportedPropertiesValidate.json") Log-Info -Message "Updated Deployment Settings Parameters File Path $updatedDeploymentSettingsParametersFilePath" -ConsoleOut Set-Content -Path $updatedDeploymentSettingsParametersFilePath -Value $deploymentSettingsParametersJson | Out-Null $deploymentSettingsTemplateFilePath = (Join-Path -Path $PSScriptRoot -ChildPath "Templates\DeploymentSettingsTemplate.json") if ($preview) { Log-Info -Message "The preview flag is enabled so picked up the preview deployment settings template" -ConsoleOut $deploymentSettingsTemplateFilePath = (Join-Path -Path $PSScriptRoot -ChildPath "Templates\DeploymentSettingsTemplatePreview.json") } $deploymentIdentifier = [guid]::NewGuid().ToString().Split("-")[0] $deploymentSettingsValidationName = $ResourceGroup + "-DSValidate" + $deploymentIdentifier Log-Info -Message "Deployment Settings Template File Path $deploymentSettingsTemplateFilePath and Deployment Name $deploymentSettingsDeploymentName" -ConsoleOut $currentDeploymentNameFilePath = (Join-Path -Path $env:TEMP -ChildPath "\DeploymentName.txt") Log-Info -Message "Current Deployment File Path where the deployment name is stored is $currentDeploymentNameFilePath" -ConsoleOut Set-Content -Path $currentDeploymentNameFilePath -Value $deploymentSettingsValidationName | Out-Null $resourceGroupDeploymentStatus = New-AzResourceGroupDeployment -Name $deploymentSettingsValidationName -ResourceGroupName $ResourceGroup -TemplateFile $deploymentSettingsTemplateFilePath -TemplateParameterFile $updatedDeploymentSettingsParametersFilePath -Force -Verbose -AsJob $deploystatusString = $resourceGroupDeploymentStatus | Out-String Log-Info -Message "Triggered Validated the deployment Settings Resource $deploystatusString" -ConsoleOut Start-Sleep -Seconds 120 $entireDeploymentStatus = Get-AzResourceGroupDeployment -ResourceGroupName $ResourceGroup -Name $deploymentSettingsValidationName $deploymentStatus = $entireDeploymentStatus | Format-Table ResourceGroupName, DeploymentName, ProvisioningState $deploystatusString = $deploymentStatus | Out-String Log-Info -Message "Triggered Validated the deployment Settings Resource $deploystatusString" -ConsoleOut $validationOperation = Get-AzResourceGroupDeploymentOperation -DeploymentName $deploymentSettingsValidationName -ResourceGroup $ResourceGroup | ConvertTo-Json Log-Info -Message "Current Deployment Settings Validation Operation is $validationOperation" -ConsoleOut if ($deploymentStatus.ProvisioningState -eq "Failed") { Log-Info -Message "The deployment status is already in a failed state , Entire deployment Status : $entireDeploymentStatus" -ConsoleOut throw "The deployment status is in a failed state, status = $deploystatusString" } } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error throw $_ } finally { $Script:ErrorActionPreference = 'SilentlyContinue' Write-AzStackHciArcInstallerFooter -invocation $MyInvocation -Failed:$cmdletFailed -PassThru:$PassThru $DebugPreference = "Stop" } } function Invoke-AzStackHCIEnvironmentValidatorForClusterUpgrade { [CmdletBinding(DefaultParametersetName = 'AZContext')] param ( [Parameter(Mandatory = $true, HelpMessage = "Azure Subscription Id for HCI Cluster Deployment")] [string] $SubscriptionID, [Parameter(Mandatory = $true, HelpMessage = "Azure Resource group used for HCI Cluster Deployment")] [string] $ResourceGroup, [Parameter(Mandatory = $false, HelpMessage = "Azure Stack HCI Cluster Name for Registration")] [string] $ClusterName, [Parameter(Mandatory = $true, HelpMessage = "Arc Node ids required for cloud based deployment")] [string[]] $ArcNodeIds, [Parameter(Mandatory = $true, HelpMessage = "Answer file path required for deployment")] [string] $AnswerFilePath, [Parameter(Mandatory = $false, HelpMessage = "Return PSObject result.")] [System.Collections.Hashtable] $Tag, [Parameter(Mandatory = $false, HelpMessage = "Directory path for log and report output")] [string]$OutputPath, [Parameter(Mandatory = $false)] [Switch] $Force, [Parameter(Mandatory = $false, HelpMessage = "Prefix to uniquely identify a storage account and a keyvault")] [string] $Prefix, [Parameter(Mandatory = $false)] [System.Collections.Hashtable] $SBESecrets ) try { $script:ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' $DebugPreference = "Continue" Set-AzStackHciOutputPath -Path $OutputPath if(CheckIfScriptIsRunByAdministrator){ Log-Info -Message "Script is run as administrator, so enabling" -ConsoleOut [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 3072; } else{ throw "This script should be executed in administrator mode or above" } $contextStatus = CheckIfAzContextIsSetOrNot if($contextStatus) { Log-Info -Message "Az Context is set, so proceeding" -ConsoleOut } else { throw "Az Context is not set , so cannot proceed with environment validation, please run Connect-AzAccount and retry" } if ($null -eq $ClusterName) { Log-Info -Message "Obtained cluster name is null, so getting the cluster Name from the answer file" -ConsoleOut $ClusterName = GetClusterNameFromAnswerFile -AnswerFilePath $AnswerFilePath Log-Info -Message "Obtained cluster name from answer file is $ClusterName" -ConsoleOut } Log-Info -Message "Starting Deployment Settings Validation Operation" -ConsoleOut $KVName = GetKeyVaultName -ClusterName $ClusterName -Prefix $Prefix $deploymentSettingsObject = Get-Content $AnswerFilePath | ConvertFrom-Json if ($null -eq $deploymentSettingsObject){ throw "Deployment Settings Object cannot be null" } Log-Info -Message "Deployment Settings Object obtained is $deploymentSettingsObject" -ConsoleOut $kvResource = Get-AzResource -Name $KVName -ResourceType "Microsoft.KeyVault/vaults" -ResourceGroupName $ResourceGroup $kvVaultUri = $kvResource.Properties.vaultUri Log-Info -Message "Key Vault Uri obtained is $kvVaultUri" -ConsoleOut if ($null -ne $SBESecrets) { $sbeCredetailsList = CreateSBECredentialsList -SBESecrets $SBESecrets -kvVaultUri $kvVaultUri } # Will Trigger Validate first $deploymentSettingsParameters = ReplaceDeploymentSettingsParametersTemplateWithActualValuesForClusterUpgrade -deploymentSettingsObject $deploymentSettingsObject -clusterName $ClusterName -arcNodeResourceIds $ArcNodeIds -secretsLocation $kvVaultUri -sbeCredentialsList $sbeCredetailsList -Prefix $Prefix if ($null -eq $deploymentSettingsParameters){ throw "Deployment Settings Parameters cannot be null" } $deploymentSettingsParameters.parameters.deploymentMode.value = "Validate" $deploymentSettingsParameters.parameters.operationType.value = "ClusterUpgrade" Log-Info -Message "Deployment settings parameters obtained is $deploymentSettingsParameters" -ConsoleOut $deploymentSettingsParametersJson = $deploymentSettingsParameters | ConvertTo-Json -Depth 100 Log-Info -Message "Deployment Settings Parameters to json is $deploymentSettingsParametersJson" -ConsoleOut $updatedDeploymentSettingsParametersFilePath = (Join-Path -Path $env:TEMP -ChildPath "\DeploymentSettingsReportedPropertiesValidate.json") Log-Info -Message "Updated Deployment Settings Parameters File Path $updatedDeploymentSettingsParametersFilePath" -ConsoleOut Set-Content -Path $updatedDeploymentSettingsParametersFilePath -Value $deploymentSettingsParametersJson | Out-Null $deploymentSettingsTemplateFilePath = (Join-Path -Path $PSScriptRoot -ChildPath "Templates\DeploymentSettingsTemplateForClusterUpgrade.json") $deploymentIdentifier = [guid]::NewGuid().ToString().Split("-")[0] $deploymentSettingsValidationName = $ResourceGroup + "-DSValidate" + $deploymentIdentifier Log-Info -Message "Deployment Settings Template File Path $deploymentSettingsTemplateFilePath and Deployment Name $deploymentSettingsDeploymentName" -ConsoleOut $currentDeploymentNameFilePath = (Join-Path -Path $env:TEMP -ChildPath "\DeploymentName.txt") Log-Info -Message "Current Deployment File Path where the deployment name is stored is $currentDeploymentNameFilePath" -ConsoleOut Set-Content -Path $currentDeploymentNameFilePath -Value $deploymentSettingsValidationName | Out-Null $resourceGroupDeploymentStatus = New-AzResourceGroupDeployment -Name $deploymentSettingsValidationName -ResourceGroupName $ResourceGroup -TemplateFile $deploymentSettingsTemplateFilePath -TemplateParameterFile $updatedDeploymentSettingsParametersFilePath -Force -Verbose -AsJob $deploystatusString = $resourceGroupDeploymentStatus | Out-String Log-Info -Message "Triggered Validated the deployment Settings Resource $deploystatusString" -ConsoleOut Start-Sleep -Seconds 120 $entireDeploymentStatus = Get-AzResourceGroupDeployment -ResourceGroupName $ResourceGroup -Name $deploymentSettingsValidationName $deploymentStatus = $entireDeploymentStatus | Format-Table ResourceGroupName, DeploymentName, ProvisioningState $deploystatusString = $deploymentStatus | Out-String Log-Info -Message "Triggered Validated the deployment Settings Resource $deploystatusString" -ConsoleOut $validationOperation = Get-AzResourceGroupDeploymentOperation -DeploymentName $deploymentSettingsValidationName -ResourceGroup $ResourceGroup | ConvertTo-Json Log-Info -Message "Current Deployment Settings Validation Operation is $validationOperation" -ConsoleOut if ($deploymentStatus.ProvisioningState -eq "Failed") { Log-Info -Message "The deployment status is already in a failed state , Entire deployment Status : $entireDeploymentStatus" -ConsoleOut throw "The deployment status is in a failed state, status = $deploystatusString" } } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error throw $_ } finally { $Script:ErrorActionPreference = 'SilentlyContinue' Write-AzStackHciArcInstallerFooter -invocation $MyInvocation -Failed:$cmdletFailed -PassThru:$PassThru $DebugPreference = "Stop" } } function Invoke-AzStackHCIDeployment { [CmdletBinding(DefaultParametersetName = 'AZContext')] param ( [Parameter(Mandatory = $true, HelpMessage = "Azure Subscription Id for HCI Cluster Deployment")] [string] $SubscriptionID, [Parameter(Mandatory = $true, HelpMessage = "Azure Resource group used for HCI Cluster Deployment")] [string] $ResourceGroup, [Parameter(Mandatory = $false, HelpMessage = "Azure Stack HCI Cluster Name for Registration")] [string] $ClusterName, [Parameter(Mandatory = $true, HelpMessage = "Arc Node ids required for cloud based deployment")] [string[]] $ArcNodeIds, [Parameter(Mandatory = $true, HelpMessage = "Answer file path required for deployment")] [string] $AnswerFilePath, [Parameter(Mandatory = $false, HelpMessage = "Return PSObject result.")] [System.Collections.Hashtable] $Tag, [Parameter(Mandatory = $false, HelpMessage = "Directory path for log and report output")] [string]$OutputPath, [Parameter(Mandatory = $false)] [Switch] $Force, [Parameter(Mandatory = $false, HelpMessage = "Prefix to uniquely identify a storage account and a keyvault")] [string] $Prefix ) try { $script:ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' $DebugPreference = "Continue" Set-AzStackHciOutputPath -Path $OutputPath if(CheckIfScriptIsRunByAdministrator){ Log-Info -Message "Script is run as administrator, so enabling" -ConsoleOut [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 3072; } else{ throw "This script should be executed in administrator mode or above" } $contextStatus = CheckIfAzContextIsSetOrNot if($contextStatus) { Log-Info -Message "Az Context is set, so proceeding" -ConsoleOut } else { throw "Az Context is not set , so cannot proceed with deployment, please run Connect-AzAccount and retry" } $preview = $false # ENV Variable for choosing deployment preview arm template CHOOSEDEPLOYMENTPREVIEWTEMPLATE $preview = [System.Convert]::ToBoolean($env:CHOOSEDEPLOYMENTPREVIEWTEMPLATE) if ($null -eq $ClusterName) { Log-Info -Message "Obtained cluster name is null, so getting the cluster Name from the answer file" -ConsoleOut $ClusterName = GetClusterNameFromAnswerFile -AnswerFilePath $AnswerFilePath Log-Info -Message "Obtained cluster name from answer file is $ClusterName" -ConsoleOut } Log-Info -Message "Starting Deployment Settings Validation Operation" -ConsoleOut $storageAccountName = GetStorageAccountName -ClusterName $ClusterName -Prefix $Prefix $KVName = GetKeyVaultName -ClusterName $ClusterName -Prefix $Prefix $deploymentSettingsObject = Get-Content $AnswerFilePath | ConvertFrom-Json if ($null -eq $deploymentSettingsObject){ throw "Deployment Settings Object cannot be null" } Log-Info -Message "Deployment Settings Object obtained is $deploymentSettingsObject" -ConsoleOut $kvResource = Get-AzResource -Name $KVName -ResourceType "Microsoft.KeyVault/vaults" -ResourceGroupName $ResourceGroup $kvVaultUri = $kvResource.Properties.vaultUri Log-Info -Message "Key Vault Uri obtained is $kvVaultUri" -ConsoleOut # Will Trigger Deployment $deploymentSettingsParameters = ReplaceDeploymentSettingsParametersTemplateWithActualValues -deploymentSettingsObject $deploymentSettingsObject -clusterName $ClusterName -arcNodeResourceIds $ArcNodeIds -storageAccountName $storageAccountName -secretsLocation $kvVaultUri -preview $preview -Prefix $Prefix if ($null -eq $deploymentSettingsParameters){ throw "Deployment Settings Parameters cannot be null" } $deploymentSettingsParameters.parameters.deploymentMode.value = "Deploy" Log-Info -Message "Deployment settings parameters obtained is $deploymentSettingsParameters" -ConsoleOut $deploymentSettingsParametersJson = $deploymentSettingsParameters | ConvertTo-Json -Depth 100 Log-Info -Message "Deployment Settings Parameters to json is $deploymentSettingsParametersJson" -ConsoleOut $updatedDeploymentSettingsParametersFilePath = (Join-Path -Path $env:TEMP -ChildPath "\DeploymentSettingsReportedPropertiesDeploy.json") Log-Info -Message "Updated Deployment Settings Parameters File Path $updatedDeploymentSettingsParametersFilePath" -ConsoleOut Set-Content -Path $updatedDeploymentSettingsParametersFilePath -Value $deploymentSettingsParametersJson | Out-Null $deploymentSettingsTemplateFilePath = (Join-Path -Path $PSScriptRoot -ChildPath "Templates\DeploymentSettingsTemplate.json") if ($preview) { Log-Info -Message "The preview flag is enabled so picked up the preview deployment settings template" -ConsoleOut $deploymentSettingsTemplateFilePath = (Join-Path -Path $PSScriptRoot -ChildPath "Templates\DeploymentSettingsTemplatePreview.json") } $deploymentIdentifier = [guid]::NewGuid().ToString().Split("-")[0] $deploymentSettingsValidationName = $ResourceGroup + "-DSDeploy" + $deploymentIdentifier Log-Info -Message "Deployment Settings Template File Path $deploymentSettingsTemplateFilePath and Deployment Name $deploymentSettingsValidationName" -ConsoleOut $currentDeploymentNameFilePath = (Join-Path -Path $env:TEMP -ChildPath "\DeploymentName.txt") Log-Info -Message "Current Deployment File Path where the deployment name is stored is $currentDeploymentNameFilePath" -ConsoleOut Set-Content -Path $currentDeploymentNameFilePath -Value $deploymentSettingsValidationName | Out-Null New-AzResourceGroupDeployment -Name $deploymentSettingsValidationName -ResourceGroupName $ResourceGroup -TemplateFile $deploymentSettingsTemplateFilePath -TemplateParameterFile $updatedDeploymentSettingsParametersFilePath -Force -Verbose -AsJob Start-Sleep -Seconds 120 $deploymentStatus = Get-AzResourceGroupDeployment -ResourceGroupName $ResourceGroup -Name $deploymentSettingsValidationName $deploystatusString = $deploymentStatus | Out-String Log-Info -Message "Triggered the deployment Settings Resource in deploy mode: $deploystatusString " -ConsoleOut $deploymentOperation = Get-AzResourceGroupDeploymentOperation -DeploymentName $deploymentSettingsValidationName -ResourceGroup $ResourceGroup | ConvertTo-Json Log-Info -Message "Current Deployment Settings Validation Operation is $deploymentOperation" -ConsoleOut } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error throw $_ } finally { $Script:ErrorActionPreference = 'SilentlyContinue' Write-AzStackHciArcInstallerFooter -invocation $MyInvocation -Failed:$cmdletFailed -PassThru:$PassThru $DebugPreference = "Stop" } } function Invoke-AzStackHCIUpgrade { [CmdletBinding(DefaultParametersetName = 'AZContext')] param ( [Parameter(Mandatory = $true, HelpMessage = "Azure Subscription Id for HCI Cluster Deployment")] [string] $SubscriptionID, [Parameter(Mandatory = $true, HelpMessage = "Azure Resource group used for HCI Cluster Deployment")] [string] $ResourceGroup, [Parameter(Mandatory = $false, HelpMessage = "Azure Stack HCI Cluster Name for Registration")] [string] $ClusterName, [Parameter(Mandatory = $true, HelpMessage = "Arc Node ids required for cloud based deployment")] [string[]] $ArcNodeIds, [Parameter(Mandatory = $true, HelpMessage = "Answer file path required for deployment")] [string] $AnswerFilePath, [Parameter(Mandatory = $false, HelpMessage = "Return PSObject result.")] [System.Collections.Hashtable] $Tag, [Parameter(Mandatory = $false, HelpMessage = "Directory path for log and report output")] [string]$OutputPath, [Parameter(Mandatory = $false)] [Switch] $Force, [Parameter(Mandatory = $false, HelpMessage = "Prefix to uniquely identify a storage account and a keyvault")] [string] $Prefix ) try { $script:ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' $DebugPreference = "Continue" Set-AzStackHciOutputPath -Path $OutputPath if(CheckIfScriptIsRunByAdministrator){ Log-Info -Message "Script is run as administrator, so enabling" -ConsoleOut [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 3072; } else{ throw "This script should be executed in administrator mode or above" } $contextStatus = CheckIfAzContextIsSetOrNot if($contextStatus) { Log-Info -Message "Az Context is set, so proceeding" -ConsoleOut } else { throw "Az Context is not set , so cannot proceed with deployment, please run Connect-AzAccount and retry" } if ($null -eq $ClusterName) { Log-Info -Message "Obtained cluster name is null, so getting the cluster Name from the answer file" -ConsoleOut $ClusterName = GetClusterNameFromAnswerFile -AnswerFilePath $AnswerFilePath Log-Info -Message "Obtained cluster name from answer file is $ClusterName" -ConsoleOut } Log-Info -Message "Starting Deployment Settings Validation Operation" -ConsoleOut $storageAccountName = GetStorageAccountName -ClusterName $ClusterName -Prefix $Prefix $KVName = GetKeyVaultName -ClusterName $ClusterName -Prefix $Prefix $deploymentSettingsObject = Get-Content $AnswerFilePath | ConvertFrom-Json if ($null -eq $deploymentSettingsObject){ throw "Deployment Settings Object cannot be null" } Log-Info -Message "Deployment Settings Object obtained is $deploymentSettingsObject" -ConsoleOut $kvResource = Get-AzResource -Name $KVName -ResourceType "Microsoft.KeyVault/vaults" -ResourceGroupName $ResourceGroup $kvVaultUri = $kvResource.Properties.vaultUri Log-Info -Message "Key Vault Uri obtained is $kvVaultUri" -ConsoleOut # Will Trigger Upgrade $deploymentSettingsParameters = ReplaceDeploymentSettingsParametersTemplateWithActualValuesForClusterUpgrade -deploymentSettingsObject $deploymentSettingsObject -clusterName $ClusterName -arcNodeResourceIds $ArcNodeIds -secretsLocation $kvVaultUri -Prefix $Prefix if ($null -eq $deploymentSettingsParameters){ throw "Deployment Settings Parameters cannot be null" } $deploymentSettingsParameters.parameters.deploymentMode.value = "Deploy" $deploymentSettingsParameters.parameters.operationType.value = "ClusterUpgrade" Log-Info -Message "Deployment settings parameters obtained is $deploymentSettingsParameters" -ConsoleOut $deploymentSettingsParametersJson = $deploymentSettingsParameters | ConvertTo-Json -Depth 100 Log-Info -Message "Deployment Settings Parameters to json is $deploymentSettingsParametersJson" -ConsoleOut $updatedDeploymentSettingsParametersFilePath = (Join-Path -Path $env:TEMP -ChildPath "\DeploymentSettingsReportedPropertiesDeploy.json") Log-Info -Message "Updated Deployment Settings Parameters File Path $updatedDeploymentSettingsParametersFilePath" -ConsoleOut Set-Content -Path $updatedDeploymentSettingsParametersFilePath -Value $deploymentSettingsParametersJson | Out-Null $deploymentSettingsTemplateFilePath = (Join-Path -Path $PSScriptRoot -ChildPath "Templates\DeploymentSettingsTemplateForClusterUpgrade.json") $deploymentIdentifier = [guid]::NewGuid().ToString().Split("-")[0] $deploymentSettingsValidationName = $ResourceGroup + "-DSDeploy" + $deploymentIdentifier Log-Info -Message "Deployment Settings Template File Path $deploymentSettingsTemplateFilePath and Deployment Name $deploymentSettingsDeploymentName" -ConsoleOut $currentDeploymentNameFilePath = (Join-Path -Path $env:TEMP -ChildPath "\DeploymentName.txt") Log-Info -Message "Current Deployment File Path where the deployment name is stored is $currentDeploymentNameFilePath" -ConsoleOut Set-Content -Path $currentDeploymentNameFilePath -Value $deploymentSettingsValidationName | Out-Null New-AzResourceGroupDeployment -Name $deploymentSettingsValidationName -ResourceGroupName $ResourceGroup -TemplateFile $deploymentSettingsTemplateFilePath -TemplateParameterFile $updatedDeploymentSettingsParametersFilePath -Force -Verbose -AsJob Start-Sleep -Seconds 120 $deploymentStatus = Get-AzResourceGroupDeployment -ResourceGroupName $ResourceGroup -Name $deploymentSettingsValidationName $deploystatusString = $deploymentStatus | Out-String Log-Info -Message "Triggered the deployment Settings Resource in deploy mode: $deploystatusString " -ConsoleOut $deploymentOperation = Get-AzResourceGroupDeploymentOperation -DeploymentName $deploymentSettingsValidationName -ResourceGroup $ResourceGroup | ConvertTo-Json Log-Info -Message "Current Deployment Settings Validation Operation is $deploymentOperation" -ConsoleOut } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error throw $_ } finally { $Script:ErrorActionPreference = 'SilentlyContinue' Write-AzStackHciArcInstallerFooter -invocation $MyInvocation -Failed:$cmdletFailed -PassThru:$PassThru $DebugPreference = "Stop" } } function Invoke-AzStackHCIFullDeployment { [CmdletBinding(DefaultParametersetName = 'AZContext')] param ( [Parameter(Mandatory = $true, HelpMessage = "Azure Subscription Id for HCI Cluster Deployment")] [string] $SubscriptionID, [Parameter(Mandatory = $true, HelpMessage = "Azure Resource group used for HCI Cluster Deployment")] [string] $ResourceGroup, # AzureCloud , AzureUSGovernment , AzureChinaCloud [Parameter(Mandatory = $true, HelpMessage = "Azure Cloud type used for HCI Cluster Deployment. Valid values are : AzureCloud , AzureUSGovernment , AzureChinaCloud")] [string] $Cloud, [Parameter(Mandatory = $true, HelpMessage = "Azure Region used for HCI Cluster Deployment")] [string] $Region, [Parameter(Mandatory = $false, HelpMessage = "Azure Stack HCI Cluster Name for Registration")] [string] $ClusterName, [Parameter(Mandatory = $true, HelpMessage = "Local Admin Credentials Required for deployment")] [System.Management.Automation.PSCredential] $LocalAdminCredentials, [Parameter(Mandatory = $true, HelpMessage = "Cloud Admin Credentials Required for deployment")] [System.Management.Automation.PSCredential] $DomainAdminCredentials, [Parameter(Mandatory = $true, HelpMessage = "Arc Node ids required for cloud based deployment")] [string[]] $ArcNodeIds, [Parameter(Mandatory = $true, HelpMessage = "Answer file path required for deployment")] [string] $AnswerFilePath, [Parameter(Mandatory = $false, HelpMessage = "Return PSObject result.")] [System.Collections.Hashtable] $Tag, [Parameter(Mandatory = $false, HelpMessage = "Directory path for log and report output")] [string]$OutputPath, [Parameter(Mandatory = $false)] [Switch] $Force, [Parameter(Mandatory = $false, HelpMessage = "Prefix to uniquely identify a storage account and a keyvault")] [string] $Prefix ) try { $script:ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' $DebugPreference = "Continue" Set-AzStackHciOutputPath -Path $OutputPath if(CheckIfScriptIsRunByAdministrator){ Log-Info -Message "Script is run as administrator, so enabling" [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 3072; } else{ throw "This script should be executed in administrator mode or above" } $contextStatus = CheckIfAzContextIsSetOrNot if($contextStatus) { Log-Info -Message "Az Context is set, so proceeding" -ConsoleOut } else { throw "Az Context is not set , so cannot proceed with environment preparation, please run Connect-AzAccount and retry" } if ($null -eq $ClusterName) { Log-Info -Message "Obtained cluster name is null, so getting the cluster Name from the answer file" -ConsoleOut $ClusterName = GetClusterNameFromAnswerFile -AnswerFilePath $AnswerFilePath Log-Info -Message "Obtained cluster name from answer file is $ClusterName" -ConsoleOut } Log-Info -Message "Starting AzStackHci Full Deployment" -ConsoleOut $environmentPreparationParameters = @{ SubscriptionID = $SubscriptionID ResourceGroup = $ResourceGroup Region = $Region ClusterName = $ClusterName LocalAdminCredentials = $LocalAdminCredentials DomainAdminCredentials = $DomainAdminCredentials ArcNodeIds = $ArcNodeIds Tag = $Tag OutputPath = $OutputPath Force = $Force Prefix = $Prefix } Log-Info -Message "Successfully got the parameters for environment validation" -ConsoleOut Invoke-AzStackHCIEnvironmentPreparator @environmentPreparationParameters Log-Info -Message "Successfully prepared the environment for cloud deployment, triggering validation" $deploymentSettingsParameters = @{ SubscriptionID = $SubscriptionID ResourceGroup = $ResourceGroup Region = $Region ClusterName = $ClusterName ArcNodeIds = $ArcNodeIds AnswerFilePath = $AnswerFilePath Tag = $Tag OutputPath = $OutputPath Force = $Force Prefix = $Prefix } Log-Info -Message "Successfully got the parameters for deployment settings validation" -ConsoleOut Invoke-AzStackHCIEnvironmentValidator @deploymentSettingsParameters Log-Info -Message "Started polling on the environment validation status" PollDeploymentSettingsStatus -SubscriptionID $SubscriptionID -ResourceGroup $ResourceGroup -ClusterName $ClusterName Log-Info -Message "Environment Validation succeeded , so moving to the deployment stage" -ConsoleOut Invoke-AzStackHCIDeployment @deploymentSettingsParameters Log-Info -Message "Starting polling on the deployment action plan" PollDeploymentSettingsStatus -SubscriptionID $SubscriptionID -ResourceGroup $ResourceGroup -ClusterName $ClusterName Log-Info -Message "Congrats, the Azure Stack HCI cluster has been deployed successfully" } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error $cmdletFailed = $true Log-Info -Message "Clearing the resource group since deployment failed" Remove-AzResourceGroup -Name $ResourceGroup -Force -Verbose throw $_ } finally { $Script:ErrorActionPreference = 'SilentlyContinue' Write-AzStackHciArcInstallerFooter -invocation $MyInvocation -Failed:$cmdletFailed -PassThru:$PassThru $DebugPreference = "Stop" } } enum EdgeDeviceKind { HCI } function New-EdgeDevices { param ( # AzureCloud , AzureUSGovernment , AzureChinaCloud [Parameter(Mandatory = $true, HelpMessage = "Azure Cloud type used for HCI Cluster Deployment. Valid values are : AzureCloud , AzureUSGovernment , AzureChinaCloud")] [string] $Cloud, [Parameter(Mandatory = $true, HelpMessage = "Azure Region used for HCI Cluster Deployment")] [string] $Region, [Parameter(Mandatory = $true, HelpMessage = "Arc Node for which EdgeDevice resource needs to be created")] [string] $ArcNodeId, [Parameter(Mandatory = $true, HelpMessage = "Edge Device Kind that needs to be created")] [EdgeDeviceKind] $kind, [Parameter(Mandatory = $false, HelpMessage = "Directory path for log and report output")] [string]$OutputPath ) try { $extensionInstallationStatus = $true $script:ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' $DebugPreference = "Continue" Set-AzStackHciOutputPath -Path $OutputPath $contextStatus = CheckIfAzContextIsSetOrNot if($contextStatus) { Log-Info -Message "Az Context is set, so proceeding" -ConsoleOut } else { throw "Az Context is not set , so cannot proceed, please run Connect-AzAccount and retry" } $edgeDeviceNodeId = "$($ArcNodeId)/providers/Microsoft.AzureStackHCI/edgeDevices/default" $edgeDevicesValidateEndpointWithAPI = "{0}?api-version={1}" -f $edgeDeviceNodeId, $global:RPAPIVersion Log-Info -Message "EdgeDevices Endpoint URI : $edgeDevicesValidateEndpointWithAPI" -ConsoleOut $edgeDeviceResource = [PSCustomObject]@{ kind = "HCI" properties = @{} } $edgeDeviceResourcePayload = $edgeDeviceResource | ConvertTo-Json -Depth 100 Log-Info -Message "EdgeDevices payload : $($jsonString) " -ConsoleOut $response = Invoke-AzRestMethod -Path $edgeDevicesValidateEndpointWithAPI -Method PUT -Payload $edgeDeviceResourcePayload if ($response.StatusCode -eq 200) { Log-Info -Message "Feature is not enabled yet in this cloud region " -ConsoleOut throw "Extension installation from cloud is not enabled yet in this cloud region " } elseif($response.StatusCode -ge 300) { Log-Info -Message "Failure in creating EdgeDevices resource " -ConsoleOut throw "Failure while creating EdgeDevices resource statuscode: $($response.StatusCode) , edgeDeviceID: $($edgeDeviceNodeId) " } Log-Info -Message "EdgeDevices Creation response : $($response.StatusCode) " -ConsoleOut $asyncURL = $response.Headers.GetValues("Azure-AsyncOperation")[0] $stopLoop = $false $status = $false do { Log-Info -Message "Querying EdgeDevice creation status using : $asyncURL " -ConsoleOut $response = Invoke-AzRestMethod -URI $asyncURL -Method GET Log-Info -Message "EdgeDevice creation status: $response " -ConsoleOut $validationResponse = $response.Content | ConvertFrom-Json $prettyResponse = $validationResponse | ConvertTo-Json -Depth 100 Log-Info -Message "Validation status $prettyResponse" -ConsoleOut if( $validationResponse.status.Equals("Provisioning") -or $validationResponse.status.Equals("Accepted") ) { Start-Sleep -Seconds 25 } else { $stopLoop = $true Log-Info -Message "EdgeDevice creation has completed, checking whether extension installation has succeeded" -ConsoleOut $status = $validationResponse.status.Equals("Succeeded") Log-Info -Message "Extension installation completed with status $status" -ConsoleOut } } While (-Not $stopLoop) $response = Invoke-AzRestMethod -Path $edgeDevicesValidateEndpointWithAPI -Method GET $edgeDevicesJson = $response.Content | ConvertFrom-Json $edgeDeviceExtensionProfile = $edgeDevicesJson.properties.reportedProperties.extensionProfile | ConvertTo-Json -Depth 100 Log-Info -Message " EdgeDevice $edgeDevicesJson.id extension profile: $edgeDeviceExtensionProfile" -ConsoleOut if ($edgeDevicesJson.properties.reportedProperties.extensionProfile.extensions.Count -ne 4 ) { $extensionInstallationStatus = $false Log-Info -Message "Count of Extension [ $edgeDevicesJson.properties.reportedProperties.extensionProfile.extensions.Count ] status does not match the expected Extensions [4]" -ConsoleOut } foreach ($extensionState in $edgeDevicesJson.properties.reportedProperties.extensionProfile.extensions) { if ($extensionState.state -ne "Succeeded") { $extensionInstallationStatus = $false Log-Info -Message "$extensionState.extensionName is in $extensionState.state which is not expected state [Succeeded] " -ConsoleOut } } } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error $status = $false throw $_ } finally { $Script:ErrorActionPreference = 'SilentlyContinue' Write-AzStackHciArcInstallerFooter -invocation $MyInvocation -Failed:$cmdletFailed -PassThru:$PassThru $DebugPreference = "Stop" } if ($extensionInstallationStatus) { Log-Info -Message "All mandatory extensions were successfully installed" -ConsoleOut return $extensionInstallationStatus } else { throw "Extension installation status was not successful, check the logs to debug the issue" } } function Invoke-validateNodesForDeployment { param ( [Parameter(Mandatory = $true, HelpMessage = "Azure Subscription Id for HCI Cluster Deployment")] [string] $SubscriptionID, [Parameter(Mandatory = $true, HelpMessage = "Azure Resource group used for HCI Cluster Deployment")] [string] $ResourceGroup, # AzureCloud , AzureUSGovernment , AzureChinaCloud [Parameter(Mandatory = $true, HelpMessage = "Azure Cloud type used for HCI Cluster Deployment. Valid values are : AzureCloud , AzureUSGovernment , AzureChinaCloud")] [string] $Cloud, [Parameter(Mandatory = $true, HelpMessage = "Azure Region used for HCI Cluster Deployment")] [string] $Region, [Parameter(Mandatory = $true, HelpMessage = "Arc Node ids required for cloud based deployment")] [string[]] $ArcNodeIds, [Parameter(Mandatory = $false, HelpMessage = "Directory path for log and report output")] [string]$OutputPath ) try { $script:ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' $DebugPreference = "Continue" Set-AzStackHciOutputPath -Path $OutputPath $contextStatus = CheckIfAzContextIsSetOrNot if($contextStatus) { Log-Info -Message "Az Context is set, so proceeding" -ConsoleOut } else { throw "Az Context is not set , so cannot proceed with environment preparation, please run Connect-AzAccount and retry" } $edgeDeviceNodeIds=@() foreach ($arcResourceID in $ArcNodeIds) { $edgeDeviceNodeIds += "$($arcResourceID)/providers/Microsoft.AzureStackHCI/edgeDevices/default" } $edgeDevicesValidateEndpointWithAPI = "{0}/validate?api-version={1}" -f $edgeDeviceNodeIds[0], $global:RPAPIVersion Log-Info -Message "Validation Endpoint Uri : $edgeDevicesValidateEndpointWithAPI" -ConsoleOut $parameters = @{EdgeDeviceIds=$edgeDeviceNodeIds} $jsonString = $parameters | ConvertTo-Json Log-Info -Message "Validation action payload : $($jsonString) " -ConsoleOut $response = Invoke-AzRestMethod -Path $edgeDevicesValidateEndpointWithAPI -Method POST -Payload $jsonString Log-Info -Message "Validation action response : $($response.StatusCode) " -ConsoleOut $asyncURL = $response.Headers.GetValues("Azure-AsyncOperation") $stopLoop = $false $status = $false do { Log-Info -Message "Querying validation status using : $asyncURL[0] " -ConsoleOut $response = Invoke-AzRestMethod -URI $asyncURL[0] -Method GET Log-Info -Message "validation Response: $response " -ConsoleOut $validationResponse = $response.Content | ConvertFrom-Json $prettyResponse = $validationResponse | ConvertTo-Json -Depth 100 Log-Info -Message "Validation status $prettyResponse" -ConsoleOut if( $validationResponse.status.Equals("Inprogress") -or $validationResponse.status.Equals("Accepted") ) { Start-Sleep -Seconds 10 } else { $stopLoop = $true Log-Info -Message "Validation has completed, checking whether validation has succeeded" -ConsoleOut $status = $validationResponse.status.Equals("Succeeded") Log-Info -Message "Validation Response succeeded status $status" -ConsoleOut } } While (-Not $stopLoop) } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error $status = $false throw $_ } finally { $Script:ErrorActionPreference = 'SilentlyContinue' Write-AzStackHciArcInstallerFooter -invocation $MyInvocation -Failed:$cmdletFailed -PassThru:$PassThru $DebugPreference = "Stop" } if ($status) { Log-Info -Message "The status of node validation is successful" -ConsoleOut return $status } else { throw "Node validation was unsuccessful, check the logs to debug the issue" } } function CheckIfAzContextIsSetOrNot { try { $context = Get-AzContext if ([string]::IsNullOrEmpty($context)){ Log-Info -Message "Az Context is Not Set, so cannot run the operation" -ConsoleOut return $false } } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error return $false } return $true } function GetClusterNameFromAnswerFile { [CmdletBinding(DefaultParametersetName = 'AZContext')] param ( [Parameter(Mandatory = $true, HelpMessage = "Answer File Path")] [string] $AnswerFilePath ) try { $deploymentSettingsObject = Get-Content $AnswerFilePath | ConvertFrom-Json if ($null -eq $deploymentSettingsObject){ throw "Deployment Settings Object cannot be null" } $deploymentDataFromAnswerFile = $deploymentSettingsObject.ScaleUnits[0].DeploymentData $clusterName = $deploymentDataFromAnswerFile.Cluster.Name Log-Info -Message "Cluster Name obtained in answer file is $clusterName" -ConsoleOut if ($null -ne $clusterName) { Log-Info -Message "Cluster Name is not null, so returning clustername $clusterName" -ConsoleOut return $clusterName } } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error throw $_ } return $null } function CreateKeyVaultAndAddSecretsForClusterUpgrade { [CmdletBinding(DefaultParametersetName = 'AZContext')] param ( [Parameter(Mandatory = $true, HelpMessage = "Azure Subscription Id for HCI Cluster Deployment")] [string] $SubscriptionID, [Parameter(Mandatory = $true, HelpMessage = "Azure Resource group used for HCI Cluster Deployment")] [string] $ResourceGroup, [Parameter(Mandatory = $true, HelpMessage = "Azure Region used for HCI Cluster Deployment")] [string] $Region, [Parameter(Mandatory = $true, HelpMessage = "Azure Stack HCI Cluster Name for Registration")] [string] $ClusterName, [Parameter(Mandatory = $true, HelpMessage = "Local Admin Credentials Required for deployment")] [System.Management.Automation.PSCredential] $LocalAdminCredentials, [Parameter(Mandatory = $true, HelpMessage = "Cloud Admin Credentials Required for deployment")] [System.Management.Automation.PSCredential] $DomainAdminCredentials, [Parameter(Mandatory = $false, HelpMessage = "Prefix to uniquely identify a storage account and a keyvault")] [string] $Prefix, [Parameter(Mandatory = $false)] [System.Collections.Hashtable] $SBESecrets ) try { Log-Info -Message "Initializing the flow where the kv for cluster upgrade creation starts" -ConsoleOut # $storageAccountName = GetStorageAccountName -ClusterName $ClusterName -Prefix $Prefix $KVName = GetKeyVaultName -ClusterName $ClusterName -Prefix $Prefix # $storageWitnessKey = GetStorageWitnessKey -SubscriptionId $SubscriptionID -ResourceGroup $ResourceGroup -StorageAccountName $storageAccountName # if ($null -eq $storageWitnessKey){ # throw "Storage Witness Key is null, so cannot proceed with deployment" # } # Log-Info -Message "Successfully received the storage witness key for storage account $storageAccountName" -ConsoleOut # $storageWitnessKeyB64Encoded = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($storageWitnessKey)) #Starting to create the spn for ARB Deployment $spnDisplayName = GetSpnName -ClusterName $ClusterName -Prefix $Prefix $servicePrincialCreds = CreateServicePrincipalForCloudDeployment -DisplayName $spnDisplayName -ResourceGroup $ResourceGroup if ($null -eq $servicePrincialCreds){ throw "Service Principal Credentials are null, so cannot proceed with deployment" } Log-Info -Message "Successfully created the service principal and the corresponding credentials to put in the kv" -ConsoleOut Log-Info -Message "Starting Key Vault Creation...." -ConsoleOut $localAdminSecret = ExtractUsernameAndPasswordFromCredential -Credential $LocalAdminCredentials if ($null -eq $localAdminSecret){ throw "Local Admin secret cannot be null, so cannot proceed with deployment" } Log-Info -Message "Successfully extracted and encoded the Local Admin Credentials" $domainAdminSecret = ExtractUsernameAndPasswordFromCredential -Credential $DomainAdminCredentials if ($null -eq $domainAdminSecret){ throw "Domain Admin secret cannot be null, so cannot proceed with deployment" } Log-Info -Message "Successfully extracted and encoded the Domain Admin Credentials" $updatedSBESecretsList = ExtractSBECredentialsToKeyVaulepairs -Credentials $SBESecrets $keyVaultParameters = ReplaceKeyVaultTemplateWithActualValuesForClusterUpgrade -KVName $KVName -Region $Region -DomainAdminSecret $domainAdminSecret -ArbDeploymentSpnSecret $servicePrincialCreds -ClusterName $ClusterName -SBESecrets $updatedSBESecretsList if ($null -eq $keyVaultParameters){ throw "Key Vault parameters file could not be updated with actual values" } $deploymentIdentifier = [guid]::NewGuid().ToString().Split("-")[0] $KVDeploymentName = $KVName + "-KVDeploy" + $deploymentIdentifier $kvTemplateFilePath = (Join-Path -Path $PSScriptRoot -ChildPath "Templates\KeyVaultTemplate.json") Log-Info -Message "Key Vault Template file path $kvTemplateFilePath" -ConsoleOut $keyVaultParametersJson = $keyVaultParameters | ConvertTo-Json -Depth 4 Log-Info -Message "Json value of key vault parameters $keyVaultParametersJson" -ConsoleOut $updatedKVParametersFilePath = (Join-Path -Path $env:TEMP -ChildPath "\KeyVaultReportedParameters.json") Set-Content -Path $updatedKVParametersFilePath -Value $keyVaultParametersJson | Out-Null New-AzResourceGroupDeployment -Name $KVDeploymentName -ResourceGroupName $ResourceGroup -TemplateFile $kvTemplateFilePath -TemplateParameterFile $updatedKVParametersFilePath -Force $kvDeploymentStatus = Get-AzResourceGroupDeployment -ResourceGroupName $ResourceGroup -DeploymentName $KVDeploymentName if ($kvDeploymentStatus.ProvisioningState -eq "Succeeded"){ Log-Info -Message "Successfully deployed the KV with name $KVName" -ConsoleOut } else{ throw "KV Deployment Failed so not proceeding with the deployment" } } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error throw $_ } } function CreateKeyVaultAndAddSecrets { [CmdletBinding(DefaultParametersetName = 'AZContext')] param ( [Parameter(Mandatory = $true, HelpMessage = "Azure Subscription Id for HCI Cluster Deployment")] [string] $SubscriptionID, [Parameter(Mandatory = $true, HelpMessage = "Azure Resource group used for HCI Cluster Deployment")] [string] $ResourceGroup, [Parameter(Mandatory = $true, HelpMessage = "Azure Region used for HCI Cluster Deployment")] [string] $Region, [Parameter(Mandatory = $true, HelpMessage = "Azure Stack HCI Cluster Name for Registration")] [string] $ClusterName, [Parameter(Mandatory = $true, HelpMessage = "Local Admin Credentials Required for deployment")] [System.Management.Automation.PSCredential] $LocalAdminCredentials, [Parameter(Mandatory = $true, HelpMessage = "Cloud Admin Credentials Required for deployment")] [System.Management.Automation.PSCredential] $DomainAdminCredentials, [Parameter(Mandatory = $false, HelpMessage = "Prefix to uniquely identify a storage account and a keyvault")] [string] $Prefix, [Parameter(Mandatory = $false)] [System.Collections.Hashtable] $SBESecrets ) try { Log-Info -Message "Initializing the flow where the kv creation starts" -ConsoleOut $storageAccountName = GetStorageAccountName -ClusterName $ClusterName -Prefix $Prefix $KVName = GetKeyVaultName -ClusterName $ClusterName -Prefix $Prefix $storageWitnessKey = GetStorageWitnessKey -SubscriptionId $SubscriptionID -ResourceGroup $ResourceGroup -StorageAccountName $storageAccountName if ($null -eq $storageWitnessKey){ throw "Storage Witness Key is null, so cannot proceed with deployment" } Log-Info -Message "Successfully received the storage witness key for storage account $storageAccountName" -ConsoleOut $storageWitnessKeyB64Encoded = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($storageWitnessKey)) #Starting to create the spn for ARB Deployment $spnDisplayName = GetSpnName -ClusterName $ClusterName -Prefix $Prefix $servicePrincialCreds = CreateServicePrincipalForCloudDeployment -DisplayName $spnDisplayName -ResourceGroup $ResourceGroup if ($null -eq $servicePrincialCreds){ throw "Service Principal Credentials are null, so cannot proceed with deployment" } Log-Info -Message "Successfully created the service principal and the corresponding credentials to put in the kv" -ConsoleOut Log-Info -Message "Starting Key Vault Creation...." -ConsoleOut $localAdminSecret = ExtractUsernameAndPasswordFromCredential -Credential $LocalAdminCredentials if ($null -eq $localAdminSecret){ throw "Local Admin secret cannot be null, so cannot proceed with deployment" } Log-Info -Message "Successfully extracted and encoded the Local Admin Credentials" $domainAdminSecret = ExtractUsernameAndPasswordFromCredential -Credential $DomainAdminCredentials if ($null -eq $domainAdminSecret){ throw "Domain Admin secret cannot be null, so cannot proceed with deployment" } Log-Info -Message "Successfully extracted and encoded the Domain Admin Credentials" $updatedSBESecretsList = ExtractSBECredentialsToKeyVaulepairs -Credentials $SBESecrets $keyVaultParameters = ReplaceKeyVaultTemplateWithActualValues -KVName $KVName -Region $Region -LocalAdminSecret $localAdminSecret -DomainAdminSecret $domainAdminSecret -ArbDeploymentSpnSecret $servicePrincialCreds -StorageWitnessKey $storageWitnessKeyB64Encoded -ClusterName $ClusterName -SBESecrets $updatedSBESecretsList if ($null -eq $keyVaultParameters){ throw "Key Vault parameters file could not be updated with actual values" } $deploymentIdentifier = [guid]::NewGuid().ToString().Split("-")[0] $KVDeploymentName = $KVName + "-KVDeploy" + $deploymentIdentifier $kvTemplateFilePath = (Join-Path -Path $PSScriptRoot -ChildPath "Templates\KeyVaultTemplate.json") Log-Info -Message "Key Vault Template file path $kvTemplateFilePath" -ConsoleOut $keyVaultParametersJson = $keyVaultParameters | ConvertTo-Json -Depth 4 Log-Info -Message "Json value of key vault parameters $keyVaultParametersJson" -ConsoleOut $updatedKVParametersFilePath = (Join-Path -Path $env:TEMP -ChildPath "\KeyVaultReportedParameters.json") Set-Content -Path $updatedKVParametersFilePath -Value $keyVaultParametersJson | Out-Null New-AzResourceGroupDeployment -Name $KVDeploymentName -ResourceGroupName $ResourceGroup -TemplateFile $kvTemplateFilePath -TemplateParameterFile $updatedKVParametersFilePath -Force $kvDeploymentStatus = Get-AzResourceGroupDeployment -ResourceGroupName $ResourceGroup -DeploymentName $KVDeploymentName if ($kvDeploymentStatus.ProvisioningState -eq "Succeeded"){ Log-Info -Message "Successfully deployed the KV with name $KVName" -ConsoleOut } else{ throw "KV Deployment Failed so not proceeding with the deployment" } } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error throw $_ } } function CreateStorageAccountForCloudDeployment { [CmdletBinding(DefaultParametersetName = 'AZContext')] param ( [Parameter(Mandatory = $true, HelpMessage = "Azure Resource group used for HCI Cluster Deployment")] [string] $ResourceGroup, [Parameter(Mandatory = $true, HelpMessage = "Azure Region used for HCI Cluster Deployment")] [string] $Region, [Parameter(Mandatory = $true, HelpMessage = "Azure Stack HCI Cluster Name for Registration")] [string] $ClusterName, [Parameter(Mandatory = $false, HelpMessage = "Prefix to uniquely identify a storage account and a keyvault")] [string] $Prefix ) try { Log-Info -Message "Starting to create the storage account for deployment" -ConsoleOut #Perform Storage Account Deployment here $storageAccountName = GetStorageAccountName -ClusterName $ClusterName -Prefix $Prefix $deploymentIdentifier = [guid]::NewGuid().ToString().Split("-")[0] $storageAccountDeploymentName = $storageAccountName + "sadeployment" + $deploymentIdentifier Log-Info -Message "Trying to create storage account with name $storageAccountName and Deployment Name $storageAccountDeploymentName" -ConsoleOut $storageAccountParameters = ReplaceStorageAccountTemplateWithActualValues -StorageAccountName $storageAccountName -Location $Region if ($null -ne $storageAccountParameters){ $storageAccountTemplateFilePath = (Join-Path -Path $PSScriptRoot -ChildPath "Templates\StorageAccountTemplate.json") Log-Info -Message "Storage Account Template File Path $storageAccountTemplateFilePath" $storageAccountParametersJson = $storageAccountParameters | ConvertTo-Json Log-Info -Message "Storage Account Parameters Converted to JSON is $storageAccountParametersJson" -ConsoleOut $updatedStorageAccountParametersFilePath = (Join-Path -Path $env:TEMP -ChildPath "\StorageAccountReportedParameters.json") Log-Info -Message "Updated Storage Account Parameters File Path is $updatedStorageAccountParametersFilePath" -ConsoleOut Set-Content -Path $updatedStorageAccountParametersFilePath -Value $storageAccountParametersJson | Out-Null New-AzResourceGroupDeployment -Name $storageAccountDeploymentName -ResourceGroupName $ResourceGroup -TemplateFile $storageAccountTemplateFilePath -TemplateParameterFile $updatedStorageAccountParametersFilePath -Force $statusOfStorageAccountDeployment = Get-AzResourceGroupDeployment -ResourceGroupName $ResourceGroup -DeploymentName $storageAccountDeploymentName if ($statusOfStorageAccountDeployment.ProvisioningState -eq "Succeeded"){ Log-Info -Message "Storage Account $storageAccountName is created successfully" -ConsoleOut } else{ throw "Storage account deployment with name $storageAccountName and deploymentName $storageAccountDeploymentName failed" } } else{ throw "Could not replace storage account parameter template with the parameter values" } } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error throw $_ } } function CreateClusterAndAssignRoles { [CmdletBinding(DefaultParametersetName = 'AZContext')] param ( [Parameter(Mandatory = $true, HelpMessage = "Azure Subscription Id for HCI Cluster Deployment")] [string] $SubscriptionID, [Parameter(Mandatory = $true, HelpMessage = "Azure Resource group used for HCI Cluster Deployment")] [string] $ResourceGroup, [Parameter(Mandatory = $true, HelpMessage = "Azure Region used for HCI Cluster Deployment")] [string] $Region, [Parameter(Mandatory = $true, HelpMessage = "Azure Stack HCI Cluster Name for Registration")] [string] $ClusterName ) try { # Checking if cluster is already deployed DeleteClusterResourceIfAlreadyExists -ClusterName $ClusterName -SubscriptionID $SubscriptionID -ResourceGroupName $ResourceGroup # Trying to create the cluster object $properties = [ResourceProperties]::new($Region, @{}) $payload = ConvertTo-Json -InputObject $properties Log-Info -Message "Payload for cluster creation is $payload" -ConsoleOut $resourceId = "/subscriptions/$SubscriptionID/resourceGroups/$ResourceGroup/providers/Microsoft.AzureStackHCI/clusters/$ClusterName" $resourceIdApiVersion = "{0}?api-version={1}" -f $resourceId, $global:RPAPIVersion Log-Info -Message "Resource Id is $resourceId" -ConsoleOut $clusterResult = New-ClusterWithRetries -ResourceIdWithAPI $resourceIdApiVersion -Payload $payload if ($clusterResult -eq $false) { throw "Cluster creation with name $ClusterName failed in $Region with Resource Group $ResourceGroup" } $clusterResource = Get-AzResource -ResourceId $resourceId -ApiVersion $global:RPAPIVersion -ErrorAction SilentlyContinue if ($null -ne $clusterResource) { Log-Info -Message "Successfully created the cluster resource $clusterResource" -ConsoleOut #Assigning permission to the HCI first party object id on the resource group level AssignRolesToHCIResourceProvider -ResourceGroup $ResourceGroup -hciObjectId $clusterResource.Properties.resourceProviderObjectId } else { throw "Cluster creation with name $ClusterName failed in $Region with Resource Group $ResourceGroup" } } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error throw $_ } } function PollDeploymentSettingsStatus { param ( [Parameter(Mandatory = $true, HelpMessage = "Azure Subscription Id for HCI Cluster Deployment")] [string] $SubscriptionID, [Parameter(Mandatory = $true, HelpMessage = "Azure Resource group used for HCI Cluster Deployment")] [string] $ResourceGroup, [Parameter(Mandatory = $true)] [string] $ClusterName ) $deploymentSettingsResourceUri = "/subscriptions/$SubscriptionID/resourceGroups/$ResourceGroup/providers/Microsoft.AzureStackHCI/clusters/$ClusterName/deploymentSettings/default" Log-Info -Message "Deployment Settings Resource Uri is $deploymentSettingsResourceUri" -ConsoleOut $currentDeploymentNameFilePath = (Join-Path -Path $env:TEMP -ChildPath "\DeploymentName.txt") Log-Info -Message "Current Deployment Name stored file path is $currentDeploymentNameFilePath" -ConsoleOut $currentDeploymentName = Get-Content -Path $currentDeploymentNameFilePath $currentDeploymentName = $currentDeploymentName.Trim() Log-Info -Message "Current Deployment Name obtained is $currentDeploymentName" -ConsoleOut $stopLoop = $false $status = $false $currentDeploymentSettingsResource = $null $currentDeploymentOperationStatus = $null do { $deploymentSettingsResource = Get-AzResource -ResourceId $deploymentSettingsResourceUri -ApiVersion $global:RPAPIVersion -Verbose $currentDeploymentSettingsResource = $deploymentSettingsResource | ConvertTo-Json Log-Info -Message "Deployment Settings Resource obtained is $currentDeploymentSettingsResource" -ConsoleOut $provisioningState = $deploymentSettingsResource.properties.provisioningState if (("Succeeded" -eq $provisioningState) -or ("Failed" -eq $provisioningState) -or ("Cancelled" -eq $provisioningState)){ $stopLoop = $true if (("Succeeded" -eq $provisioningState)){ $status = $true } Log-Info -Message "Provisioning State has reached a terminal state, so closing the operation" -ConsoleOut } if (-Not [string]::IsNullOrEmpty($currentDeploymentName)) { $currentDeploymentOperationStatus = Get-AzResourceGroupDeploymentOperation -DeploymentName $currentDeploymentName -ResourceGroupName $ResourceGroup | ConvertTo-Json Log-Info -Message "Current Deployment Operation status is $currentDeploymentOperationStatus" -ConsoleOut } $reportedProperties = $deploymentSettingsResource.properties.reportedProperties $reportedPropertiesJson = $reportedProperties | ConvertTo-Json Log-Info -Message "Reported Properties obtained is $reportedPropertiesJson" -ConsoleOut Start-Sleep -Seconds 120 } While (-Not $stopLoop) if (-not $status) { Log-Info -Message "The current deployment settings resource is in failed state, so throwing an exception, deployment settings resource = $currentDeploymentSettingsResource" -ConsoleOut throw "Deployment Settings resource is in Failed state , current deployment operation = $currentDeploymentOperationStatus" } } function RegisterRequiredResourceProviders { try { Log-Info -Message "Registering required resource providers" -ConsoleOut Register-RPIfRequired -ProviderNamespace "Microsoft.HybridCompute" Register-RPIfRequired -ProviderNamespace "Microsoft.GuestConfiguration" Register-RPIfRequired -ProviderNamespace "Microsoft.HybridConnectivity" Register-RPIfRequired -ProviderNamespace "Microsoft.AzureStackHCI" Register-RPIfRequired -ProviderNamespace "Microsoft.Storage" Register-RPIfRequired -ProviderNamespace "Microsoft.KeyVault" Register-RPIfRequired -ProviderNamespace "Microsoft.ResourceConnector" Register-RPIfRequired -ProviderNamespace "Microsoft.HybridContainerService" Register-RPIfRequired -ProviderNamespace "Microsoft.Attestation" Log-Info -Message "Successfully registered Resource Providers" -ConsoleOut } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error } } function Register-RPIfRequired{ param( [string] $ProviderNamespace ) $rpState = Get-AzResourceProvider -ProviderNamespace $ProviderNamespace $notRegisteredResourcesForRP = ($rpState.Where({$_.RegistrationState -ne "Registered"}) | Measure-Object ).Count if ($notRegisteredResourcesForRP -eq 0 ) { Log-Info -Message "$ProviderNamespace RP already registered, skipping registration" -ConsoleOut } else { try { Register-AzResourceProvider -ProviderNamespace $ProviderNamespace | Out-Null Log-Info -Message "registered Resource Provider: $ProviderNamespace " -ConsoleOut } catch { Log-Info -Message -Message "Exception occured while registering $ProviderNamespace RP, $_" -ConsoleOut throw } } } function GetStorageAccountName { param ( [Parameter(Mandatory = $true)] [string] $ClusterName, [Parameter(Mandatory = $false)] [string] $Prefix ) try { $storageAccountName = $ClusterName + "sa" if ([string]::IsNullOrEmpty($Prefix)) { Log-Info -Message "Storage account name with null prefix is $storageAccountName" -ConsoleOut } else { $storageAccountName = $storageAccountName + $Prefix Log-Info -Message "Storage account name appended with prefix is $storageAccountName" -ConsoleOut } $storageAccountName = $storageAccountName -replace "[^a-zA-Z0-9]", "" $storageAccountName = $storageAccountName.ToLower() if ($storageAccountName.Length -gt 24) { $storageAccountName = $storageAccountName.Substring(0, 24) } Log-Info -Message "Storage account name is $storageAccountName" -ConsoleOut return $storageAccountName } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error throw $_ } } function GetKeyVaultName { param ( [Parameter(Mandatory = $true)] [string] $ClusterName, [Parameter(Mandatory = $false)] [string] $Prefix ) try { $KVName = $ClusterName + "-KV" if ([string]::IsNullOrEmpty($Prefix)) { Log-Info -Message "KV Name with without prefix is $KVName" -ConsoleOut } else { $KVName = $KVName + $Prefix Log-Info -Message "KV Name with unique prefix provided by user is $KVName" -ConsoleOut } $KVName = $KVName -replace "[^a-zA-Z0-9]", "" $KVName = $KVName.ToLower() if ($KVName.Length -gt 24) { $KVName = $KVName.Substring(0, 24) } Log-Info -Message "Key Vault name is $KVName" -ConsoleOut return $KVName } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error throw $_ } } function GetSpnName { param ( [Parameter(Mandatory = $true)] [string] $ClusterName, [Parameter(Mandatory = $false)] [string] $Prefix ) try { $spnDisplayName = $ClusterName + "-SPN" if ([string]::IsNullOrEmpty($Prefix)) { Log-Info -Message "Spn display name without prefix is $spnDisplayName" -ConsoleOut } else { $spnDisplayName = $ClusterName + "-SPN" + $Prefix Log-Info -Message "Spn display name with prefix is $spnDisplayName" -ConsoleOut } return $spnDisplayName } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error throw $_ } } function CheckIfKVAlreadyExists { param ( [Parameter(Mandatory = $true)] [string] $KVName, [Parameter(Mandatory = $true)] [string] $ResourceGroupName ) try { $kvAccount = Get-AzResource -Name $KVName -ResourceType "Microsoft.KeyVault/vaults" -ResourceGroupName $ResourceGroupName -ErrorAction SilentlyContinue if (($null -ne $kvAccount) -and ($null -ne $kvAccount.properties.ProvisioningState)){ $status = $kvAccount.properties.ProvisioningState if (($status -eq "Succeeded")){ Log-Info -Message "Key Vault with the same name $kvAccount exists in the Resource Group $ResourceGroupName" -ConsoleOut return [ErrorDetail]::KeyVaultAlreadyExists } } } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error } return [ErrorDetail]::NotFound } function CheckIfStorageAccountAlreadyExists { param ( [Parameter(Mandatory = $true)] [string] $StorageAccountName, [Parameter(Mandatory = $true)] [string] $ResourceGroupName ) try { $storageAccount = Get-AzResource -Name $StorageAccountName -ResourceType "Microsoft.Storage/storageAccounts" -ResourceGroupName $ResourceGroupName -ErrorAction SilentlyContinue if (($null -ne $storageAccount) -and ($null -ne $storageAccount.properties.ProvisioningState)){ $status = $storageAccount.properties.ProvisioningState if (($status -eq "Succeeded")){ Log-Info -Message "Storage Account with the same name $StorageAccountName exists in the Resource Group $ResourceGroupName" -ConsoleOut return [ErrorDetail]::StorageAccountAlreadyExists } } } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error } return [ErrorDetail]::NotFound } function DeleteClusterResourceIfAlreadyExists { param ( [Parameter(Mandatory = $true)] [string] $ClusterName, [Parameter(Mandatory = $true)] [string] $SubscriptionID, [Parameter(Mandatory = $true)] [string] $ResourceGroupName ) try { $clusterResourceUri = "/subscriptions/$SubscriptionID/resourceGroups/$ResourceGroupName/providers/Microsoft.AzureStackHCI/clusters/$ClusterName" Log-Info -Message "Cluster Resource Uri obtained is $clusterResourceUri" -ConsoleOut $stopLoop = $false do{ $clusterResource = Get-AzResource -ResourceId $clusterResourceUri -ApiVersion $global:RPAPIVersion -ErrorAction SilentlyContinue Log-Info -Message "Current cluster resource obtained is $clusterResource" -ConsoleOut if ($null -ne $clusterResource) { Log-Info -Message "Current cluster resource is not null, so deleting the cluster resource" -ConsoleOut Remove-AzResource -ResourceId $clusterResourceUri -Force Log-Info -Message "Successfully triggered delete of the cluster resource $clusterResourceUri" -ConsoleOut } else { Log-Info -Message "Current cluster resource is deleted, so returning" -ConsoleOut $stopLoop = true } Start-Sleep -Seconds 120 } While (-Not $stopLoop) Log-Info -Message "Triggering a force delete of the cluster even though it is null" -ConsoleOut Remove-AzResource -ResourceId $clusterResourceUri -Force -ErrorAction SilentlyContinue } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error } } function GetStorageWitnessKey { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $SubscriptionId, [Parameter(Mandatory = $true)] [string] $ResourceGroup, [Parameter(Mandatory = $true)] [string] $StorageAccountName ) try { $resourceId = "/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Storage/storageAccounts/{2}" -f $SubscriptionId, $ResourceGroup, $StorageAccountName Log-Info -Message "Resource id of storage account is $resourceId" -ConsoleOut $res = Invoke-AzResourceAction -ResourceId $resourceId -Action "listKeys" -ApiVersion "2023-01-01" -Force Log-Info -Message "Successfully got the keys for the storage account $StorageAccountName" -ConsoleOut if (($null -ne $res) -and ($res.keys.Count -gt 0)){ return $res.keys[0].value } } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error } return $null } function ReplaceDeploymentSettingsParametersTemplateWithActualValues { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [object] $deploymentSettingsObject, [Parameter(Mandatory = $true)] [string] $clusterName, [Parameter(Mandatory = $true)] [string[]] $arcNodeResourceIds, [Parameter(Mandatory = $true)] [string] $storageAccountName, [Parameter(Mandatory = $true)] [string] $secretsLocation, [Parameter(Mandatory = $false)] [PSCustomObject[]] $sbeCredentialsList, [Parameter(Mandatory = $false, HelpMessage = "For testing preview features enable this flag")] [bool] $preview, [Parameter(Mandatory = $false, HelpMessage = "Prefix to uniquely identify a storage account and a keyvault")] [string] $Prefix ) try { $customLocationName = $clusterName + "-customlocation" $KVName = GetKeyVaultName -ClusterName $ClusterName -Prefix $Prefix $deploymentDataFromAnswerFile = $deploymentSettingsObject.ScaleUnits[0].DeploymentData $deploymentSettingsParameterFilePath = "" if ($preview) { $deploymentSettingsParameterFilePath = (Join-Path -Path $PSScriptRoot -ChildPath "Parameters\DeploymentSettingsParametersPreview.json") } else { $deploymentSettingsParameterFilePath = (Join-Path -Path $PSScriptRoot -ChildPath "Parameters\DeploymentSettingsParameters.json") } $deploymentSettingsParameters = Get-Content $deploymentSettingsParameterFilePath | ConvertFrom-Json $deploymentSettingsParameters.parameters.name.value = $clusterName $deploymentSettingsParameters.parameters.arcNodeResourceIds.value = $arcNodeResourceIds $deploymentSettingsParameters.parameters.domainFqdn.value = $deploymentDataFromAnswerFile.DomainFQDN $deploymentSettingsParameters.parameters.namingPrefix.value = $deploymentDataFromAnswerFile.NamingPrefix $deploymentSettingsParameters.parameters.adouPath.value = $deploymentDataFromAnswerFile.ADOUPath $deploymentSettingsParameters.parameters.driftControlEnforced.value = $deploymentDataFromAnswerFile.SecuritySettings.DriftControlEnforced $deploymentSettingsParameters.parameters.credentialGuardEnforced.value = $deploymentDataFromAnswerFile.SecuritySettings.CredentialGuardEnforced $deploymentSettingsParameters.parameters.smbSigningEnforced.value = $deploymentDataFromAnswerFile.SecuritySettings.SMBSigningEnforced $deploymentSettingsParameters.parameters.smbClusterEncryption.value = $deploymentDataFromAnswerFile.SecuritySettings.SMBClusterEncryption $deploymentSettingsParameters.parameters.bitlockerBootVolume.value = $deploymentDataFromAnswerFile.SecuritySettings.BitlockerBootVolume $deploymentSettingsParameters.parameters.bitlockerDataVolumes.value = $deploymentDataFromAnswerFile.SecuritySettings.BitlockerDataVolumes $deploymentSettingsParameters.parameters.wdacEnforced.value = $deploymentDataFromAnswerFile.SecuritySettings.WDACEnforced $deploymentSettingsParameters.parameters.streamingDataClient.value = $deploymentDataFromAnswerFile.Observability.StreamingDataClient $deploymentSettingsParameters.parameters.euLocation.value = $deploymentDataFromAnswerFile.Observability.EULocation $deploymentSettingsParameters.parameters.episodicDataUpload.value = $deploymentDataFromAnswerFile.Observability.EpisodicDataUpload $deploymentSettingsParameters.parameters.clusterName.value = $clusterName $deploymentSettingsParameters.parameters.storageWitnessSecretName.value = $clusterName + "-WitnessStorageKey" $deploymentSettingsParameters.parameters.LocalAdminCredentialSecretName.value = $clusterName + "-LocalAdminCredential" $deploymentSettingsParameters.parameters.domainAdminSecretName.value = $clusterName + "-AzureStackLCMUserCredential" $deploymentSettingsParameters.parameters.arbDeploymentSpnSecretName.value = $clusterName + "-DefaultARBApplication" $deploymentSettingsParameters.parameters.keyVaultName.value = $KVName $deploymentSettingsParameters.parameters.cloudAccountName.value = $storageAccountName $deploymentSettingsParameters.parameters.configurationMode.value = $deploymentDataFromAnswerFile.Storage.ConfigurationMode $deploymentSettingsParameters.parameters.subnetMask.value = $deploymentDataFromAnswerFile.InfrastructureNetwork.SubnetMask $deploymentSettingsParameters.parameters.defaultGateway.value = $deploymentDataFromAnswerFile.InfrastructureNetwork.Gateway $deploymentSettingsParameters.parameters.infrastructureIpPoolSettings.value = @(GetInfrastructureNetworkIPPoolSettingsFromAnswerFile -deploymentData $deploymentDataFromAnswerFile) $deploymentSettingsParameters.parameters.dnsServers.value = @($deploymentDataFromAnswerFile.InfrastructureNetwork.DNSServers) $deploymentSettingsParameters.parameters.physicalNodesSettings.value = @(GetPhysicalNodesSettingsFromAnswerFile -deploymentData $deploymentDataFromAnswerFile -preview $preview) $deploymentSettingsParameters.parameters.storageNetworkList.value = @(GetStorageNetworkListFromDeploymentData -deploymentData $deploymentDataFromAnswerFile) $deploymentSettingsParameters.parameters.intentList.value = @(GetNetworkIntents -deploymentData $deploymentDataFromAnswerFile) $deploymentSettingsParameters.parameters.customLocation.value = $customLocationName $deploymentSettingsParameters.parameters.secretsLocation.value = $secretsLocation # TODO: useDHCP in answerfile needs to be verified. if (-Not [string]::IsNullOrEmpty($deploymentDataFromAnswerFile.InfrastructureNetwork.UseDhcp)) { $deploymentSettingsParameters.parameters.useDhcp.value = $deploymentDataFromAnswerFile.InfrastructureNetwork.UseDhcp } if ($preview -and (-Not [string]::IsNullOrEmpty($deploymentDataFromAnswerFile.Cluster.ClusterPattern))) { $deploymentSettingsParameters.parameters.clusterPattern.value = $deploymentDataFromAnswerFile.Cluster.ClusterPattern } if ($preview -and (-Not [string]::IsNullOrEmpty($deploymentDataFromAnswerFile.IdentityProvider))) { $deploymentSettingsParameters.parameters.identityProvider.value = $deploymentDataFromAnswerFile.IdentityProvider } if ($preview -and (-Not ($null -eq $deploymentDataFromAnswerFile.InfrastructureNetwork.DnsZones -or $deploymentDataFromAnswerFile.InfrastructureNetwork.DnsZones.Count -eq 0))) { $deploymentSettingsParameters.parameters.dnsZones.value = @(GetDnsZonesInformationFromDeploymentData -deploymentData $deploymentDataFromAnswerFile) } if ($preview -and (-Not [string]::IsNullOrEmpty($deploymentDataFromAnswerFile.Cluster.ReplicationMode))) { $deploymentSettingsParameters.parameters.replicationMode.value = $deploymentDataFromAnswerFile.Cluster.ReplicationMode } if (-Not [string]::IsNullOrEmpty($deploymentDataFromAnswerFile.HostNetwork.EnableStorageAutoIP)) { $deploymentSettingsParameters.parameters.enableStorageAutoIp.value = $deploymentDataFromAnswerFile.HostNetwork.EnableStorageAutoIP } else { # IF EnableStorageAutoIP is not available in Lab Answerfile, assume true as default. $deploymentSettingsParameters.parameters.enableStorageAutoIp.value = $true } if (-Not [string]::IsNullOrEmpty($deploymentDataFromAnswerFile.HostNetwork.StorageConnectivitySwitchless)) { $deploymentSettingsParameters.parameters.storageConnectivitySwitchless.value = $deploymentDataFromAnswerFile.HostNetwork.StorageConnectivitySwitchless } if ($null -ne $deploymentDataFromAnswerFile.SdnIntegration -and $null -ne $deploymentDataFromAnswerFile.SdnIntegration.NetworkController ) { $deploymentSettingsParameters.parameters.sdnIntegration.value = GetSDNIntegrationFromDeploymentData -deploymentData $deploymentDataFromAnswerFile } $sbePartnerInfo = $deploymentSettingsObject.ScaleUnits[0].sbePartnerInfo if ($null -ne $sbePartnerInfo) { $deploymentSettingsParameters.parameters.sbeVersion.value = $sbePartnerInfo.sbeDeploymentInfo.version $deploymentSettingsParameters.parameters.sbeFamily.value = $sbePartnerInfo.sbeDeploymentInfo.family $deploymentSettingsParameters.parameters.sbePublisher.value = $sbePartnerInfo.sbeDeploymentInfo.publisher $deploymentSettingsParameters.parameters.sbeManifestSource.value = $sbePartnerInfo.sbeDeploymentInfo.sbeManifestSource $deploymentSettingsParameters.parameters.sbeManifestCreationDate.value = $sbePartnerInfo.sbeDeploymentInfo.sbeManifestCreationDate $deploymentSettingsParameters.parameters.partnerProperties.value = $sbePartnerInfo.partnerProperties $deploymentSettingsParameters.parameters.credentiallist.value = $sbeCredentialsList } Log-Info -Message "Deployment Settings Parameters Object $deploymentSettingsParameters" -ConsoleOut return $deploymentSettingsParameters } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error } return $null } function ReplaceDeploymentSettingsParametersTemplateWithActualValuesForClusterUpgrade { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [object] $deploymentSettingsObject, [Parameter(Mandatory = $true)] [string] $clusterName, [Parameter(Mandatory = $true)] [string[]] $arcNodeResourceIds, [Parameter(Mandatory = $true)] [string] $secretsLocation, [Parameter(Mandatory = $false)] [PSCustomObject[]] $sbeCredentialsList, [Parameter(Mandatory = $false, HelpMessage = "Prefix to uniquely identify a storage account and a keyvault")] [string] $Prefix ) try { $customLocationName = $clusterName + "-customlocation" $KVName = GetKeyVaultName -ClusterName $ClusterName -Prefix $Prefix $deploymentDataFromAnswerFile = $deploymentSettingsObject.ScaleUnits[0].DeploymentData $deploymentSettingsParameterFilePath = (Join-Path -Path $PSScriptRoot -ChildPath "Parameters\DeploymentSettingsParametersForClusterUpgrade.json") $deploymentSettingsParameters = Get-Content $deploymentSettingsParameterFilePath | ConvertFrom-Json $deploymentSettingsParameters.parameters.name.value = $clusterName $deploymentSettingsParameters.parameters.arcNodeResourceIds.value = $arcNodeResourceIds $deploymentSettingsParameters.parameters.domainFqdn.value = $deploymentDataFromAnswerFile.DomainFQDN $deploymentSettingsParameters.parameters.namingPrefix.value = $deploymentDataFromAnswerFile.NamingPrefix $deploymentSettingsParameters.parameters.streamingDataClient.value = $true $deploymentSettingsParameters.parameters.euLocation.value = $true $deploymentSettingsParameters.parameters.episodicDataUpload.value = $true $deploymentSettingsParameters.parameters.clusterName.value = $deploymentDataFromAnswerFile.Cluster.Name # This is Failover Cluster name $deploymentSettingsParameters.parameters.configurationMode.value = "InfraOnly" $deploymentSettingsParameters.parameters.domainAdminSecretName.value = $clusterName + "-AzureStackLCMUserCredential" $deploymentSettingsParameters.parameters.arbDeploymentSpnSecretName.value = $clusterName + "-DefaultARBApplication" $deploymentSettingsParameters.parameters.keyVaultName.value = $KVName $deploymentSettingsParameters.parameters.subnetMask.value = $deploymentDataFromAnswerFile.InfrastructureNetwork.SubnetMask $deploymentSettingsParameters.parameters.defaultGateway.value = $deploymentDataFromAnswerFile.InfrastructureNetwork.Gateway $deploymentSettingsParameters.parameters.infrastructureIpPoolSettings.value = @(GetInfrastructureNetworkIPPoolSettingsFromAnswerFile -deploymentData $deploymentDataFromAnswerFile) $deploymentSettingsParameters.parameters.dnsServers.value = @($deploymentDataFromAnswerFile.InfrastructureNetwork.DNSServers) $deploymentSettingsParameters.parameters.physicalNodesSettings.value = @(GetPhysicalNodesSettingsFromAnswerFileForClusterUpgrade -deploymentData $deploymentDataFromAnswerFile) $deploymentSettingsParameters.parameters.adouPath.value = $deploymentDataFromAnswerFile.ADOUPath $deploymentSettingsParameters.parameters.customLocation.value = $customLocationName $deploymentSettingsParameters.parameters.secretsLocation.value = $secretsLocation Log-Info -Message "Deployment Settings Parameters Object $deploymentSettingsParameters" -ConsoleOut return $deploymentSettingsParameters } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error } return $null } function GetNetworkIntents { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [object] $deploymentData ) $networkIntents = @() try { $networkIntentList = $deploymentData.HostNetwork.Intents foreach ($intent in $networkIntentList) { $networkIntentInfo = New-Object -TypeName PSObject $networkIntentInfo | Add-Member -Name 'name' -MemberType Noteproperty -Value $intent.Name $networkIntentInfo | Add-Member -Name 'trafficType' -MemberType Noteproperty -Value @($intent.TrafficType) $networkIntentInfo | Add-Member -Name 'adapter' -MemberType Noteproperty -Value @($intent.Adapter) $networkIntentInfo | Add-Member -Name 'overrideVirtualSwitchConfiguration' -MemberType Noteproperty -Value $intent.OverrideVirtualSwitchConfiguration $networkIntentInfo | Add-Member -Name 'overrideQosPolicy' -MemberType Noteproperty -Value $intent.OverrideQosPolicy $networkIntentInfo | Add-Member -Name 'overrideAdapterProperty' -MemberType Noteproperty -Value $intent.overrideAdapterProperty $virtualSwitchConfigurationOverrides = New-Object -TypeName PSObject $virtualSwitchConfigurationOverrides | Add-Member -Name 'enableIov' -MemberType Noteproperty -Value $intent.VirtualSwitchConfigurationOverrides.EnableIov $virtualSwitchConfigurationOverrides | Add-Member -Name 'loadBalancingAlgorithm' -MemberType Noteproperty -Value $intent.VirtualSwitchConfigurationOverrides.LoadBalancingAlgorithm $networkIntentInfo | Add-Member -Name 'virtualSwitchConfigurationOverrides' -MemberType Noteproperty -Value $virtualSwitchConfigurationOverrides $qosPolicyOverrides = New-Object -TypeName PSObject $qosPolicyOverrides | Add-Member -Name 'priorityValue8021Action_Cluster' -MemberType Noteproperty -Value $intent.QosPolicyOverrides.PriorityValue8021Action_Cluster $qosPolicyOverrides | Add-Member -Name 'priorityValue8021Action_SMB' -MemberType Noteproperty -Value $intent.QosPolicyOverrides.PriorityValue8021Action_Cluster $qosPolicyOverrides | Add-Member -Name 'bandwidthPercentage_SMB' -MemberType Noteproperty -Value $intent.QosPolicyOverrides.BandwidthPercentage_SMB $networkIntentInfo | Add-Member -Name 'qosPolicyOverrides' -MemberType Noteproperty -Value $qosPolicyOverrides $adapterPropertyOverrides = New-Object -TypeName PSObject $adapterPropertyOverrides | Add-Member -Name 'jumboPacket' -MemberType Noteproperty -Value $intent.AdapterPropertyOverrides.JumboPacket if( ([string]::IsNullOrEmpty($intent.AdapterPropertyOverrides.NetworkDirect))) { $adapterPropertyOverrides | Add-Member -Name 'networkDirect' -MemberType Noteproperty -Value "Disabled" }else { $adapterPropertyOverrides | Add-Member -Name 'networkDirect' -MemberType Noteproperty -Value $intent.AdapterPropertyOverrides.NetworkDirect } $adapterPropertyOverrides | Add-Member -Name 'networkDirectTechnology' -MemberType Noteproperty -Value $intent.AdapterPropertyOverrides.NetworkDirectTechnology $networkIntentInfo | Add-Member -Name 'adapterPropertyOverrides' -MemberType Noteproperty -Value $adapterPropertyOverrides $networkIntents += $networkIntentInfo Log-Info -Message "Network Intent Info obtained is $networkIntentInfo" -ConsoleOut } } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error throw $_ } Log-Info -Message "Network Intents obtained is $networkIntents" -ConsoleOut return $networkIntents } function GetSDNIntegrationFromDeploymentData { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [object] $deploymentData ) $sdnIntegration = New-Object -TypeName psobject try { $networkControllerFromDeploymentData = $deploymentData.SDNIntegration.NetworkController $networkController = New-Object -TypeName psobject $networkController | Add-Member -Name 'macAddressPoolStart' -MemberType Noteproperty -Value $networkControllerFromDeploymentData.MacAddressPoolStart $networkController | Add-Member -Name 'macAddressPoolStop' -MemberType Noteproperty -Value $networkControllerFromDeploymentData.MacAddressPoolStop $networkController | Add-Member -Name 'networkVirtualizationEnabled' -MemberType Noteproperty -Value $networkControllerFromDeploymentData.NetworkVirtualizationEnabled $sdnIntegration | Add-Member -Name 'networkController' -MemberType Noteproperty -Value $networkController } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error throw $_ } Log-Info -Message "SDN integration parsed: $sdnIntegration" -ConsoleOut return $sdnIntegration } function GetStorageNetworkListFromDeploymentData { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [object] $deploymentData ) $storageNetworks = @() try { $storageNetworksList = $deploymentData.HostNetwork.StorageNetworks foreach ($network in $storageNetworksList) { $storageNetworkInfo = New-Object -TypeName psobject $storageNetworkInfo | Add-Member -Name 'name' -MemberType Noteproperty -Value $network.Name $storageNetworkInfo | Add-Member -Name 'networkAdapterName' -MemberType Noteproperty -Value $network.NetworkAdapterName $storageNetworkInfo | Add-Member -Name 'vlanId' -MemberType Noteproperty -Value $network.VlanId.ToString() if ($null -ne $network.StorageAdapterIPInfo) { $storageAdapterInfos = @() foreach ($storageAdapterIPInformation in $network.StorageAdapterIPInfo) { $storageAdapterIPInfo = New-Object -TypeName psobject $storageAdapterIPInfo | Add-Member -Name 'physicalNode' -MemberType Noteproperty -Value $storageAdapterIPInformation.PhysicalNode $storageAdapterIPInfo | Add-Member -Name 'ipv4Address' -MemberType Noteproperty -Value $storageAdapterIPInformation.IPv4Address $storageAdapterIPInfo | Add-Member -Name 'subnetMask' -MemberType Noteproperty -Value $storageAdapterIPInformation.SubnetMask $storageAdapterInfos += $storageAdapterIPInfo } $storageNetworkInfo | Add-Member -Name 'storageAdapterIPInfo' -MemberType Noteproperty -Value $storageAdapterInfos } $storageNetworks += $storageNetworkInfo Log-Info -Message "Storage Network Setting Info is $storageNetworkInfo" -ConsoleOut } } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error throw $_ } Log-Info -Message "Storage Network Settings Obtained is $storageNetworks" -ConsoleOut return $storageNetworks } function GetDnsZonesInformationFromDeploymentData { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [object] $deploymentData ) $dnsZones = @() try { $dnsZonesList = $deploymentData.InfrastructureNetwork.DnsZones foreach ($dnsZone in $dnsZonesList) { $dnsZonesInfo = New-Object -TypeName psobject $dnsZonesInfo | Add-Member -Name 'dnsZoneName' -MemberType Noteproperty -Value $dnsZone.DnsZoneName $dnsZonesInfo | Add-Member -Name 'dnsForwarder' -MemberType Noteproperty -Value @($dnsZone.DnsForwarder) $dnsZones += $dnsZonesInfo Log-Info -Message "DNS Zone Info is $dnsZonesInfo" -ConsoleOut } } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error throw $_ } Log-Info -Message "DNS Zones Obtained is $dnsZones" -ConsoleOut return $dnsZones } function GetInfrastructureNetworkIPPoolSettingsFromAnswerFile { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [object] $deploymentData ) $ipPoolSettings = @() try { $infrastructureNetworkIpPools = $deploymentData.InfrastructureNetwork.IPPools foreach ($ippool in $infrastructureNetworkIpPools) { $ippoolInfo = New-Object -TypeName psobject $ippoolInfo | Add-Member -Name 'startingAddress' -MemberType Noteproperty -Value $ippool.StartingAddress $ippoolInfo | Add-Member -Name 'endingAddress' -MemberType Noteproperty -Value $ippool.EndingAddress $ipPoolSettings += $ippoolInfo Log-Info -Message "Infrastructure Ip Pool info is $ippoolInfo" -ConsoleOut } } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error throw $_ } Log-Info -Message "Infrastructure Ip Pool Settings obtained is $ipPoolSettings" -ConsoleOut return $ipPoolSettings } function GetPhysicalNodesSettingsFromAnswerFile { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [object] $deploymentData, [Parameter(Mandatory = $false, HelpMessage = "For testing preview features enable this flag")] [bool] $preview ) $physicalNodeSettings = @() try { $physicalNodesData = $deploymentData.PhysicalNodes foreach ($settings in $physicalNodesData) { $physicalNodeInfo = New-Object -TypeName psobject $physicalNodeInfo | Add-Member -Name 'name' -MemberType Noteproperty -Value $settings.Name $physicalNodeInfo | Add-Member -Name 'ipv4Address' -MemberType Noteproperty -Value $settings.Ipv4Address if ($preview -and (-Not [string]::IsNullOrEmpty($settings.FaultDomainName))) { $physicalNodeInfo | Add-Member -Name 'faultDomainName' -MemberType Noteproperty -Value $settings.FaultDomainName } if ($preview -and (-Not [string]::IsNullOrEmpty($settings.FaultDomainMode))) { $physicalNodeInfo | Add-Member -Name 'faultDomainMode' -MemberType Noteproperty -Value $settings.faultDomainMode } $physicalNodeSettings += $physicalNodeInfo Log-Info -Message "Physical Node Ip info is $physicalNodeInfo" -ConsoleOut } } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error throw $_ } Log-Info -Message "Physical Node Settings obtained is $physicalNodeSettings" -ConsoleOut return $physicalNodeSettings } function GetPhysicalNodesSettingsFromAnswerFileForClusterUpgrade { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [object] $deploymentData, [Parameter(Mandatory = $false, HelpMessage = "For testing preview features enable this flag")] [bool] $preview ) $physicalNodeSettings = @() try { $physicalNodesData = $deploymentData.PhysicalNodes foreach ($settings in $physicalNodesData) { $physicalNodeInfo = New-Object -TypeName psobject $physicalNodeInfo | Add-Member -Name 'name' -MemberType Noteproperty -Value $settings.Name $physicalNodeSettings += $physicalNodeInfo Log-Info -Message "Physical Node Ip info is $physicalNodeInfo" -ConsoleOut } } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error throw $_ } Log-Info -Message "Physical Node Settings obtained is $physicalNodeSettings" -ConsoleOut return $physicalNodeSettings } function AssignPermissionsToArcMachines { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string[]] $ArcMachineIds, [Parameter(Mandatory = $true)] [string] $ResourceGroup ) try { ForEach ($arcMachineUri in $ArcMachineIds) { $objectId = GetArcMachineObjectId -ArcMachineUri $arcMachineUri if ($null -ne $objectId) { $setHCIRegistrationRoleResult = PerformObjectRoleAssignmentWithRetries -ObjectId $objectId -RoleName "Azure Stack HCI Device Management Role" -ResourceGroup $ResourceGroup -Verbose if ($setHCIRegistrationRoleResult -ne [ErrorDetail]::Success) { Log-Info -Message "Failed to assign the Azure Stack HCI Device Management Role on the resource group" -ConsoleOut -Type Error } else { Log-Info -Message "Successfully assigned the Azure Stack HCI Device Management Role on the resource group" -ConsoleOut } # Start: Temp role assignment for TVM action plan to work $tvmRoleAssignerRoleExists = $null -ne (Get-AzRoleDefinition -Name "TVM Attestation Role Assigner (Custom)") $tvmRoleExists = $null -ne (Get-AzRoleDefinition -Name "Azure Stack HCI TVM Attestation Role (Custom)") if ($tvmRoleAssignerRoleExists -and $tvmRoleExists) { $setTvmAttestationRoleResult = PerformObjectRoleAssignmentWithRetries -ObjectId $objectId -RoleName "Azure Stack HCI TVM Attestation Role (Custom)" -ResourceGroup $ResourceGroup -Verbose if ($setTvmAttestationRoleResult -ne [ErrorDetail]::Success) { Log-Info -Message "Failed to assign the Azure Stack HCI TVM Attestation Role (Custom) on the resource group" -ConsoleOut -Type Error } else { Log-Info -Message "Successfully assigned the Azure Stack HCI TVM Attestation Role (Custom) on the resource group" -ConsoleOut } } else { Log-Info -Message "TVM Attestation Role Assigner (Custom) or Azure Stack HCI TVM Attestation Role (Custom) role definition does not exist" -ConsoleOut } # End: Temp role assignment for TVM action plan to work $keyVaultSecretsUserRoleResult = PerformObjectRoleAssignmentWithRetries -ObjectId $objectId -RoleName "Key Vault Secrets User" -ResourceGroup $ResourceGroup -Verbose if ($keyVaultSecretsUserRoleResult -ne [ErrorDetail]::Success) { Log-Info -Message "Failed to assign the Key Vault Secrets User role on the resource group" -ConsoleOut -Type Error } else { Log-Info -Message "Successfully assigned the Key Vault Secrets User role on the resource group" -ConsoleOut } $connectedInfraVMsRoleResult = PerformObjectRoleAssignmentWithRetries -ObjectId $objectId -RoleName "Azure Stack HCI Connected InfraVMs" -ResourceGroup $ResourceGroup -Verbose if ($connectedInfraVMsRoleResult -ne [ErrorDetail]::Success) { Log-Info -Message "Failed to assign the Azure Stack HCI Connected InfraVMs role on the resource group" -ConsoleOut -Type Error } else { Log-Info -Message "Successfully assigned the Azure Stack HCI Connected InfraVMs role on the resource group" -ConsoleOut } } else{ Log-Info -Message "HCI Object Id is null, so could not assign the required permissions the HCI RP on the RG" -Type Error -ConsoleOut } } } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error throw $_ } } function GetArcMachineObjectId { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string] $ArcMachineUri ) try { Log-Info -Message "Arc Machine Uri $ArcMachineUri" -ConsoleOut $arcResource = Get-AzResource -ResourceId $ArcMachineUri $objectId = $arcResource.Identity.PrincipalId Log-Info -Message "Successfully got Object Id for Arc Installation $objectId" -ConsoleOut return $objectId } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error throw $_ } return $null } function ReplaceKeyVaultTemplateWithActualValues { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $KVName, [Parameter(Mandatory = $true)] [string] $Region, [Parameter(Mandatory = $true)] [string] $LocalAdminSecret, [Parameter(Mandatory = $true)] [string] $DomainAdminSecret, [Parameter(Mandatory = $true)] [string] $ArbDeploymentSpnSecret, [Parameter(Mandatory = $true)] [string] $StorageWitnessKey, [Parameter(Mandatory = $true)] [string] $ClusterName, [Parameter(Mandatory = $false)] [PSCustomObject[]] $SBESecrets ) try { Log-Info -Message "Starting to change the parameters of the key vault parameyters template" -ConsoleOut $keyVaultParameterFilePath = (Join-Path -Path $PSScriptRoot -ChildPath "Parameters\KeyVaultParameters.json") $keyVaultParameters = Get-Content $keyVaultParameterFilePath | ConvertFrom-Json Log-Info -Message "Successfully got the template file for the key vault parameters" -ConsoleOut $keyVaultParameters.parameters.keyVaultName.value = $KVName $keyVaultParameters.parameters.location.value = $Region $keyVaultParameters.parameters.localAdminSecretName.value = $ClusterName + "-LocalAdminCredential" $keyVaultParameters.parameters.localAdminSecretValue.value = $LocalAdminSecret $keyVaultParameters.parameters.domainAdminSecretName.value = $ClusterName + "-AzureStackLCMUserCredential" $keyVaultParameters.parameters.domainAdminSecretValue.value = $DomainAdminSecret $keyVaultParameters.parameters.arbDeploymentSpnName.value = $ClusterName + "-DefaultARBApplication" $keyVaultParameters.parameters.arbDeploymentSpnValue.value = $ArbDeploymentSpnSecret $keyVaultParameters.parameters.storageWitnessName.value = $ClusterName + "-WitnessStorageKey" $keyVaultParameters.parameters.storageWitnessValue.value = $StorageWitnessKey Log-Info -Message "Successfully updated the key vault parameters file with the actual values" -ConsoleOut return $keyVaultParameters } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error } return $null } function ReplaceKeyVaultTemplateWithActualValuesForClusterUpgrade { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $KVName, [Parameter(Mandatory = $true)] [string] $Region, [Parameter(Mandatory = $true)] [string] $DomainAdminSecret, [Parameter(Mandatory = $true)] [string] $ArbDeploymentSpnSecret, [Parameter(Mandatory = $true)] [string] $ClusterName, [Parameter(Mandatory = $false)] [PSCustomObject[]] $SBESecrets ) try { Log-Info -Message "Starting to change the parameters of the key vault parameyters template" -ConsoleOut $keyVaultParameterFilePath = (Join-Path -Path $PSScriptRoot -ChildPath "Parameters\KeyVaultParameters.json") $keyVaultParameters = Get-Content $keyVaultParameterFilePath | ConvertFrom-Json Log-Info -Message "Successfully got the template file for the key vault parameters" -ConsoleOut $keyVaultParameters.parameters.keyVaultName.value = $KVName $keyVaultParameters.parameters.location.value = $Region $keyVaultParameters.parameters.domainAdminSecretName.value = $ClusterName + "-AzureStackLCMUserCredential" $keyVaultParameters.parameters.domainAdminSecretValue.value = $DomainAdminSecret $keyVaultParameters.parameters.arbDeploymentSpnName.value = $ClusterName + "-DefaultARBApplication" $keyVaultParameters.parameters.arbDeploymentSpnValue.value = $ArbDeploymentSpnSecret Log-Info -Message "Successfully updated the key vault parameters file with the actual values" -ConsoleOut return $keyVaultParameters } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error } return $null } function CreateServicePrincipalForCloudDeployment { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $DisplayName, [Parameter(Mandatory = $true)] [string] $ResourceGroup ) try { $SPNAndAppArray = CreateAndCheckAadApp -DisplayName $DisplayName $servicePrincipal = $SPNAndAppArray[0] $AADApp = $SPNAndAppArray[1] if ($null -eq $AADApp) { Log-Info -Message "Could not create AAD app successfully for creating SPN, so returning null" -ConsoleOut return $null } Log-Info -Message "Service principal obtained is $servicePrincipal" -ConsoleOut Log-Info -Message "Created a spn with the appId $AADApp" -ConsoleOut $PasswordCedentials = @{ StartDateTime = Get-Date EndDateTime = (Get-Date).AddDays(7) DisplayName = ("Secret auto-rotated on: " + (Get-Date).ToUniversalTime().ToString("yyyy'-'MM'-'dd")) } $servicePrincipalSecret = New-AzADAppCredential -ApplicationObject $AADApp -PasswordCredentials $PasswordCedentials $servicePrincipalSecretTest = $servicePrincipalSecret.SecretText Log-Info -Message "Successfully created a service principal secret for the app $AADApp" -ConsoleOut $spnCredentialForArb = $servicePrincipal.AppId + ":" + $servicePrincipalSecretTest $base64EncodedSpnCredential = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($spnCredentialForArb)) Log-Info -Message "The base 64 encoded spn credential for deployment is created successfully" -ConsoleOut Log-Info -Message "Trying to assign permission to the SPN" -ConsoleOut AssignPermissionToSPN -spnObjectId $servicePrincipal.Id -ResourceGroup $ResourceGroup return $base64EncodedSpnCredential } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error } return $null } function CreateAndCheckAadApp { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $DisplayName ) $App = $null $servicePrincipal = $null try { $servicePrincipal = New-AzADServicePrincipal -DisplayName $DisplayName Log-Info -Message "Initialized creation of an aad app with display name $DisplayName" -ConsoleOut $stopLoop = $false $count = 0; do { $count++; Log-Info -Message "Current retry count is $count" -ConsoleOut try { $AADApp = Get-AzADApplication -ApplicationId $servicePrincipal.AppId if ($null -ne $AADApp) { Log-Info -Message "Successfully fetched an aad app using the created spn $($servicePrincipal.AppId)" -ConsoleOut $stopLoop = $true; $App = $AADApp; } else { Log-Info -Message "AAD app obtained is null, so retrying" -ConsoleOut } } catch { Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "Error while getting the aad app for $DisplayName so retrying" -ConsoleOut } finally { Start-Sleep -Seconds 30 if ($count -ge 30) { Log-Info -Message "AAD application could not be created within 15 mins, so stopping retrying" -ConsoleOut $stopLoop = $true; } } } while (-Not $stopLoop) } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error } Log-Info -Message "Returning AAD app $App" -ConsoleOut return @($servicePrincipal,$App); } function ReplaceStorageAccountTemplateWithActualValues { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $StorageAccountName, [Parameter(Mandatory = $true)] [string] $Location ) try { $storageAccountParameterFilePath = (Join-Path -Path $PSScriptRoot -ChildPath "Parameters\StorageAccountParameters.json") Log-Info -Message "Storage Account Parameters File Path $storageAccountParameterFilePath" -ConsoleOut $storageAccountParameters = Get-Content $storageAccountParameterFilePath | ConvertFrom-Json $storageAccountParameters.parameters.cloudDeployStorageAccountName.value = $StorageAccountName $storageAccountParameters.parameters.location.value = $Location Log-Info -Message "Successfully replaced the storage account name in the parameters file" -ConsoleOut return $storageAccountParameters } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error } return $null } function CreateSBECredentialsList{ [CmdletBinding()] param ( [System.Collections.Hashtable] $SBESecrets, [string] $kvVaultUri ) $objectsArray = @() if( $null -ne $SBESecrets) { foreach ($key in $SBESecrets.Keys) { $object = [PSCustomObject]@{ Key = $key Value = $SBESecrets[$key] } $object = [PSCustomObject]@{ secretName = $key eceSecretName = $key secretLocation= $kvVaultUri+"secrets/"+$key } Log-Info -Message "Added SBE secret Name $username and the secret Value to deploymentsettings $($object.secretName), $($object.eceSecretName), $($object.secretLocation) " -ConsoleOut $objectsArray += $object } } return $objectsArray } function ExtractSBECredentialsToKeyVaulepairs { [CmdletBinding()] param ( [System.Collections.Hashtable] $Credentials ) if( $null -ne $Credentials) { $objectsArray = @() foreach ($key in $Credentials.Keys) { $object = [PSCustomObject]@{ Key = $key Value = ExtractUsernameAndPasswordFromCredential -Credential $Credentials[$key] } Log-Info -Message "Added SBE secret Name $($key) and the secret Value to hashtable" -ConsoleOut $objectsArray += $object } } return $objectsArray } function ExtractUsernameAndPasswordFromCredential { [CmdletBinding()] param ( [System.Management.Automation.PSCredential] $Credential ) try { $secretName = $Credential.GetNetworkCredential().UserName $secretValue = $Credential.GetNetworkCredential().Password Log-Info -Message "Successfully extracted the secret Name $secretName and the secret Value from the Credential Object" -ConsoleOut $KVSecret = $secretName + ":" + $secretValue $base64EncodedKVSecret = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($KVSecret)) Log-Info -Message "Successfully base 64 encoded the secret $secretName " return $base64EncodedKVSecret } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error } return $null } function AssignPermissionToSPN { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $ResourceGroup, [Parameter(Mandatory = $true)] [string] $spnObjectId ) try { $rolesToAssign = @("Azure Resource Bridge Deployment Role", "Azure Connected Machine Resource Administrator") if ($null -ne $spnObjectId) { foreach ($role in $rolesToAssign) { # Assign Azure Connected Machine Resource Administrator role at Resource Group level if ($role -eq "Azure Connected Machine Resource Administrator") { $roleStatus = PerformObjectRoleAssignmentWithRetries -ObjectId $spnObjectId -RoleName $role -ResourceGroup $ResourceGroup } else { $roleStatus = PerformObjectRoleAssignmentWithRetries -ObjectId $spnObjectId -RoleName $role } if ($roleStatus -ne [ErrorDetail]::Success) { Log-Info -Message "Failed to assign the role $role" -ConsoleOut -Type Error } else { Log-Info -Message "Successfully assigned the role $role" -ConsoleOut } } } } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error } } function AssignRolesToHCIResourceProvider { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $ResourceGroup, [Parameter(Mandatory = $true)] [string] $hciObjectId ) try { if ($null -ne $hciObjectId) { $arcManagerRoleStatus = PerformObjectRoleAssignmentWithRetries -ObjectId $hciObjectId -RoleName "Azure Connected Machine Resource Manager" -ResourceGroup $ResourceGroup if ($arcManagerRoleStatus -ne [ErrorDetail]::Success) { Log-Info -Message "Failed to assign the Azure Connected Machine Resource Nanager role on the resource group" -ConsoleOut -Type Error } else { Log-Info -Message "Successfully assigned the Azure Connected Machine Resource Nanager role on the resource group" -ConsoleOut } } } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error } } function PerformObjectRoleAssignmentWithRetries { param( [String] $ObjectId, [String] $ResourceGroup, [string] $RoleName ) $stopLoop = $false [int]$retryCount = "0" [int]$maxRetryCount = "5" Log-Info -Message $"Checking if $RoleName is assigned already for SPN with Object ID: $ObjectId" -ConsoleOut if( [string]::IsNullOrEmpty($ResourceGroup)) { $arcSPNRbacRoles = Get-AzRoleAssignment -ObjectId $ObjectId } else { $arcSPNRbacRoles = Get-AzRoleAssignment -ObjectId $ObjectId -ResourceGroupName $ResourceGroup } $alreadyFoundRole = $false $arcSPNRbacRoles | ForEach-Object { $roleFound = $_.RoleDefinitionName if ($roleFound -eq $RoleName) { $alreadyFoundRole = $true Log-Info -Message $"Already Found $RoleName Not Assigning" -ConsoleOut } } if ( -not $alreadyFoundRole) { Log-Info -Message "Assigning $RoleName to Object : $ObjectId" -ConsoleOut do { try { if( [string]::IsNullOrEmpty($ResourceGroup)) { New-AzRoleAssignment -ObjectId $ObjectId -RoleDefinitionName $RoleName | Out-Null } else { New-AzRoleAssignment -ObjectId $ObjectId -ResourceGroupName $ResourceGroup -RoleDefinitionName $RoleName | Out-Null } Log-Info -Message $"Sucessfully assigned $RoleName to Object Id $ObjectId" -ConsoleOut $stopLoop = $true } catch { # 'Conflict' can happen when either the RoleAssignment already exists or the limit for number of role assignments has been reached. if ($_.Exception.Response.StatusCode -eq 'Conflict') { if( [string]::IsNullOrEmpty($ResourceGroup)) { $roleAssignment = Get-AzRoleAssignment -ObjectId $ObjectId -RoleDefinitionName $RoleName } else { $roleAssignment = Get-AzRoleAssignment -ObjectId $ObjectId -ResourceGroupName $ResourceGroup -RoleDefinitionName $RoleName } if ($null -ne $roleAssignment) { Log-Info -Message $"Sucessfully assigned $RoleName to Object Id $ObjectId" -ConsoleOut return [ErrorDetail]::Success } Log-Info -Message $"Failed to assign roles to service principal with object Id $($ObjectId). ErrorMessage: " + $_.Exception.Message + " PositionalMessage: " + $_.InvocationInfo.PositionMessage -ConsoleOut -Type Error return [ErrorDetail]::PermissionsMissing } if ($retryCount -ge $maxRetryCount) { # Timed out. Log-Info -Message $"Failed to assign roles to service principal with object Id $($ObjectId). ErrorMessage: " + $_.Exception.Message + " PositionalMessage: " + $_.InvocationInfo.PositionMessage -ConsoleOut -Type Error return [ErrorDetail]::PermissionsMissing } Log-Info -Message $"Could not assign roles to service principal with Object Id $($ObjectId). Retrying in 10 seconds..." -ConsoleOut Start-Sleep -Seconds 10 $retryCount = $retryCount + 1 } } While (-Not $stopLoop) } return [ErrorDetail]::Success } function CreateResourceGroupIfNotExists { param ( [Parameter(Mandatory = $true)] [string] $ResourceGroupName, [Parameter(Mandatory = $true)] [string] $Region ) try { # Check if the resource group exists $existingResourceGroup = Get-AzResourceGroup -Name $ResourceGroupName -ErrorAction SilentlyContinue if (([string]::IsNullOrEmpty($existingResourceGroup)) -or ([string]::IsNullOrEmpty($existingResourceGroup.ResourceGroupName))) { # Resource group doesn't exist, create it Log-Info -Message "$ResourceGroupName does not exist, creating it" -ConsoleOut New-AzResourceGroup -Name $ResourceGroupName -Location $Region -Force | Out-Null Log-info -Message "Created the resource group $ResourceGroupName" -ConsoleOut } else { # Resource group already exists Log-Info -Message "The resource group '$ResourceGroupName' already exists." -ConsoleOut } } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error } } function CheckIfScriptIsRunByAdministrator { try { $user = [System.Security.Principal.WindowsIdentity]::GetCurrent() # Get the Windows Principal for the current user $principal = New-Object System.Security.Principal.WindowsPrincipal($user) # Check if the user is in the Administrator role $is_admin = $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) if ($is_admin) { Log-Info -Message "User has administrator access" -ConsoleOut return $is_admin } Log-Info -Message "User is not running the script in administrator mode" -ConsoleOut return $is_admin } catch { Log-Info -Message "" -ConsoleOut Log-Info -Message "$($_.Exception.Message)" -ConsoleOut -Type Error Log-Info -Message "$($_.ScriptStackTrace)" -ConsoleOut -Type Error } } function New-ClusterWithRetries { param( [String] $ResourceIdWithAPI, [String] $Payload ) $stopLoop = $false [int]$retryCount = "0" [int]$maxRetryCount = "10" do { $response = Invoke-AzRestMethod -Path $ResourceIdWithAPI -Method PUT -Payload $Payload if (($response.StatusCode -ge 200) -and ($response.StatusCode -lt 300)) { $stopLoop = $true return $true } if ($retryCount -ge $maxRetryCount) { # Timed out. Log-Info -Message "Failed to create ARM resource representing the cluster. StatusCode: {0}, ErrorCode: {1}, Details: {2}" -f $response.StatusCode, $response.ErrorCode, $response.Content -Type Error -ConsoleOut return $false } Log-Info -Message "Failed to create ARM resource representing the cluster. Retrying in 10 seconds..." -Type Error -ConsoleOut Start-Sleep -Seconds 10 $retryCount = $retryCount + 1 } While (-Not $stopLoop) return $true } class Identity { [string] $type = "SystemAssigned" } class ResourceProperties { [string] $location [object] $properties [Identity] $identity = [Identity]::new() ResourceProperties ( [string] $location, [object] $properties ) { $this.location = $location $this.properties = $properties } } enum ErrorDetail { Unused; PermissionsMissing; Success; NodeAlreadyArcEnabled; NotFound; ClusterAlreadyExists; ConnectedRecently; DeploymentSuccess; StorageAccountAlreadyExists; KeyVaultAlreadyExists; EnvironmentValidationFailed } Export-ModuleMember -Function Invoke-AzStackHCIDeployment Export-ModuleMember -Function Invoke-AzStackHCIEnvironmentValidator Export-ModuleMember -Function Invoke-AzStackHCIEnvironmentPreparator Export-ModuleMember -Function Invoke-AzStackHCIUpgrade Export-ModuleMember -Function Invoke-AzStackHCIEnvironmentValidatorForClusterUpgrade Export-ModuleMember -Function Invoke-AzStackHCIEnvironmentPreparatorForClusterUpgrade Export-ModuleMember -Function Invoke-AzStackHCIFullDeployment Export-ModuleMember -Function PollDeploymentSettingsStatus Export-ModuleMember -Function Invoke-validateNodesForDeployment Export-ModuleMember -Function New-EdgeDevices # SIG # Begin signature block # MIIoLQYJKoZIhvcNAQcCoIIoHjCCKBoCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBlN63LGXj4icgy # aWK+Ix0TqTQ4kGlzKvOiujQtAQtanqCCDXYwggX0MIID3KADAgECAhMzAAADrzBA # DkyjTQVBAAAAAAOvMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjMxMTE2MTkwOTAwWhcNMjQxMTE0MTkwOTAwWjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQDOS8s1ra6f0YGtg0OhEaQa/t3Q+q1MEHhWJhqQVuO5amYXQpy8MDPNoJYk+FWA # hePP5LxwcSge5aen+f5Q6WNPd6EDxGzotvVpNi5ve0H97S3F7C/axDfKxyNh21MG # 0W8Sb0vxi/vorcLHOL9i+t2D6yvvDzLlEefUCbQV/zGCBjXGlYJcUj6RAzXyeNAN # xSpKXAGd7Fh+ocGHPPphcD9LQTOJgG7Y7aYztHqBLJiQQ4eAgZNU4ac6+8LnEGAL # go1ydC5BJEuJQjYKbNTy959HrKSu7LO3Ws0w8jw6pYdC1IMpdTkk2puTgY2PDNzB # tLM4evG7FYer3WX+8t1UMYNTAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQURxxxNPIEPGSO8kqz+bgCAQWGXsEw # RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW # MBQGA1UEBRMNMjMwMDEyKzUwMTgyNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci # tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG # CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 # MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAISxFt/zR2frTFPB45Yd # mhZpB2nNJoOoi+qlgcTlnO4QwlYN1w/vYwbDy/oFJolD5r6FMJd0RGcgEM8q9TgQ # 2OC7gQEmhweVJ7yuKJlQBH7P7Pg5RiqgV3cSonJ+OM4kFHbP3gPLiyzssSQdRuPY # 1mIWoGg9i7Y4ZC8ST7WhpSyc0pns2XsUe1XsIjaUcGu7zd7gg97eCUiLRdVklPmp # XobH9CEAWakRUGNICYN2AgjhRTC4j3KJfqMkU04R6Toyh4/Toswm1uoDcGr5laYn # TfcX3u5WnJqJLhuPe8Uj9kGAOcyo0O1mNwDa+LhFEzB6CB32+wfJMumfr6degvLT # e8x55urQLeTjimBQgS49BSUkhFN7ois3cZyNpnrMca5AZaC7pLI72vuqSsSlLalG # OcZmPHZGYJqZ0BacN274OZ80Q8B11iNokns9Od348bMb5Z4fihxaBWebl8kWEi2O # PvQImOAeq3nt7UWJBzJYLAGEpfasaA3ZQgIcEXdD+uwo6ymMzDY6UamFOfYqYWXk # ntxDGu7ngD2ugKUuccYKJJRiiz+LAUcj90BVcSHRLQop9N8zoALr/1sJuwPrVAtx # HNEgSW+AKBqIxYWM4Ev32l6agSUAezLMbq5f3d8x9qzT031jMDT+sUAoCw0M5wVt # CUQcqINPuYjbS1WgJyZIiEkBMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq # hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 # IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG # EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG # A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg # Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC # CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03 # a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr # rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg # OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy # 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9 # sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh # dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k # A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB # w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn # Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90 # lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w # ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o # ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD # VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa # BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny # bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG # AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t # L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV # HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG # AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl # AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb # C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l # hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6 # I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0 # wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560 # STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam # ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa # J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah # XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA # 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt # Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr # /Xmfwb1tbWrJUnMTDXpQzTGCGg0wghoJAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp # Z25pbmcgUENBIDIwMTECEzMAAAOvMEAOTKNNBUEAAAAAA68wDQYJYIZIAWUDBAIB # BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO # MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEICbcsw5ZY9Nv4tDOyn6vU0pQ # K+1/+XJqpXvK/rifFwX0MEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A # cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB # BQAEggEAYieYVztDXr/vvaCWKzp9xk8IUPLcyXacg/C3U1oZusZCMgXLUs913cpC # edc1vRSwH2T8eN6OGO/opGzm1kWled84PcIxpsSsavas+7bB4oD4CrSVzd75qGXv # DWVoTp5rKT/zkT4wolF0rAIcLm1VPGQ+JM4rW4bw7gyS7niyiXY2FQbsPpJnfyah # fzMAEx3D+AUKBWIHaPyTABUKt/ngA045Y3P6qTEot2yjmXs8pBCUBhtgcOy8Eji3 # YvFghhlYaoW5L0DeViKkwzaXA6Eghq9p9fEvmcanW2vnMQEtvlQ6Un/rhi/ZGyg5 # O7AcvReaVXTJWi94WgSACzD4Ih6snKGCF5cwgheTBgorBgEEAYI3AwMBMYIXgzCC # F38GCSqGSIb3DQEHAqCCF3AwghdsAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq # hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl # AwQCAQUABCCjssv703iCkL4A68V6cWa3Zr4LmSeiFUzMC+SW+d7d/AIGZwfNCe3q # GBMyMDI0MTAxNDIyNDQ1OC40NTdaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l # cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046QTAwMC0w # NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg # ghHtMIIHIDCCBQigAwIBAgITMwAAAevgGGy1tu847QABAAAB6zANBgkqhkiG9w0B # AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD # VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMzEyMDYxODQ1 # MzRaFw0yNTAzMDUxODQ1MzRaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz # aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv # cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z # MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046QTAwMC0wNUUwLUQ5NDcxJTAjBgNV # BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQDBFWgh2lbgV3eJp01oqiaFBuYbNc7hSKmktvJ15NrB # /DBboUow8WPOTPxbn7gcmIOGmwJkd+TyFx7KOnzrxnoB3huvv91fZuUugIsKTnAv # g2BU/nfN7Zzn9Kk1mpuJ27S6xUDH4odFiX51ICcKl6EG4cxKgcDAinihT8xroJWV # ATL7p8bbfnwsc1pihZmcvIuYGnb1TY9tnpdChWr9EARuCo3TiRGjM2Lp4piT2lD5 # hnd3VaGTepNqyakpkCGV0+cK8Vu/HkIZdvy+z5EL3ojTdFLL5vJ9IAogWf3XAu3d # 7SpFaaoeix0e1q55AD94ZwDP+izqLadsBR3tzjq2RfrCNL+Tmi/jalRto/J6bh4f # PhHETnDC78T1yfXUQdGtmJ/utI/ANxi7HV8gAPzid9TYjMPbYqG8y5xz+gI/SFyj # +aKtHHWmKzEXPttXzAcexJ1EH7wbuiVk3sErPK9MLg1Xb6hM5HIWA0jEAZhKEyd5 # hH2XMibzakbp2s2EJQWasQc4DMaF1EsQ1CzgClDYIYG6rUhudfI7k8L9KKCEufRb # K5ldRYNAqddr/ySJfuZv3PS3+vtD6X6q1H4UOmjDKdjoW3qs7JRMZmH9fkFkMzb6 # YSzr6eX1LoYm3PrO1Jea43SYzlB3Tz84OvuVSV7NcidVtNqiZeWWpVjfavR+Jj/J # OQIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFHSeBazWVcxu4qT9O5jT2B+qAerhMB8G # A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG # Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy # MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w # XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy # dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG # A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD # AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQCDdN8voPd8C+VWZP3+W87c/QbdbWK0sOt9 # Z4kEOWng7Kmh+WD2LnPJTJKIEaxniOct9wMgJ8yQywR8WHgDOvbwqdqsLUaM4Nre # rtI6FI9rhjheaKxNNnBZzHZLDwlkL9vCEDe9Rc0dGSVd5Bg3CWknV3uvVau14F55 # ESTWIBNaQS9Cpo2Opz3cRgAYVfaLFGbArNcRvSWvSUbeI2IDqRxC4xBbRiNQ+1qH # XDCPn0hGsXfL+ynDZncCfszNrlgZT24XghvTzYMHcXioLVYo/2Hkyow6dI7uULJb # KxLX8wHhsiwriXIDCnjLVsG0E5bR82QgcseEhxbU2d1RVHcQtkUE7W9zxZqZ6/jP # maojZgXQO33XjxOHYYVa/BXcIuu8SMzPjjAAbujwTawpazLBv997LRB0ZObNckJY # yQQpETSflN36jW+z7R/nGyJqRZ3HtZ1lXW1f6zECAeP+9dy6nmcCrVcOqbQHX7Zr # 8WPcghHJAADlm5ExPh5xi1tNRk+i6F2a9SpTeQnZXP50w+JoTxISQq7vBij2nitA # sSLaVeMqoPi+NXlTUNZ2NdtbFr6Iir9ZK9ufaz3FxfvDZo365vLOozmQOe/Z+pu4 # vY5zPmtNiVIcQnFy7JZOiZVDI5bIdwQRai2quHKJ6ltUdsi3HjNnieuE72fT4eWh # xtmnN5HYCDCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI # hvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw # DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x # MjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAy # MDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp # bWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC # AQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25Phdg # M/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPF # dvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6 # GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBp # Dco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50Zu # yjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3E # XzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0 # lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1q # GFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ # +QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PA # PBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkw # EgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxG # NSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARV # MFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWlj # cm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAK # BggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC # AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX # zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v # cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI # KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG # 9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0x # M7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmC # VgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449 # xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wM # nosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDS # PeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2d # Y3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxn # GSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+Crvs # QWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokL # jzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL # 6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNQ # MIICOAIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp # bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw # b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn # MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOkEwMDAtMDVFMC1EOTQ3MSUwIwYDVQQD # ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQCA # Bol1u1wwwYgUtUowMnqYvbul3qCBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w # IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6reRLjAiGA8yMDI0MTAxNDEyNDYz # OFoYDzIwMjQxMDE1MTI0NjM4WjB3MD0GCisGAQQBhFkKBAExLzAtMAoCBQDqt5Eu # AgEAMAoCAQACAgFlAgH/MAcCAQACAhQAMAoCBQDquOKuAgEAMDYGCisGAQQBhFkK # BAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJ # KoZIhvcNAQELBQADggEBAHfJUAzpGvMxGVLd7waL4dPsjGxsAdCod7Mt6z1cjviA # OGHCXiAFKfThM5UlQN+cI0PgbgOfMfc8A/Yt6SgTzl3gBRzUvc8wVX2P8HF0YCC+ # Vlr55ffbsVtf2ji8rP8i/+GiXLvkGmz3z3hmxDVjG5WUvPzBDsn7ZvV+JcPKxQcM # iSEHnxDjGB7lVr/KL6Fub6YWbXMeSQnhlAJaj9W0HQIizSv9Odi5PtkWEvsorW55 # /GBfUy5qDL2ditHkquZPL179ZgEbJqugTV/+kLTxR/D/jVABjFB08ZWXgze4bhFj # +pyeZp07dbTss5SESYBmGD2lmSSJkFmA6FIQfbvYzuoxggQNMIIECQIBATCBkzB8 # MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk # bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1N # aWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAevgGGy1tu847QABAAAB # 6zANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEE # MC8GCSqGSIb3DQEJBDEiBCAN1EP4Fuz+u+6pA7s1bhfSa77my7p9VxUQXMrcylwG # yjCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIM63a75faQPhf8SBDTtk2DSU # gIbdizXsz76h1JdhLCz4MIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT # Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m # dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB # IDIwMTACEzMAAAHr4BhstbbvOO0AAQAAAeswIgQgpiEg8jtpqYEJSgoKzA9uDgVj # 8HVUpZltvzp9LxbHoAgwDQYJKoZIhvcNAQELBQAEggIAOWyKxk08g4jlGZdKDBQq # llfUfxukHK/btFwmDU5E+p35R8u6BC97pWRrxHy4S43lDMxDJJGcWqwwTscVcQ1C # 8fbKj9SfZerBQYsilDO9zTFEtOA63DkhfUNl4P2OwsRgPwmAyIfT9GhGSJE0TeY/ # 6fnLJQKAoYNVpDhPDvVvLIr7OlavsLgk5CCk9ZYQopulZPhEAZE4/FK1LEYBSc3K # JiVPZznDEHYpoNcFscJtPQMMAtQ+RINEMLOA9mogyNBdls+fiAW7hiJazWg1wnyI # e6Hu6fiZoJsG0S0dvskc3EDp7myzonQllibTsFx/OrCvFJYIm5i2n9/YTHQDoHVi # 5PZxZcJVws7OtlmV1tfrcG77wnD/EHE91kKfnlm3Ns7XzFnzZLefQeLFcP5ntdBk # VuZWAoXl6msuGnuhOwsvn2q1YYUS3j1q/atPm6rQ/abKqWiH3RgU3UEUfgCSHaBg # o2f7K00It+35sqJjwHR8tRedv1r4e0FghJZCc8l29bHtROr4jZvGUVHQbY9YY+XP # DS9iTbLUyVgII9Xcbf7k+WHYYajusrwtQxSAA0HNdBuUp/f3IUJnYSN0IZA5w40d # d+1SX9Qlc5GiO4Wzhph40Jo1saSFG4uJugR3brupMBa+aYZd1dLfZIOyPoJT9MV7 # 8NZxRQya3AAY7kjY+4NmNqk= # SIG # End signature block |