# AWS Server Migration Service Deployment Script for Azure Connector. # Copyright 2019, Amazon Web Services, Inc. or its affiliates. All rights reserved. <# .SYNOPSIS This powershell script will deploy AWS SMS Connector VM for Azure. .DESCRIPTION This powershell script will deploy AWS SMS Connector VM for Azure. It takes two required parameters and two optional parameters. - StorageAccountName - Required. This is the name of the (existing) storage account where you want the connector to be deployed. Read notes section before you choose one. - ExistingVNetName - Required. Existing Virtual Network name where you want the Connector VM to be deployed. - SubscriptionId - Optional. Id of the specific subscription to use. If not specified, the default Subscription for the account is used. - SubnetName - Optional. Existing Subnet name in the provided Virtual Network above. If not specified, Subnet named "default" is used. .EXAMPLE ./aws-sms-azure-setup.ps1 -StorageAccountName -ExistingVNetName -SubscriptionId -SubnetName .LINK https://docs.aws.amazon.com/server-migration-service/latest/userguide/Azure.html .NOTES The script uses Azure's new "Az" commandlets. This requires Powershell version >= 5.1. Az commandlets will be installed if necessary. The Storage account that you choose is used to store the Connector related artifacts (Disk Image, etc..) and some transient SMS related artifacts during the migration. It is recommended that you create a separate storage account so that all the connector resources are isolated and easily managed. Whether you are creating a new one or using an existing storage account, make sure you choose the one that is located (primary location) in the same region as all the VMs that you want to migrate. This is because a single Connector can only migrate VMs from a single subscription and a location pair. Only Azure Resource Manager (ARM) deployed VMs can be migrated by the Connector. If the Storage account is not under the default subscription, please specify the SubscriptionId under which the Storage account exists. All Connector related resources will be created under this subscription. The deployed Connector will only have inbound access from within its own Virtual Network (ARM deployed default VM configuration). The Connector VM will be setup with a NIC/NSG with default security rules which will let the Connector work out of the box. However, if the chosen subnet has a network security group attached, then this can impact/override the default security rules. You will need to ensure that the Connector VM has inbound HTTPS (443) access from a VM from within its subnet to complete the registration. You also need to ensure that the Connector VM has outbound internet connectivity to be able to reach AWS and Azure services. You can further configure network firewall settings from Azure Portal by clicking on the Network Settings section of the Connector VM once it is deployed. If you run into any issue, please contact aws-server-migration-azure-support@amazon.com #> #################### SCRIPT PARAMETERS ######################## param( [parameter(Mandatory=$true, HelpMessage="This is the name of the existing storage account where you want the connector to be deployed.")] [ValidateNotNullOrEmpty()] [String] $StorageAccountName, [parameter(Mandatory=$true, HelpMessage="Existing Virtual Network name where you want the Connector VM to be deployed.")] [ValidateNotNullOrEmpty()] [String] $ExistingVNetName, [parameter(Mandatory=$true, HelpMessage="Id of the specific subscription to use. If not specified, the default Subscription for the account is used.")] [AllowEmptyString()] [String] $SubscriptionId, [parameter(Mandatory=$true, HelpMessage="Existing Subnet name in the provided Virtual Network above. If not specified, Subnet named 'default' is used.")] [AllowEmptyString()] [String] $SubnetName ) #################### SCRIPT GLOBALS ######################## $Global:AWS_AZURE_SMS_CONFIG_SCRIPT_VERSION = "1.2.0.3" $Global:AWS_AZURE_SMS_CONNECTOR_DEFAULT_VERSION = "1.2.0.1" $Global:POWERSHELL_MIN_VERSION = "5.1" $Global:AZ_MODULE_MIN_VERSION = "1.1.0" $Global:ErrorActionPreference = "Stop" $Global:WarningPreference = "Continue" $Global:DEPLOYMENT_STAGE_INITIALIZE = "Initialize" $Global:DEPLOYMENT_STAGE_RG_CREATE = "ResourceGroup_Create" $Global:DEPLOYMENT_STAGE_BLOB_COPY = "Blob_Copy" $Global:DEPLOYMENT_STAGE_BLOB_VERIFY = "Blob_Verify" $Global:DEPLOYMENT_STAGE_DISK_CREATE = "Disk_Create" $Global:DEPLOYMENT_STAGE_VM_CREATE = "VM_Create" $Global:DEPLOYMENT_STAGE_POST_CONFIG = "Post_Config" $Global:DEPLOYMENT_STAGE_COMPLETE = "Complete" $Global:DEPLOYMENT_BLOB_NAME = "sms-deployment-tracker-donot-delete" $Global:TRANSCRIPT_FILE_NAME = "sms-deployment-transcript.txt" #################### DEPLOYMENT LOGIC CORE FUNCTIONS ######################## # Function to display help text about script parameters before proceeding # It also retrieves and caches the necessary data to execute this script Function Initialize { Write-Status "Performing Validation and Initialization" Try { # Check if Storage Account exists/accessible Write-Status "Checking if Storage Account exists and is accessible by the logged in user" $SAObj = (Get-AzStorageAccount | Where-Object {$_.StorageAccountName -eq "$StorageAccountName"}) | GetNonBlankValue -Field "StorageAccount" $LocationName = $SAObj.PrimaryLocation if ($LocationName -eq "") { $LocationName = $SAObj.Location } $RgName = $SAObj.ResourceGroupName $StorageContext = Get-Storage-Context -SAName $StorageAccountName -RgName $RgName # Check if Storage Account is Standard and General Purpose if (-Not $SAObj.Kind.toString().StartsWith("Storage")) { Write-Failure "The Storage Account needs to be of type Standard General Purpose." } if (-Not $SaObj.Sku.Tier.ToString().StartsWith("Standard")) { Write-Failure "The Storage Account needs to be of type Standard General Purpose." } # Get Subscription Details Write-Status "Getting subscription details" $RgObj = (Get-AzResourceGroup -Name $RgName -Location $LocationName) | GetNonBlankValue -Field "ResourceGroup" $SubId = $RgObj.ResourceId.Split("/")[2] $SubObj = Select-AzSubscription -SubscriptionId $SubId # Check if VNet exists Write-Status "Checking if $ExistingVNetName exists" $VnetObj = (Get-AzVirtualNetwork | Where-Object {$_.Name -eq "$ExistingVNetName"}) | GetNonBlankValue -Field "VirtualNetwork" if ($VnetObj.count -gt 1) { Write-Warning "There are multiple Virtual Networks found under the same name $ExistingVNetName. Here is the list." Write-Info ($VnetObj | Select-Object Name, ResourceGroupName) $VnetRgName = Read-Host "Enter the resource group name corresponding to the Virtual Network you would like to use: " $VnetObj = (Get-AzVirtualNetwork -Name $ExistingVNetName -ResourceGroupName $VnetRgName) | GetNonBlankValue -Field "VirtualNetwork" } else { $VnetRgName = $VnetObj.ResourceGroupName } # Check VNet location if ($VnetObj.Location -ne $LocationName) { Write-Failure "The Virtual Network Location does not match. It needs to be in $LocationName." } # Check if Subnet exists if (($SubnetName -eq $null) -Or ($SubnetName -eq "")) { $SubnetName = "default" } $SubnetObj = (Get-AzVirtualNetworkSubnetConfig -Name $SubnetName -VirtualNetwork $VnetObj) | GetNonBlankValue -Field "Subnet" # Collect all resource names into DeploymentTracker Write-Status "Computing resource names" $RgIdx = "" $FoundNewName = $false Do { # Resource Group is special and needs to handle indexing # for deploying multiple connectors in the same location $RGroupName = Derive-Resource-Name -ResourceType "rg" -Location $LocationName -Index $RgIdx $ExistingRgObj = Get-AzResourceGroup -Name $RGroupName -ErrorAction Ignore if ($ExistingRgObj -eq $null) { $FoundNewName = $true } else { if ($RgIdx -eq "") { $RgIdx = 1 } else { $RgIdx++ } } } While ($FoundNewName -eq $false) # Get Connector Version Try { $TempFile = New-TemporaryFile $VersionBlobUri = Get-Location-Based-Version-BlobUri Invoke-WebRequest -Uri $VersionBlobUri -OutFile $TempFile.FullName $ConnectorVersion = Get-Content -Path $TempFile.FullName Write-Success "Obtained Connector Version: $ConnectorVersion" $Removed = Remove-Item -Path $TempFile.FullName -ErrorAction Ignore -Force } Catch { $ConnectorVersion = $Global:AWS_AZURE_SMS_CONNECTOR_DEFAULT_VERSION Write-Warning "Could not read Connector Version. Will name blob with Default Version: $ConnectorVersion" } $VMName = Derive-Resource-Name -ResourceType "vm" -Location $LocationName $DiskName = Derive-Resource-Name -ResourceType "disk" -Location $LocationName $BlobName = Derive-Resource-Name -ResourceType "blob" -Index $ConnectorVersion -Location $LocationName $ContainerName= Derive-Resource-Name -ResourceType "container" -Location $LocationName $NSGName = Derive-Resource-Name -ResourceType "nsg" -Location $LocationName $NICName = Derive-Resource-Name -ResourceType "nic" -Location $LocationName $PIPName = Derive-Resource-Name -ResourceType "pip" -Location $LocationName $RoleName = Derive-Resource-Name -ResourceType "role" -Location $SubId $RoleSAName = Derive-Resource-Name -ResourceType "role" -Location $StorageAccountName $VMTag = Derive-Resource-Name -ResourceType "tag" -Location $LocationName $VMSize = Get-Location-Based-VM-Size $LocationName $DeploymentTracker = New-Object -TypeName PSObject Add-Member -InputObject $DeploymentTracker -MemberType NoteProperty -Name "SubscriptionId" -Value $SubId Add-Member -InputObject $DeploymentTracker -MemberType NoteProperty -Name "ResourceGroupName" -Value $RGroupName Add-Member -InputObject $DeploymentTracker -MemberType NoteProperty -Name "StorageAccountName" -Value $StorageAccountName Add-Member -InputObject $DeploymentTracker -MemberType NoteProperty -Name "StorageAccountResourceGroupName" -Value $RgName Add-Member -InputObject $DeploymentTracker -MemberType NoteProperty -Name "LocationName" -Value $LocationName Add-Member -InputObject $DeploymentTracker -MemberType NoteProperty -Name "ContainerName" -Value $ContainerName Add-Member -InputObject $DeploymentTracker -MemberType NoteProperty -Name "BlobName" -Value $BlobName Add-Member -InputObject $DeploymentTracker -MemberType NoteProperty -Name "DiskName" -Value $DiskName Add-Member -InputObject $DeploymentTracker -MemberType NoteProperty -Name "RoleName" -Value $RoleName Add-Member -InputObject $DeploymentTracker -MemberType NoteProperty -Name "RoleSAName" -Value $RoleSAName Add-Member -InputObject $DeploymentTracker -MemberType NoteProperty -Name "ExistingVirtualNetworkName" -Value $ExistingVNetName Add-Member -InputObject $DeploymentTracker -MemberType NoteProperty -Name "VirtualNetworkResourceGroupName" -Value $VnetRgName Add-Member -InputObject $DeploymentTracker -MemberType NoteProperty -Name "SubnetName" -Value $SubnetName Add-Member -InputObject $DeploymentTracker -MemberType NoteProperty -Name "NICName" -Value $NICName Add-Member -InputObject $DeploymentTracker -MemberType NoteProperty -Name "NSGName" -Value $NSGName Add-Member -InputObject $DeploymentTracker -MemberType NoteProperty -Name "PIPName" -Value $PIPName Add-Member -InputObject $DeploymentTracker -MemberType NoteProperty -Name "VirtualMachineName" -Value $VMName Add-Member -InputObject $DeploymentTracker -MemberType NoteProperty -Name "VirtualMachineSize" -Value $VMSize Add-Member -InputObject $DeploymentTracker -MemberType NoteProperty -Name "VirtualMachineTagKey" -Value $VMTag Add-Member -InputObject $DeploymentTracker -MemberType NoteProperty -Name "Stage" -Value $Global:DEPLOYMENT_STAGE_INITIALIZE # Display Input summary. Get User Consent Write-Text "" Write-Warning "Please read the following carefully before proceeding" Write-Info "Storage Account ""$StorageAccountName"" resides in Azure Location ""$LocationName""." Write-Info "The Connector and its storage resources will be deployed into this account." Write-Info "This means the Connector being deployed can only migrate VMs in ""$LocationName"" and under the subscription ""$SubId""." Write-Info "The Connector VM access is restricted and its NIC is configured with the a Security Group with default rules." Write-Info "Connector VM will be configured to have Inbound SSH/HTTPS access only from within its Virtual Network ($ExistingVNetName)." Write-Info "Connector will have Internet Outbound access enabled in order to access AWS services." Write-Info "If the Subnet's Network Security Group overrides the default security rules, then rule changes may be required to satisfy the above." Write-Info "Following the deployment, you need to register the connector." Write-Info "In order to register, please use a machine from ""$ExistingVNetName"" to access https:// from its browser." Write-Text "" Write-Info "The following resources will be created under the subscription: $SubId" Write-Text ($DeploymentTracker | Format-List | Out-String) $Conf = Read-Host "Would you like to proceed with the deployment (y/n)? " if ($Conf -ne 'y') { Exit-Application } Write-Success "Validation and Initialization Complete" Transition-To-Next-Deployment-Stage $DeploymentTracker } Catch { Write-Failure -Message "Error initializing" -Exception $_.Exception } } # Function to create a new resource group where all connector related # resources will be grouped under. Function ResourceGroup-Create { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [PSObject]$DeploymentTracker ) Write-Status "Creating Resource Group" Try { $RgObj = Get-AzResourceGroup -Name $DeploymentTracker.ResourceGroupName -ErrorAction Ignore if ($RgObj -ne $nusll) { Write-Success "Resource Group $($DeploymentTracker.ResourceGroupName) already exists. Skipping creation." Transition-To-Next-Deployment-Stage $DeploymentTracker Return } $RgObj = New-AzResourceGroup -Name $DeploymentTracker.ResourceGroupName -Location $DeploymentTracker.LocationName Write-Success "Resource Group Created" Transition-To-Next-Deployment-Stage $DeploymentTracker } Catch { Write-Failure "Error initializing" $_.Exception } } # Function to download the connector disk image to customer's account (blob-blob copy) Function Blob-Copy { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [PSObject]$DeploymentTracker ) Write-Status "Copying Connector Disk Image Blob to $($DeploymentTracker.StorageAccountName)" Try { $StorageContext = Get-Storage-Context -SAName $DeploymentTracker.StorageAccountName -RgName $DeploymentTracker.StorageAccountResourceGroupName Write-Status "Creating Container if needed" Try { $ContainerObj = Get-AzStorageContainer -Name $DeploymentTracker.ContainerName -Context $StorageContext } Catch { $ContainerObj = New-AzStorageContainer -Name $DeploymentTracker.ContainerName -Context $StorageContext -Permission Off } Write-Status "Checking previously initiated Blob Copy" Try { $Blob = Get-AzStorageBlob -Blob $DeploymentTracker.BlobName -Container $ContainerObj.Name -Context $StorageContext Write-Success "Previously Intiated BlobCopy found" } Catch { $SrcUri = Get-Location-Based-Disk-Image-BlobUri $DeploymentTracker Write-Status "Initiating Blob Copy from ""$SrcUri""" $Blob = Start-AzStorageBlobCopy -DestBlob $DeploymentTracker.BlobName -DestContainer $ContainerObj.Name -AbsoluteUri $SrcUri -DestContext $StorageContext } Write-Status "Waiting for Blob Copy to Finish" Do { $Blob = Get-AzStorageBlob -Blob $DeploymentTracker.BlobName -Container $ContainerObj.Name -Context $StorageContext $CopyStatus = $Blob.ICloudBlob.CopyState.Status $CopiedBytes = $Blob.ICloudBlob.CopyState.BytesCopied $TotalBytes = $Blob.ICloudBlob.CopyState.TotalBytes if ($TotalBytes -gt 0) { $Progress = ($CopiedBytes / $TotalBytes) * 100 $Progress = [int]$Progress } Write-Status "CopyState: $CopyStatus; Bytes Copied: $CopiedBytes; Progress: $Progress%" if ($CopyStatus -like "pending") { Start-Sleep -Seconds 30 } } While ($CopyStatus -like "pending") if (($CopyStatus -like "complete") -Or ($CopyStatus -like "success")) { Write-Success "Blob Copy Complete" Transition-To-Next-Deployment-Stage $DeploymentTracker } else { Write-Failure -Message "Blob Copy did not complete. Last Copy Status: $CopyStatus" } } Catch { Write-Failure -Message "Error while copying connector blob" -Exception $_.Exception } } # Function to verify checksums of downloaded blob Function Blob-Verify { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [PSObject]$DeploymentTracker ) Write-Status "Verifying Blob for Integrity" Try { # Get access to Copied blob $StorageContext = Get-Storage-Context -SAName $DeploymentTracker.StorageAccountName -RgName $DeploymentTracker.StorageAccountResourceGroupName $Blob = Get-AzStorageBlob -Blob $DeploymentTracker.BlobName -Container $DeploymentTracker.ContainerName -Context $StorageContext # Give it url access for Putblock api (will be revoked after expiration) $StartTime = (Get-Date) $EndTime = $StartTime.AddMinutes(30) $SASUri = New-AzStorageBlobSASToken -Container $DeploymentTracker.ContainerName -Blob $DeploymentTracker.BlobName -Context $StorageContext -Permission r -Protocol HttpsOnly -FullUri -StartTime $StartTime -ExpiryTime $EndTime $ChunkSize = (100L * 1024L * 1024L) # Download source checksums into ArrayList $TempFile1 = New-TemporaryFile $S3ChecksumListUrl = "https://s3.amazonaws.com/sms-connector/AWS-SMS-Connector-for-Azure.vhd.md5list" $Req = Invoke-WebRequest -Uri $S3ChecksumListUrl -OutFile $TempFile1.FullName $SourceChecksums = Get-Content $TempFile1.FullName $RemoveTempFile = Remove-Item $TempFile1.FullName -ErrorAction Ignore # Setup Temporary block blob $TempFile2 = New-TemporaryFile "Calculating Checksum" | Out-File $TempFile2.FullName $BlockBlobName = "ChecksumCalculationBlob" $TempBlockBlob = Set-AzStorageBlobContent -Context $StorageContext -File $TempFile2.FullName -Container $DeploymentTracker.ContainerName -Blob $BlockBlobName -BlobType Block -Force $BlobSize = $Blob.Length $MD5Zero100MB = "LygrhOfmCNWFJEntlAv8UQ==" $Bytes = [System.Text.Encoding]::UTF8.GetBytes("BlockId-1") $BlockId = [System.Convert]::ToBase64String($Bytes); $RemainingLen = $BlobSize $SrcOffset = 0L $Tasks = New-Object -Type System.Collections.ArrayList $Retries = 5 while ($RemainingLen -gt 0) { $TryCount = 0 while ($TryCount -lt $Retries) { Try { if ($RemainingLen -lt $ChunkSize) { $Task = $TempBlockBlob.ICloudBlob.PutBlockAsync($BlockId, $SASUri, $SrcOffset, $RemainingLen, $MD5Zero100MB) } else { $Task = $TempBlockBlob.ICloudBlob.PutBlockAsync($BlockId, $SASUri, $SrcOffset, $ChunkSize, $MD5Zero100MB) } break } Catch { Start-Sleep -Seconds 1 Write-Warning -Message "[$($TryCount + 1)/$Retries] Error occured while creating a PutBlockAsync block. Retrying." -Exception $_.Exception $TryCount++ } } $Added = $Tasks.Add($Task) $SrcOffset += $ChunkSize $RemainingLen -= $ChunkSize } # Check if total chunks match if ($Tasks.count -ne $SourceChecksums.Count) { $RemoveTempFile = Remove-Item $TempFile2 -ErrorAction Ignore $RemoveBlockBlob = Remove-AzStorageBlob -Blob $BlockBlobName -Container $DeploymentTracker.ContainerName -Context $StorageContext -Force -ErrorAction Ignore Write-Failure "Blob Verification Failed. The downloaded blob failed the integrity check. Size mismatch. Expected $($SourceChecksums.Count) tasks, but got $($Tasks.count)" } # Check if individual chunk checksums match $WaitTimeMs = 30000 for ($i = 0; $i -lt $Tasks.Count; $i++) { $Task = $Tasks[$i] try { $w = $Task.Wait($WaitTimeMs) } Catch { } $TryCount = 0 $CorrectCksum = "" $RemainingLen = $BlobSize - ($ChunkSize * $i) while ( $TryCount -lt $Retries -and $CorrectCksum -ne $SourceChecksums[$i]) { if ($Task.IsFaulted -eq $true) { if($Task.Exception.InnerException.RequestInformation.ErrorCode -like "Md5Mismatch") { $CorrectCksum = $Task.Exception.InnerException.RequestInformation.ExtendedErrorInformation.AdditionalDetails.ServerCalculatedMd5 } } else { $CorrectCksum = $MD5Zero100MB } Start-Sleep -Seconds 1 $TryCount++ if ($CorrectCksum -ne $SourceChecksums[$i]) { # A small number of tasks can fail with Storage Exception (due to throttling) and needs to be retried if ($TryCount -eq $Retries) { Write-Status -Message "[$TryCount/$Retries] Exhausted all retries" } else { Write-Status -Message "[$TryCount/$Retries] Error occured in PutBlockAsync while uploading a block. Retrying $($Task.Exception)" if ($RemainingLen -lt $ChunkSize) { $Task = $TempBlockBlob.ICloudBlob.PutBlockAsync($BlockId, $SASUri, ($i * $ChunkSize), $RemainingLen, $MD5Zero100MB) } else { $Task = $TempBlockBlob.ICloudBlob.PutBlockAsync($BlockId, $SASUri, ($i * $ChunkSize), $ChunkSize, $MD5Zero100MB) } try { $w = $Task.Wait($WaitTimeMs) } Catch { Write-Warning -Message "Error while waiting for the task ($i) to finish: " -Exception $_.Exception } } } } if ($CorrectCksum -ne $SourceChecksums[$i]) { $RemoveTempFile = Remove-Item $TempFile2 -ErrorAction Ignore $RemoveBlockBlob = Remove-AzStorageBlob -Blob $BlockBlobName -Container $DeploymentTracker.ContainerName -Context $StorageContext -Force -ErrorAction Ignore $Expected = $SourceChecksums[$i] Write-Failure "Blob Verification Failed ($i). The downloaded blob failed the integrity check." } } # Cleanup $RemoveTempFile = Remove-Item $TempFile2 -ErrorAction Ignore $RemoveBlockBlob = Remove-AzStorageBlob -Blob $BlockBlobName -Container $DeploymentTracker.ContainerName -Context $StorageContext -Force -ErrorAction Ignore Write-Success "Blob Integrity Check Succeeded!" Transition-To-Next-Deployment-Stage $DeploymentTracker } Catch { Write-Failure -Message "Error while checking for Blob Integrity: " -Exception $_.Exception } Write-Success "Blob Integrity Check Succeeded!" Transition-To-Next-Deployment-Stage $DeploymentTracker } # Function to Create Disk from Disk Blob Function Disk-Create { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [PSObject]$DeploymentTracker ) Write-Status "Creating Disk from Copied DiskBlob. This can take a while." Try { $ExistingDisk = Get-AzDisk -ResourceGroupName $DeploymentTracker.ResourceGroupName -DiskName $DeploymentTracker.DiskName -ErrorAction Ignore if ($ExistingDisk -ne $null) { Write-Success "Disk $($DeploymentTracker.DiskName) already exists. Skipping creation." Transition-To-Next-Deployment-Stage $DeploymentTracker Return } $ContainerName = $DeploymentTracker.ContainerName $BlobName = $DeploymentTracker.BlobName $StorageAccountName = $DeploymentTracker.StorageAccountName $BlobUri="https://$StorageAccountName.blob.core.windows.net/$ContainerName/$BlobName" $StorageContext = Get-Storage-Context -SAName $StorageAccountName -RgName $DeploymentTracker.StorageAccountResourceGroupName $Blob = Get-AzStorageBlob -Blob $BlobName -Container $ContainerName -Context $StorageContext $BlobSizeInGB = [int]($Blob.Length / 1024 / 1024 / 1024) # Handle breaking Change in New-AzDiskConfig - https://docs.microsoft.com/en-us/powershell/azure/release-notes-azureps?view=azps-3.0.0 Try { $AzModule = Get-InstalledModule -Name Az -MinimumVersion 3.0.0 } Catch { } if ($AzModule -eq $null) { # Az Version is < 3.0.0 - Does not need storageAccountId $DiskConfig = New-AzDiskConfig -SkuName Standard_LRS -OsType Linux -DiskSizeGB $BlobSizeInGB -Location $DeploymentTracker.LocationName -SourceUri $BlobUri -CreateOption Import } else { # Az Version is >= 3.0.0 - Needs storageAccountId $SAObj = (Get-AzStorageAccount | Where-Object {$_.StorageAccountName -eq "$StorageAccountName"}) | GetNonBlankValue -Field "StorageAccount" $DiskConfig = New-AzDiskConfig -SkuName Standard_LRS -OsType Linux -DiskSizeGB $BlobSizeInGB -Location $DeploymentTracker.LocationName -SourceUri $BlobUri -CreateOption Import -StorageAccountId $SAObj.Id } $Disk = New-AzDisk -ResourceGroupName $DeploymentTracker.ResourceGroupName -DiskName $DeploymentTracker.DiskName -Disk $DiskConfig Write-Success "Disk Creation Complete" Transition-To-Next-Deployment-Stage $DeploymentTracker } Catch { Write-Failure -Message "Error while creating Disk" -Exception $_.Exception } } # Function to create Connector VM from Managed Disk Function VirtualMachine-Create { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [PSObject]$DeploymentTracker ) Write-Status "Creating Connector Virtual Machine. This can take a while." Try { $ExistingVM = Get-AzVM -ResourceGroupName $DeploymentTracker.ResourceGroupName -Name $DeploymentTracker.VirtualMachineName -ErrorAction Ignore if ($ExistingVM -ne $null) { Write-Success "Virtual Machine $($DeploymentTracker.VirtualMachineName) is already created. Skipping creation." Transition-To-Next-Deployment-Stage $DeploymentTracker Return } $RgName = $DeploymentTracker.ResourceGroupName $LocationName = $DeploymentTracker.LocationName $VnetName = $DeploymentTracker.ExistingVirtualNetworkName $Disk = Get-AzDisk -ResourceGroupName $RgName -DiskName $DeploymentTracker.DiskName # Initialize VMConfig $VMConfig = New-AzVMConfig -VMName $DeploymentTracker.VirtualMachineName -VMSize $DeploymentTracker.VirtualMachineSize -IdentityType SystemAssigned $VMConfig.Location = $DeploymentTracker.LocationName # Configure OS disk to be attached $VMConfig = Set-AzVMOSDisk -VM $VMConfig -ManagedDiskId $Disk.Id -CreateOption Attach -Linux # Enable Boot Diagnostics # Handle Change in Cmdlet name - https://docs.microsoft.com/en-us/powershell/azure/release-notes-azureps?view=azps-2.0.0 Try { $AzModule = Get-InstalledModule -Name Az -MinimumVersion 2.0.0 } Catch { } if ($AzModule -eq $null) { # Az Version is < 2.0.0 - Use "Set-AzVMBootDiagnostics" $VMConfig = Set-AzVMBootDiagnostics -VM $VMConfig -Enable -StorageAccountName $DeploymentTracker.StorageAccountName -ResourceGroupName $DeploymentTracker.StorageAccountResourceGroupName } else { # Az Version is >= 2.0.0 - Use "Set-AzVMBootDiagnostic" $VMConfig = Set-AzVMBootDiagnostic -VM $VMConfig -Enable -StorageAccountName $DeploymentTracker.StorageAccountName -ResourceGroupName $DeploymentTracker.StorageAccountResourceGroupName } # Configure Network settings $Vnet = Get-AzVirtualNetwork -Name $VnetName -ResourceGroupName $DeploymentTracker.VirtualNetworkResourceGroupName $Subnet = Get-AzVirtualNetworkSubnetConfig -Name $DeploymentTracker.SubnetName -VirtualNetwork $Vnet # Create NSG $ExistingNSG = Get-AzNetworkSecurityGroup -ResourceGroupName $RgName -Name $DeploymentTracker.NSGName -ErrorAction Ignore if ($ExistingNSG -eq $null) { $NSG = New-AzNetworkSecurityGroup -ResourceGroupName $RgName -Location $LocationName -Name $DeploymentTracker.NSGName Write-Success "Created Network Security Group $($NSG.Name)" } else { $NSG = $ExistingNSG Write-Success "Network Security Group $($ExistingNSG.Name) already exists. Skipping creation." } # Create PIP $ExistingPIP = Get-AzPublicIpAddress -ResourceGroupName $RgName -Name $DeploymentTracker.PIPName -ErrorAction Ignore if ($ExistingPIP -eq $null) { $PIP = New-AzPublicIpAddress -Name $DeploymentTracker.PIPName -ResourceGroupName $RgName -Location $LocationName -AllocationMethod Dynamic Write-Success "Created Public Ip $($PIP.Name)" } else { $PIP = $ExistingPIP Write-Success "Public Ip $($ExistingPIP.Name) already exists. Skipping creation." } # Create NIC $ExistingNIC = Get-AzNetworkInterface -ResourceGroupName $RgName -Name $DeploymentTracker.NICName -ErrorAction Ignore if ($ExistingNIC -eq $null) { $NIC = New-AzNetworkInterface -Name $DeploymentTracker.NICName -ResourceGroupName $RgName -Location $LocationName ` -SubnetId $Subnet.Id -NetworkSecurityGroupId $NSG.Id -PublicIpAddressId $PIP.Id Write-Success "Created NIC $($NIC.Name)" } else { $NIC = $ExistingNIC Write-Success "NIC $($ExistingNIC.Name) already exists. Skipping creation." } $VMConfig = Add-AzVMNetworkInterface -VM $VMConfig -Id $NIC.Id Write-Info "Creating VM with the following parameters" Write-Text ($VMConfig | Format-Table | Out-String) # Tag to identify Storage account from within the Connector $SAcct = $DeploymentTracker.StorageAccountName $SRg = $DeploymentTracker.StorageAccountResourceGroupName $VMTagValue = "$SAcct|$SRg" $VMTag = @{"$($DeploymentTracker.VirtualMachineTagKey)" = "$VMTagValue";} # Create VM with the Configuration $VM = New-AzVM -VM $VMConfig -ResourceGroupName $RgName -Location $LocationName # -Tag $VMTag Write-Success "Virtual Machine Creation Complete" Transition-To-Next-Deployment-Stage $DeploymentTracker } Catch { Write-Failure -Message "Error while creating VirtualMachine" -Exception $_.Exception } } # Configure VM with Identity, NSG, etc.. Function Post-Config { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [PSObject]$DeploymentTracker ) Write-Status "Performing Post Config" Try { Write-Status "Creating Custom Roles for Connector" $SubscriptionScope = Create-Connector-Role $DeploymentTracker $StorageScope = Create-Connector-Storage-Role $DeploymentTracker $VM = Get-AzVM -Name $DeploymentTracker.VirtualMachineName -ResourceGroupName $DeploymentTracker.ResourceGroupName # Assign Subscription Scoped role to System Identity $ExistingAssignment = Get-AzRoleAssignment -ObjectId $VM.Identity.PrincipalId -RoleDefinitionName $DeploymentTracker.RoleName -ErrorAction Ignore if ($ExistingAssignment -eq $null) { $Assignment1 = New-AzRoleAssignment -ObjectId $VM.Identity.PrincipalId -RoleDefinitionName $DeploymentTracker.RoleName -Scope $SubscriptionScope Write-Success "$($DeploymentTracker.RoleName) successfully assigned to Connector VM's System Identity" } else { Write-Success "$($DeploymentTracker.RoleName) already assigned to Connector VM's System Identity" } # Assign Storage Scoped role to System Identity $ExistingAssignment = Get-AzRoleAssignment -ObjectId $VM.Identity.PrincipalId -RoleDefinitionName $DeploymentTracker.RoleSAName -ErrorAction Ignore if ($ExistingAssignment -eq $null) { $Assignment2 = New-AzRoleAssignment -ObjectId $VM.Identity.PrincipalId -RoleDefinitionName $DeploymentTracker.RoleSAName -Scope $StorageScope Write-Success "$($DeploymentTracker.RoleSAName) successfully assigned to Connector VM's System Identity" } else { Write-Success "$($DeploymentTracker.RoleSAName) already assigned to Connector VM's System Identity" } # Print Connector Details $Ni = Get-AzNetworkInterface | where {$_.Id -eq $VM.NetworkProfile.NetworkInterfaces[0].Id} $Ip = $Ni.IpConfigurations.PrivateIpAddress $VnetName = $DeploymentTracker.ExistingVirtualNetworkName $Vnet = Get-AzVirtualNetwork -Name $VnetName -ResourceGroupName $DeploymentTracker.VirtualNetworkResourceGroupName $Subnet = Get-AzVirtualNetworkSubnetConfig -Name $DeploymentTracker.SubnetName -VirtualNetwork $Vnet Write-Success "Post Config is Complete" Write-Text Write-Text "*******************************************************************************************" Write-Info "Connector VM Name: ""$($VM.Name)"" (Resource Group: ""$($VM.ResourceGroupName)"")" Write-Info "Connector Private IP: $Ip" Write-Info "Object Id of System Assigned Identity for Connector VM: $($VM.Identity.PrincipalId)" if ($Subnet.NetworkSecurityGroup -ne $null) { Write-Warning -Message "Subnet $($DeploymentTracker.SubnetName) under Virtual Network $VNetName has a Network Security Group attached!" Write-Info -Message "This may affect the reachability and Internet connectivity of the Connector VM." Write-Info -Message "Please ensure Connector VM has inbound HTTPS (Port 443) access from within its Subnet and Outbound Internet Connectivity for AWS/Azure access." } Write-Info "From a VM in VNet: $VnetName, Goto https://$Ip" Write-Info "Complete Connector Registration. Use the above Object Id at the last step of Registration." Write-Text "*******************************************************************************************" Write-Text Write-Text Transition-To-Next-Deployment-Stage $DeploymentTracker } Catch { Write-Failure -Message "Error while doing post configuration" -Exception $_.Exception } } # Complete and Cleanup deployment Function Complete-Deployment { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [PSObject]$DeploymentTracker ) Write-Status "Completing Deployment" Delete-DeploymentTracker $DeploymentTracker Write-Success "Deployment Complete" } ####################### Azure Utilities ############################ Function Login { Try { if (($SubscriptionId -eq $null) -Or ($SubscriptionId -eq "")) { $Login = Connect-AzAccount } else { $Login = Connect-AzAccount -Subscription $SubscriptionId } Write-Info "Logged into Account $($Login.Context.Account.Id) under Subscription $($Login.Context.Subscription.Id)" Write-Success "Login Successful!" } Catch { Write-Failure -Message "Could not login to Azure account" -Exception $_.Exception } } Function Create-Connector-Role { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [PSObject]$DeploymentTracker ) $RoleName = $DeploymentTracker.RoleName Write-Status "Creating Connector Role $RoleName" Try { $ExistingRole = Get-AzRoleDefinition -Name $RoleName -ErrorAction Ignore if ($ExistingRole -ne $null) { Write-Success "Connector Role $RoleName already exists. Skipping Creation." Return $ExistingRole.AssignableScopes } $Role = Get-AzRoleDefinition "Virtual Machine Contributor" $Role.Id = $null $Role.Name = $RoleName $Role.Description = "Perform AWS Server Migration Service related connector tasks on behalf of the customer. This lets Connector create snapshots of Virtual Machines, Read list of virtual machines, etc." $Role.Actions.Clear() $Role.Actions.Add("Microsoft.Compute/virtualMachines/read") $Role.Actions.Add("Microsoft.Network/networkInterfaces/read") $Role.Actions.Add("Microsoft.Compute/operations/read") $Role.Actions.Add("Microsoft.Compute/disks/read") $Role.Actions.Add("Microsoft.Compute/disks/beginGetAccess/action") $Role.Actions.Add("Microsoft.Compute/disks/endGetAccess/action") $Role.Actions.Add("Microsoft.Compute/snapshots/read") $Role.Actions.Add("Microsoft.Compute/snapshots/write") $Role.Actions.Add("Microsoft.Compute/snapshots/delete") $Role.Actions.Add("Microsoft.Compute/snapshots/beginGetAccess/action") $Role.Actions.Add("Microsoft.Compute/snapshots/endGetAccess/action") $Role.Actions.Add("Microsoft.Storage/storageAccounts/listkeys/action") $Role.Actions.Add("Microsoft.Storage/storageAccounts/read") $Role.Actions.Add("Microsoft.Resources/subscriptions/resourcegroups/read") $Role.Actions.Add("Microsoft.Authorization/roleDefinitions/read") $Role.Actions.Add("Microsoft.Authorization/roleAssignments/read") $SubId = $DeploymentTracker.SubscriptionId $Scope = "/subscriptions/$SubId" $Role.AssignableScopes.Clear() $Role.AssignableScopes.Add($Scope) Write-Info "Role $RoleName ($($Role.Description)) will be created under scope $($Role.AssignableScopes) and assigned to Connector with the following Actions: " Write-Text ($Role.Actions | Format-List | Out-String) $RoleDef = New-AzRoleDefinition -Role $Role Write-Success "Connector Role $RoleName successfully created!" Return $Scope } Catch { Write-Failure -Message "Could not create Connector role" -Exception $_.Exception } } Function Create-Connector-Storage-Role { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [PSObject]$DeploymentTracker ) $RoleName = $DeploymentTracker.RoleSAName Write-Status "Creating Connector Storage Role $RoleName" Try { $ExistingRole = Get-AzRoleDefinition -Name $RoleName -ErrorAction Ignore if ($ExistingRole -ne $null) { Write-Success "Connector Storage Role $RoleName already exists. Skipping creation." Return $ExistingRole.AssignableScopes } $Role = Get-AzRoleDefinition "Virtual Machine Contributor" $Role.Id = $null $Role.Name = $RoleName $Role.Description = "Perform AWS Server Migration Service related connector tasks on behalf of the customer. " $Role.Description += "This role lets Connector read and write to blobs in its deployed storage account." $Role.Actions.Clear() $Role.Actions.Add("Microsoft.Storage/storageAccounts/listkeys/action") $Role.Actions.Add("Microsoft.Storage/storageAccounts/read") $Role.Actions.Add("Microsoft.Storage/storageAccounts/blobServices/read") $Role.Actions.Add("Microsoft.Storage/storageAccounts/blobServices/write") $Role.Actions.Add("Microsoft.Storage/storageAccounts/blobServices/containers/delete") $Role.Actions.Add("Microsoft.Storage/storageAccounts/blobServices/containers/read") $Role.Actions.Add("Microsoft.Storage/storageAccounts/blobServices/containers/write") $Role.DataActions.Clear() $Role.DataActions.Add("Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read") $Role.DataActions.Add("Microsoft.Storage/storageAccounts/blobServices/containers/blobs/write") $Role.DataActions.Add("Microsoft.Storage/storageAccounts/blobServices/containers/blobs/delete") $SubId = $DeploymentTracker.SubscriptionId $SAName = $DeploymentTracker.StorageAccountName $RgName = $DeploymentTracker.StorageAccountResourceGroupName $Scope = "/subscriptions/$SubId/resourceGroups/$RgName/providers/Microsoft.Storage/storageAccounts/$SAName" $Role.AssignableScopes.Clear() $Role.AssignableScopes.Add($Scope) Write-Info "Role $RoleName ($($Role.Description)) will be created under scope $($Role.AssignableScopes) and assigned to Connector with the following Actions: " Write-Text ($Role.Actions | Format-List | Out-String) Write-Info "And the following DataActions:" Write-Text ($Role.DataActions | Format-List | Out-String) $RoleDef = New-AzRoleDefinition -Role $Role Write-Success "Connector Storage Role $RoleName successfully created!" Return $Scope } Catch { Write-Failure -Message "Could not create Connector Storage role" -Exception $_.Exception } } Function Get-Location-Based-Disk-Image-BlobUri { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [PSObject]$DeploymentTracker ) $DISK_IMAGE_URI = "https://awssmsconnector.blob.core.windows.net/release/AWS-SMS-Connector-for-Azure.vhd" Return $DISK_IMAGE_URI } Function Get-Location-Based-Version-BlobUri { Return "https://awssmsconnector.blob.core.windows.net/release/AWS-SMS-Connector-for-Azure.version" } Function Get-Location-Based-VM-Size { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$Location ) Return "Standard_F4s" } Function Get-Storage-Context { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$SAName, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$RgName ) $Keys = (Get-AzStorageAccountKey -ResourceGroupName $RgName -Name $SAName) # | GetNonBlankValue -Field "StorageAccountKey" $SAKey = $Keys[0].Value $StorageContext = New-AzStorageContext -StorageAccountName $SAName -StorageAccountKey $SAKey Return $StorageContext } Function Derive-Resource-Name { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [string]$ResourceType, [Parameter(Mandatory=$false)] [string]$Location, [Parameter(Mandatory=$false)] [string]$Index ) if (($Location -ne $null) -And ($Location -ne "")) { $FormatString = "sms-connector-{0}{1}-{2}" -f "$ResourceType", "$Index", "$Location" } else { $FormatString = "sms-connector-{0}{1}" -f "$ResourceType", "$Index" } Return $FormatString } #################### DEPLOYMENT TRACKER FUNCTIONS ######################## Function Transition-To-Next-Deployment-Stage { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [PSObject]$DeploymentTracker ) # Save Current Stage Save-DeploymentTracker $DeploymentTracker if ($DeploymentTracker.Stage -ne $Global:DEPLOYMENT_STAGE_COMPLETE) { Write-Status "Current Deployment Stage: $($DeploymentTracker.Stage). Transitioning to Next Stage" } else { Write-Status "Current Deployment Stage: $($DeploymentTracker.Stage)." } Try { # Transition to next stage $Stage = $DeploymentTracker.Stage Switch ($Stage) { $Global:DEPLOYMENT_STAGE_INITIALIZE { $DeploymentTracker.Stage = $Global:DEPLOYMENT_STAGE_RG_CREATE ResourceGroup-Create $DeploymentTracker } $Global:DEPLOYMENT_STAGE_RG_CREATE { $DeploymentTracker.Stage = $Global:DEPLOYMENT_STAGE_BLOB_COPY Blob-Copy $DeploymentTracker } $Global:DEPLOYMENT_STAGE_BLOB_COPY { $DeploymentTracker.Stage = $Global:DEPLOYMENT_STAGE_BLOB_VERIFY Blob-Verify $DeploymentTracker } $Global:DEPLOYMENT_STAGE_BLOB_VERIFY { $DeploymentTracker.Stage = $Global:DEPLOYMENT_STAGE_DISK_CREATE Disk-Create $DeploymentTracker } $Global:DEPLOYMENT_STAGE_DISK_CREATE { $DeploymentTracker.Stage = $Global:DEPLOYMENT_STAGE_VM_CREATE VirtualMachine-Create $DeploymentTracker } $Global:DEPLOYMENT_STAGE_VM_CREATE { $DeploymentTracker.Stage = $Global:DEPLOYMENT_STAGE_POST_CONFIG Post-Config $DeploymentTracker } $Global:DEPLOYMENT_STAGE_POST_CONFIG { $DeploymentTracker.Stage = $Global:DEPLOYMENT_STAGE_COMPLETE Complete-Deployment $DeploymentTracker } $Global:DEPLOYMENT_STAGE_COMPLETE { # The only way to come here is when deployment failed at complete stage and we resumed Complete-Deployment $DeploymentTracker } default { Write-Failure -Message "Invalid Deployment Stage!" } } } Catch { Write-Failure -Message "Error Transitioning to next deployment stage" -Exception $_.Exception } } Function Save-DeploymentTracker { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [PSObject]$DeploymentTracker ) Write-Status "Saving Deployment Progress" Try { $StorageContext = Get-Storage-Context $DeploymentTracker.StorageAccountName $DeploymentTracker.StorageAccountResourceGroupName $ContainerName = Derive-Resource-Name "container" $DeploymentTracker.LocationName $ContainerObj = Get-AzStorageContainer -Name $ContainerName -Context $StorageContext -ErrorAction Ignore if ($ContainerObj -eq $null) { $ContainerObj = New-AzStorageContainer -Name $ContainerName -Context $StorageContext } $BlobName = $Global:DEPLOYMENT_BLOB_NAME $TrackerJson = $DeploymentTracker | ConvertTo-Json $TrackerJson | Out-File "$BlobName" $BlobObj = Set-AzStorageBlobContent -Container $ContainerName -Blob "$BlobName" -BlobType Block -Context $StorageContext -File "$BlobName" -Force Write-Success "Saved Deployment Progress" $RemoveLocalFile = Remove-Item -Path $BlobName -ErrorAction Ignore } Catch { Write-Warning -Message "Error saving deployment progress. This deployment cannot be resumed." -Exception $_.Exception } } Function Resume-Deployment { param( [parameter(Mandatory=$true)][String] $StorageAccountName ) Write-Status "Checking if this was a previous deployment" Try { $SAObj = (Get-AzStorageAccount | Where-Object {$_.StorageAccountName -eq "$StorageAccountName"}) | GetNonBlankValue -Field "StorageAccount" $LocationName = $SAObj.Location $StorageContext = Get-Storage-Context -SAName $StorageAccountName -RgName $SAObj.ResourceGroupName Write-Info "Storage Account $StorageAccountName is under Resource Group: $($SAObj.ResourceGroupName) and Location: $LocationName" $ContainerName = Derive-Resource-Name -ResourceType "container" -Location $LocationName $ContainerObj = Get-AzStorageContainer -Name $ContainerName -Context $StorageContext -ErrorAction Ignore if ($ContainerObj -eq $null) { Return $null } $BlobName = $Global:DEPLOYMENT_BLOB_NAME $BlobObj = Get-AzStorageBlobContent -Container $ContainerName -Blob $BlobName -Context $StorageContext -Force -ErrorAction Ignore if ($BlobObj -eq $null) { Return $null } $DeploymentTracker = Get-Content "$BlobName" | ConvertFrom-Json Write-Success "Found a previous deployment that can be resumed. Last Completed Deployment Stage: $($DeploymentTracker.Stage). Deployment details:" Write-Text ($DeploymentTracker | Format-List | Out-String) $Conf = Read-Host "Would you like to resume this deployment (y/n)? " if ($Conf -eq "y") { $SubObj = Select-AzSubscription -SubscriptionId $DeploymentTracker.SubscriptionId Return $DeploymentTracker } else { Write-Warning "This will cleanup the existing deployment and start a new one." $Conf = Read-Host "Do you want to proceed (y/n): " if ($Conf -ne "y") { Exit-Application } Delete-DeploymentTracker $DeploymentTracker Return $null } } Catch { Write-Warning -Message "Error retrieving previous deployment progress. Previous deployment cannot be resumed." -Exception $_.Exception Return $null } } Function Delete-DeploymentTracker { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [PSObject]$DeploymentTracker ) Write-Status "Cleaning up Deployment Tracker" Try { $StorageContext = Get-Storage-Context -SAName $DeploymentTracker.StorageAccountName -RgName $DeploymentTracker.StorageAccountResourceGroupName $ContainerName = Derive-Resource-Name -ResourceType "container" -Location $DeploymentTracker.LocationName $ContainerObj = Get-AzStorageContainer -Name $ContainerName -Context $StorageContext $BlobName = $Global:DEPLOYMENT_BLOB_NAME $RemoveBlob = Remove-AzStorageBlob -Blob $BlobName -Container $ContainerName -Context $StorageContext -Force $RemoveLocalFile = Remove-Item -Path $BlobName -ErrorAction Ignore Write-Success "Completed Cleaning up Deployment Tracker" } Catch { Write-Warning -Message "Error cleaning up deployment tracker." -Exception $_.Exception } } #################### PS UTILITIES ######################## Function Setup { # Check for Powershell version Verify-PowerShell-Version $POWERSHELL_MIN_VERSION # Check for any installation of Az Verify-Install-Az-Version $AZ_MODULE_MIN_VERSION } Function Verify-PowerShell-Version { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.Version]$minimumVersion ) Write-Status "Verifying PowerShell version" [System.Version]$psVersion = "1.0" if ($PSVersionTable.PSVersion) { $psVersion = $PSVersionTable.PSVersion } $Message = "PowerShell version $minimumVersion or higher is required. Current PowerShell version is $psVersion." $Message += " Refer to https://docs.microsoft.com/en-us/powershell/scripting/install/installing-windows-powershell?view=powershell-6" If ($psVersion -lt $minimumVersion) { Write-Failure -Message $Message } else { Write-Success $Message } } Function Verify-Install-Az-Version { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.Version]$MinimumRequiredVersion ) $RecommendedVersion = "2.0.0" $BreakingVersion = "3.0.0" Write-Status "Verifying Az Module version (Azure Powershell)" Try { Try { $Versions = Get-InstalledModule -Name Az -AllVersions Write-Success "Found an installed version of Az Module." } Catch { Write-Warning "Could not find any installed version of Az Module." $Conf = Read-Host "Would you like to install this Module (y/n)? " if ($Conf -ne 'y') { Exit-Application } Write-Status "Installing Az Module" Set-PSRepository -Name PSGallery -InstallationPolicy Trusted Install-Module -Name Az -Scope CurrentUser -AllowClobber -MaximumVersion $RecommendedVersion } Try { $CurVersion = Get-InstalledModule -Name Az -MinimumVersion $MinimumRequiredVersion Write-Success "Current Az Module version $($CurVersion.Version) satisfies the required minimum version $MinimumRequiredVersion." if ([System.Version] $CurVersion.Version -ge [System.Version] $BreakingVersion) { Write-Warning "The script works best with Az Module version $RecommendedVersion. Found $($CurVersion.Version) instead." Write-Warning "If you run into issues related to cmdlet failures, please re-install Az Module version $RecommendedVersion." } } Catch { Write-Warning "Could not find the minimum required installed version of Az Module - $minimumVersion" $Conf = Read-Host "Would you like to update Az module to the latest version (y/n)? " if ($Conf -ne 'y') { Exit-Application } else { Write-Status "Updating Az Module" Update-Module -Name Az -MaximumVersion $RecommendedVersion } } Try { # Import Module for use Write-Status "Importing Az Module" Import-Module -Name Az } Catch { Write-Warning "Error while Importing Az Module" + $_.Exception + ", Retrying ..." Import-Module -Name Az } Write-Success "Successfully Imported required modules for setup to work" } Catch { Write-Failure "Error while installing/updating/importing Az Module for Powershell" $_.Exception } } Function GetNonBlankValue { [CmdletBinding()] Param ( [Parameter(Mandatory=$true, ValueFromPipeline=$true)] $Value1, [Parameter(Mandatory=$false)] $Value2, [Parameter(Mandatory=$true)] [string]$Field ) if (($Value1 -ne $null) -And ($Value1 -ne "")) { Return $Value1 } if (($Value2 -ne $null) -And ($Value2 -ne "")) { Return $Value2 } Write-Failure "The provided $Field does not exist or is invalid!" } Function GetExceptionString { [CmdletBinding()] Param( [Parameter(Mandatory=$false)]$Exception ) if ($Exception -ne $null) { $LineNumber = $Exception.InvocationInfo.ScriptLineNumber if (($LineNumber -eq $null) -Or ($LineNumber -eq "")) { Try { $LineNumber = $Exception.InnerException.InvocationInfo.ScriptLineNumber } Catch { } } $Message = $Exception.Message $ExceptionStr = "Message: $Message (Line: $LineNumber)" $ExceptionStr += "`r`n" $ExceptionStr += "ExceptionDetails: " + $Exception.ToString() Return $ExceptionStr } Return "" } Function Write-Failure { [CmdletBinding()] Param( [Parameter(Mandatory=$true)][string]$Message, [Parameter(Mandatory=$false)]$Exception ) $Message = "[ERROR] $Message" Write-Host $Message -ForegroundColor White -BackgroundColor Red $ExceptionStr = GetExceptionString $Exception if ($ExceptionStr -ne "") { Write-Host $ExceptionStr -ForegroundColor Red } $TranscriptPath = Join-Path -Path (Get-Location).Path -ChildPath $Global:TRANSCRIPT_FILE_NAME $SupportMessage = "For support, please email 'aws-server-migration-azure-support@amazon.com'." $SupportMessage += " Attach the log file '$TranscriptPath' with the email." Write-Host $SupportMessage -ForegroundColor White -BackgroundColor Red if ($ErrorActionPreference -eq "Stop") { Exit-Application } } Function Write-Warning { [CmdletBinding()] Param( [Parameter(Mandatory=$true)][string]$Message, [Parameter(Mandatory=$false)]$Exception ) $Message = "[Warning] $Message" Write-Host $Message -ForegroundColor Yellow -BackgroundColor Black $ExceptionStr = GetExceptionString $Exception if ($ExceptionStr -ne "") { Write-Host $ExceptionStr -ForegroundColor Yellow -BackgroundColor Black } } Function Write-Success { [CmdletBinding()] Param([Parameter(Mandatory=$true)][string]$Message) $Message = "[Success] $Message" Write-Host $Message -ForegroundColor Green -BackgroundColor Black } Function Write-Status { [CmdletBinding()] Param([Parameter(Mandatory=$true)][string]$Message) $Message = "[Working] $Message ..." Write-Host $Message -ForegroundColor White -BackgroundColor Black } Function Write-Info { [CmdletBinding()] Param([Parameter(Mandatory=$true)][string]$Message) $Message = "[INFO] $Message" Write-Host $Message -ForegroundColor White -BackgroundColor Black } Function Write-Text { [CmdletBinding()] Param([Parameter(Mandatory=$false, ValueFromPipeline=$true)][string]$Message) Write-Host $Message } Function Exit-Application { [CmdletBinding()] Param([Parameter(Mandatory=$false)][int]$ExitCode) if ($ExitCode -gt 0) { Write-Error "Exiting with Error code: $ExitCode" } Stop-Transcript Exit } ###################### MAIN DRIVER ########################### Function Driver { # Start Transcript Start-Transcript -Path $Global:TRANSCRIPT_FILE_NAME -IncludeInvocationHeader -Force -Append # Check Powershell and Az module versions Setup # Login to Azure account Login # See if this is a previous deployment $DeploymentTracker = Resume-Deployment $StorageAccountName if ($DeploymentTracker -ne $null) { Write-Status "Resuming Previous Deployment" Transition-To-Next-Deployment-Stage $DeploymentTracker } else { Write-Status "Starting new Deployment" Initialize } Exit-Application } Driver # SIG # Begin signature block # MIIcxQYJKoZIhvcNAQcCoIIctjCCHLICAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDQ9o2ezL2BzyH5 # lQfb1lEHiM/Hrl/NcagTDpMIqEmq56CCDKgwggXkMIIEzKADAgECAhABpQjzKYtg # EwvB4F5wOdBLMA0GCSqGSIb3DQEBCwUAMGwxCzAJBgNVBAYTAlVTMRUwEwYDVQQK # EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xKzApBgNV # BAMTIkRpZ2lDZXJ0IEVWIENvZGUgU2lnbmluZyBDQSAoU0hBMikwHhcNMjAwOTA4 # MDAwMDAwWhcNMjEwOTEzMTIwMDAwWjCB+jETMBEGCysGAQQBgjc8AgEDEwJVUzEZ # MBcGCysGAQQBgjc8AgECEwhEZWxhd2FyZTEdMBsGA1UEDwwUUHJpdmF0ZSBPcmdh # bml6YXRpb24xEDAOBgNVBAUTBzQxNTI5NTQxCzAJBgNVBAYTAlVTMRMwEQYDVQQI # EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdTZWF0dGxlMSIwIAYDVQQKExlBbWF6b24g # V2ViIFNlcnZpY2VzLCBJbmMuMRswGQYDVQQLExJBV1MgRUMyIEVudGVycHJpc2Ux # IjAgBgNVBAMTGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4wggEiMA0GCSqGSIb3 # DQEBAQUAA4IBDwAwggEKAoIBAQCgeBsM7/6qyPFxrQ3Y3mbY12bkZRdaPs+anlwp # 4LlfrBEIP3F2ncDBsX5ZITIdRZlXEdmQcpO0tn852g74MiffuIa1qpMFxYaYSZ+Y # 9U7KuXT/kDDR7I+pt4nhVHs2LAJOrkmAsqiStmkcqdkL13HilLcV9ni0rwilw9YT # GagvIEY0xkEFudOg0tlPztOD6D+RSwBWN5IRs79o3U6vG+U1W/W2rV3Bq/9Kvc+Q # vwtnh+Zv+tsN3cO7H6IOEQlBpr7CMJe+wL34kcYAbvqU0mvAh60FcLjF0ykyHFkv # eKtseHfbtAymHFyVOovUhXZ1YQCKMTzL0OBrsWCB6iY09v0bAgMBAAGjggHxMIIB # 7TAfBgNVHSMEGDAWgBSP6H7wbTJqAAUjx3CXajqQ/2vq1DAdBgNVHQ4EFgQUIs+1 # MZB2qt0jtlOsh3oP7GCY6+EwLgYDVR0RBCcwJaAjBggrBgEFBQcIA6AXMBUME1VT # LURFTEFXQVJFLTQxNTI5NTQwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsG # AQUFBwMDMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNv # bS9FVkNvZGVTaWduaW5nU0hBMi1nMS5jcmwwN6A1oDOGMWh0dHA6Ly9jcmw0LmRp # Z2ljZXJ0LmNvbS9FVkNvZGVTaWduaW5nU0hBMi1nMS5jcmwwSwYDVR0gBEQwQjA3 # BglghkgBhv1sAwIwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQu # Y29tL0NQUzAHBgVngQwBAzB+BggrBgEFBQcBAQRyMHAwJAYIKwYBBQUHMAGGGGh0 # dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBIBggrBgEFBQcwAoY8aHR0cDovL2NhY2Vy # dHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0RVZDb2RlU2lnbmluZ0NBLVNIQTIuY3J0 # MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggEBAGuhJgrlsIojPBjZQTT1 # 2v+HO/g2kbvy9DTrb1i0l3L5vVckhrr1eKP5I/hEx5hY9DUXwYJRB44A3LEyZhef # IFEQkMvW9gxmO1xws3ZOxOjFhZ9gSiifq5bezBnNldO/+8GMZUlHIBqqRSqOfZ/A # mkl4sDPgElop9ZUL0pAGE0Zo4r/sG73euxE0QB4bERXiSuQCkcG1H8S0cZXImUJd # ql6aMfIgKCOIKR9H0cxMLmyuk0S4WyCprWkQtIisO0HZfuIOfiwBvWCzgipF31jI # I/kA8PmBYxGrj0jypinG53qlgJNheOu34KGUh/pXuzbyeoHHW6enicQ9uSAliwJk # x/Awgga8MIIFpKADAgECAhAD8bThXzqC8RSWeLPX2EdcMA0GCSqGSIb3DQEBCwUA # MGwxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsT # EHd3dy5kaWdpY2VydC5jb20xKzApBgNVBAMTIkRpZ2lDZXJ0IEhpZ2ggQXNzdXJh # bmNlIEVWIFJvb3QgQ0EwHhcNMTIwNDE4MTIwMDAwWhcNMjcwNDE4MTIwMDAwWjBs # MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 # d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBFViBDb2RlIFNpZ25p # bmcgQ0EgKFNIQTIpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp1P6 # D7K1E/Fkz4SA/K6ANdG218ejLKwaLKzxhKw6NRI6kpG6V+TEyfMvqEg8t9Zu3Jci # ulF5Ya9DLw23m7RJMa5EWD6koZanh08jfsNsZSSQVT6hyiN8xULpxHpiRZt93mN0 # y55jJfiEmpqtRU+ufR/IE8t1m8nh4Yr4CwyY9Mo+0EWqeh6lWJM2NL4rLisxWGa0 # MhCfnfBSoe/oPtN28kBa3PpqPRtLrXawjFzuNrqD6jCoTN7xCypYQYiuAImrA9EW # giAiduteVDgSYuHScCTb7R9w0mQJgC3itp3OH/K7IfNs29izGXuKUJ/v7DYKXJq3 # StMIoDl5/d2/PToJJQIDAQABo4IDWDCCA1QwEgYDVR0TAQH/BAgwBgEB/wIBADAO # BgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwfwYIKwYBBQUHAQEE # czBxMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wSQYIKwYB # BQUHMAKGPWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEhpZ2hB # c3N1cmFuY2VFVlJvb3RDQS5jcnQwgY8GA1UdHwSBhzCBhDBAoD6gPIY6aHR0cDov # L2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0SGlnaEFzc3VyYW5jZUVWUm9vdENB # LmNybDBAoD6gPIY6aHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0SGln # aEFzc3VyYW5jZUVWUm9vdENBLmNybDCCAcQGA1UdIASCAbswggG3MIIBswYJYIZI # AYb9bAMCMIIBpDA6BggrBgEFBQcCARYuaHR0cDovL3d3dy5kaWdpY2VydC5jb20v # c3NsLWNwcy1yZXBvc2l0b3J5Lmh0bTCCAWQGCCsGAQUFBwICMIIBVh6CAVIAQQBu # AHkAIAB1AHMAZQAgAG8AZgAgAHQAaABpAHMAIABDAGUAcgB0AGkAZgBpAGMAYQB0 # AGUAIABjAG8AbgBzAHQAaQB0AHUAdABlAHMAIABhAGMAYwBlAHAAdABhAG4AYwBl # ACAAbwBmACAAdABoAGUAIABEAGkAZwBpAEMAZQByAHQAIABDAFAALwBDAFAAUwAg # AGEAbgBkACAAdABoAGUAIABSAGUAbAB5AGkAbgBnACAAUABhAHIAdAB5ACAAQQBn # AHIAZQBlAG0AZQBuAHQAIAB3AGgAaQBjAGgAIABsAGkAbQBpAHQAIABsAGkAYQBi # AGkAbABpAHQAeQAgAGEAbgBkACAAYQByAGUAIABpAG4AYwBvAHIAcABvAHIAYQB0 # AGUAZAAgAGgAZQByAGUAaQBuACAAYgB5ACAAcgBlAGYAZQByAGUAbgBjAGUALjAd # BgNVHQ4EFgQUj+h+8G0yagAFI8dwl2o6kP9r6tQwHwYDVR0jBBgwFoAUsT7DaQP4 # v0cB1JgmGggC72NkK8MwDQYJKoZIhvcNAQELBQADggEBABkzSgyBMzfbrTbJ5Mk6 # u7UbLnqi4vRDQheev06hTeGx2+mB3Z8B8uSI1en+Cf0hwexdgNLw1sFDwv53K9v5 # 15EzzmzVshk75i7WyZNPiECOzeH1fvEPxllWcujrakG9HNVG1XxJymY4FcG/4JFw # d4fcyY0xyQwpojPtjeKHzYmNPxv/1eAal4t82m37qMayOmZrewGzzdimNOwSAauV # WKXEU1eoYObnAhKguSNkok27fIElZCG+z+5CGEOXu6U3Bq9N/yalTWFL7EZBuGXO # uHmeCJYLgYyKO4/HmYyjKm6YbV5hxpa3irlhLZO46w4EQ9f1/qbwYtSZaqXBwfBk # lIAxgg9zMIIPbwIBATCBgDBsMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNl # cnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdp # Q2VydCBFViBDb2RlIFNpZ25pbmcgQ0EgKFNIQTIpAhABpQjzKYtgEwvB4F5wOdBL # MA0GCWCGSAFlAwQCAQUAoHwwEAYKKwYBBAGCNwIBDDECMAAwGQYJKoZIhvcNAQkD # MQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJ # KoZIhvcNAQkEMSIEIB7XobVMwIl1A50bj+O7SQT7bbu2tLiGLuBVxbRbhZlSMA0G # CSqGSIb3DQEBAQUABIIBAJm0Zp/ejCxiZNqMG8rhP5NCB7cUsqIkjPNXV15eEX8b # wc/pmq4hmdRrUpEXwvbO2XuWoh0WIRxJKcmUYKMwsoumZ9WqMqrPKSBucXlAaPld # jj6gpsxmu6TZWu7DZvnRB1K4goEPdiIs/pPKJ2PKnHFkwXk306e0fWK21q2Llk1n # 4KkI1TewDBIN5afvvD8w58ZSnrdIGuQiWkE68BMnNGzKaJhCtR3rKz2FvIhWeoB4 # +j0qqLA0Hup5FQtZryEbxod56I6aND/7NiQsdl9LEa9AoB0I8DVekkBqG5mYcc0Y # Gm6OIW/1/PN5rmBtF+TCql1kJtwT/C8BW/U42jll+Uuhgg1FMIINQQYKKwYBBAGC # NwMDATGCDTEwgg0tBgkqhkiG9w0BBwKggg0eMIINGgIBAzEPMA0GCWCGSAFlAwQC # AQUAMHgGCyqGSIb3DQEJEAEEoGkEZzBlAgEBBglghkgBhv1sBwEwMTANBglghkgB # ZQMEAgEFAAQg0/uzlVoPQ9rzIuXCx3R4iKhLs5xq9s2yanGjmKDDbRoCEQCL9XQ7 # bI2mckdbpIK5iel2GA8yMDIxMDUwNjE4NTEyOFqgggo3MIIE/jCCA+agAwIBAgIQ # DUJK4L46iP9gQCHOFADw3TANBgkqhkiG9w0BAQsFADByMQswCQYDVQQGEwJVUzEV # MBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29t # MTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFzc3VyZWQgSUQgVGltZXN0YW1waW5n # IENBMB4XDTIxMDEwMTAwMDAwMFoXDTMxMDEwNjAwMDAwMFowSDELMAkGA1UEBhMC # VVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMSAwHgYDVQQDExdEaWdpQ2VydCBU # aW1lc3RhbXAgMjAyMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMLm # YYRnxYr1DQikRcpja1HXOhFCvQp1dU2UtAxQtSYQ/h3Ib5FrDJbnGlxI70Tlv5th # zRWRYlq4/2cLnGP9NmqB+in43Stwhd4CGPN4bbx9+cdtCT2+anaH6Yq9+IRdHnbJ # 5MZ2djpT0dHTWjaPxqPhLxs6t2HWc+xObTOKfF1FLUuxUOZBOjdWhtyTI433UCXo # ZObd048vV7WHIOsOjizVI9r0TXhG4wODMSlKXAwxikqMiMX3MFr5FK8VX2xDSQn9 # JiNT9o1j6BqrW7EdMMKbaYK02/xWVLwfoYervnpbCiAvSwnJlaeNsvrWY4tOpXIc # 7p96AXP4Gdb+DUmEvQECAwEAAaOCAbgwggG0MA4GA1UdDwEB/wQEAwIHgDAMBgNV # HRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMEEGA1UdIAQ6MDgwNgYJ # YIZIAYb9bAcBMCkwJwYIKwYBBQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29t # L0NQUzAfBgNVHSMEGDAWgBT0tuEgHf4prtLkYaWyoiWyyBc1bjAdBgNVHQ4EFgQU # NkSGjqS6sGa+vCgtHUQ23eNqerwwcQYDVR0fBGowaDAyoDCgLoYsaHR0cDovL2Ny # bDMuZGlnaWNlcnQuY29tL3NoYTItYXNzdXJlZC10cy5jcmwwMqAwoC6GLGh0dHA6 # Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9zaGEyLWFzc3VyZWQtdHMuY3JsMIGFBggrBgEF # BQcBAQR5MHcwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBP # BggrBgEFBQcwAoZDaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0 # U0hBMkFzc3VyZWRJRFRpbWVzdGFtcGluZ0NBLmNydDANBgkqhkiG9w0BAQsFAAOC # AQEASBzctemaI7znGucgDo5nRv1CclF0CiNHo6uS0iXEcFm+FKDlJ4GlTRQVGQd5 # 8NEEw4bZO73+RAJmTe1ppA/2uHDPYuj1UUp4eTZ6J7fz51Kfk6ftQ55757TdQSKJ # +4eiRgNO/PT+t2R3Y18jUmmDgvoaU+2QzI2hF3MN9PNlOXBL85zWenvaDLw9MtAb # y/Vh/HUIAHa8gQ74wOFcz8QRcucbZEnYIpp1FUL1LTI4gdr0YKK6tFL7XOBhJCVP # st/JKahzQ1HavWPWH1ub9y4bTxMd90oNcX6Xt/Q/hOvB46NJofrOp79Wz7pZdmGJ # X36ntI5nePk2mOHLKNpbh6aKLzCCBTEwggQZoAMCAQICEAqhJdbWMht+QeQF2jaX # whUwDQYJKoZIhvcNAQELBQAwZTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lD # ZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEkMCIGA1UEAxMbRGln # aUNlcnQgQXNzdXJlZCBJRCBSb290IENBMB4XDTE2MDEwNzEyMDAwMFoXDTMxMDEw # NzEyMDAwMFowcjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ # MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hB # MiBBc3N1cmVkIElEIFRpbWVzdGFtcGluZyBDQTCCASIwDQYJKoZIhvcNAQEBBQAD # ggEPADCCAQoCggEBAL3QMu5LzY9/3am6gpnFOVQoV7YjSsQOB0UzURB90Pl9TWh+ # 57ag9I2ziOSXv2MhkJi/E7xX08PhfgjWahQAOPcuHjvuzKb2Mln+X2U/4Jvr40ZH # BhpVfgsnfsCi9aDg3iI/Dv9+lfvzo7oiPhisEeTwmQNtO4V8CdPuXciaC1TjqAlx # a+DPIhAPdc9xck4Krd9AOly3UeGheRTGTSQjMF287DxgaqwvB8z98OpH2YhQXv1m # blZhJymJhFHmgudGUP2UKiyn5HU+upgPhH+fMRTWrdXyZMt7HgXQhBlyF/EXBu89 # zdZN7wZC/aJTKk+FHcQdPK/P2qwQ9d2srOlW/5MCAwEAAaOCAc4wggHKMB0GA1Ud # DgQWBBT0tuEgHf4prtLkYaWyoiWyyBc1bjAfBgNVHSMEGDAWgBRF66Kv9JLLgjEt # UYunpyGd823IDzASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjAT # BgNVHSUEDDAKBggrBgEFBQcDCDB5BggrBgEFBQcBAQRtMGswJAYIKwYBBQUHMAGG # GGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3aHR0cDovL2Nh # Y2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNydDCB # gQYDVR0fBHoweDA6oDigNoY0aHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lD # ZXJ0QXNzdXJlZElEUm9vdENBLmNybDA6oDigNoY0aHR0cDovL2NybDMuZGlnaWNl # cnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDBQBgNVHSAESTBHMDgG # CmCGSAGG/WwAAgQwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQu # Y29tL0NQUzALBglghkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggEBAHGVEulRh1Zp # ze/d2nyqY3qzeM8GN0CE70uEv8rPAwL9xafDDiBCLK938ysfDCFaKrcFNB1qrpn4 # J6JmvwmqYN92pDqTD/iy0dh8GWLoXoIlHsS6HHssIeLWWywUNUMEaLLbdQLgcseY # 1jxk5R9IEBhfiThhTWJGJIdjjJFSLK8pieV4H9YLFKWA1xJHcLN11ZOFk362kmf7 # U2GJqPVrlsD0WGkNfMgBsbkodbeZY4UijGHKeZR+WfyMD+NvtQEmtmyl7odRIeRY # YJu6DC0rbaLEfrvEJStHAgh8Sa4TtuF8QkIoxhhWz0E0tmZdtnR79VYzIi8iNrJL # okqV2PWmjlIxggJNMIICSQIBATCBhjByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMM # RGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQD # EyhEaWdpQ2VydCBTSEEyIEFzc3VyZWQgSUQgVGltZXN0YW1waW5nIENBAhANQkrg # vjqI/2BAIc4UAPDdMA0GCWCGSAFlAwQCAQUAoIGYMBoGCSqGSIb3DQEJAzENBgsq # hkiG9w0BCRABBDAcBgkqhkiG9w0BCQUxDxcNMjEwNTA2MTg1MTI4WjArBgsqhkiG # 9w0BCRACDDEcMBowGDAWBBTh14Ko4ZG+72vKFpG1qrSUpiSb8zAvBgkqhkiG9w0B # CQQxIgQgL//C9mrblnvcybHBUiGUdtlqywSW3QmEU2OomhcpLNEwDQYJKoZIhvcN # AQEBBQAEggEANvirR08qjXJsmKsSIKwLPenzGMg5RBnsxLyuDB3sAKLvLL8ePqSR # 4TvSIUMcKVwn7UsTjiNnQBi1RTOzECKtrTxGE+WLbHo+zvh0qllOqCUqjohZTgMQ # IDzNrcN6deZbVJQXE+5ghJXaGyZNEuJOLbDBacu5IpyWJQpZBuRxqvpmzIfsr8j5 # nz6NcmMN02eiQ+403GgaCeIO4xEHH32hZbfCa6CSPokxq+kcmiEOaQXDnjh1RnBh # ifXIccvMk3TF4kZPOOpnBg8nCQHrC3v4rycJpPmQrfAfcSCSwYQyQL8I6t+c4nUN # j/MkESOnMDQg7jCTjMR+TdTJZOiRlF2znQ== # SIG # End signature block