AzureAppGWMigration.ps1
<#PSScriptInfo .VERSION 1.0.14 .GUID be3b84b4-e9c5-46fb-a050-699c68e16119 .AUTHOR Microsoft Corporation .COMPANYNAME Microsoft Corporation .COPYRIGHT Microsoft Corporation. All rights reserved. .TAGS Azure, Az, ApplicationGateway, AzNetworking .RELEASENOTES 1.0.14 -- Added support to create a WAF policy with default properties and state disabled for WAF V1 gateways not having any WAF configuration 1.0.13 -- Fixed an issue where VMs/VMSS associated with multiple backend pools in V1 gateways were being associated with only one backend pool after migration to V2 gateways. 1.0.12 -- Added support for Availability Zones -- Added support for V1 WAF config to WAF policy 1.0.11 -- Fix Resource Group Deletion Bug -- Script Version Check 1.0.10 -- Signed file with changes of 1.0.9. 1.0.9 -- Added support to provide rule priority for newly created V2 gateway request routing rule. -- Fixed request routing rule ordering bug introduced in 1.0.8. #> <# .SYNOPSIS AppGateway v1 -> v2 migration .DESCRIPTION This script will help you create a V2 sku application gateway with the same configuration as your V1 sku application gateway. .PARAMETER ResourceId Application Gateway ResourceId, like "/subscriptions/<your-subscriptionId>/resourceGroups/<v1-app-gw-rgname>/providers/Microsoft.Network/applicationGateways/<v1-app-gw-name>" .PARAMETER SubnetAddressRange The subnet address in CIDR notation, where you want to deploy v2 application gateway (Make sure the subnet is empty or contains only application gateway standard_v2/waf_v2 sku resources). .PARAMETER AppGwName Name of v2 app gateway, default will be <v1-app-gw-name>_v2 .PARAMETER AppGwResourceGroupName Name of resource group where you want v2 application gateway resources to be created (default value will be <v1-app-gw-rgname>) .PARAMETER SslCertificates Comma seperated list of Ssl certificate to be attached to app gateway listeners (set using New-AzApplicationGatewaySSLCertificate command). Note: Passing reference to all ssl certs used in v1 gateway is required to get same configuration in v2 app gateway .PARAMETER TrustedRootCertificates Comma seperated list of trusted root certificates (set using New-AzApplicationGatewayTrustedRootCertificate command). For more details refer https://aka.ms/appgwmigrationdoc .PARAMETER PrivateIpAddress Private Ip address to be assigned to v2 app gateway. .PARAMETER ValidateMigration Post migration validation by comparing ApplicationGatewayBackendHealth response. .PARAMETER PublicIpResourceId Public Ip Address resourceId (if already exists) can be attached to application gateway. If no input is given script will create a public ip resource for you in the same resource group .PARAMETER EnableAutoscale Enable autoscale configuration for app gateway v2 instances .PARAMETER Zones List of availability zones where the application gateway instances should be deployed. .PARAMETER WafPolicyName Name of the waf policy, that will be created from WAF V1 Configuration and will be attached to WAF v2 gateway. .EXAMPLE $password = ConvertTo-SecureString <your-password> -AsPlainText -Force $mySslCert1 = New-AzApplicationGatewaySslCertificate -Name "Cert01" -CertificateFile <Cert-File-Path> -Password $password $mySslCert2 = New-AzApplicationGatewaySslCertificate -Name "Cert02" -CertificateFile <Cert-File-Path> -Password $password .\migration.ps1 -ResourceId "/subscriptions/<your-sub-id>/resourceGroups/<your-rg>/providers/Microsoft.Network/applicationGateways/<v1AppGatewayName>" -SubnetAddressRange <CIDR like 10.0.3.0/24> -sslCert $mySslCert1,$mySslCert2 .INPUTS String Microsoft.Azure.Commands.Network.Models.PSApplicationGatewaySslCertificate[] Microsoft.Azure.Commands.Network.Models.PSApplicationGatewayTrustedRootCertificate[] .OUTPUTS PSApplicationGateway .LINK https://aka.ms/appgwmigrationdoc https://docs.microsoft.com/en-us/azure/application-gateway/ https://docs.microsoft.com/en-us/azure/application-gateway/ssl-overview#end-to-end-ssl-with-the-v2-sku .NOTES Note - Passing reference to all ssl certs used in v1 gateway is required to get same configuration in v2 app gateway #> #Requires -Module Az.Network #Requires -Module Az.Compute #Requires -Module Az.Resources Param([Parameter(Mandatory = $True)][string] $ResourceId, [Parameter(Mandatory = $True)][string] $SubnetAddressRange, [string] $AppGwName, [string] $AppGwResourceGroupName, [Microsoft.Azure.Commands.Network.Models.PSApplicationGatewaySslCertificate[]] $SslCertificates, [Microsoft.Azure.Commands.Network.Models.PSApplicationGatewayTrustedRootCertificate[]] $TrustedRootCertificates, [string] $PublicIpResourceId, [string] $PrivateIpAddress, [switch] $ValidateMigration, [switch] $EnableAutoscale, [string[]] $Zones, [string] $WafPolicyName ) if (!(Get-Module -ListAvailable -Name Az.Network)) { Write-Error ("You need 'Az' module to proceed. Az is a new cross-platform PowerShell module that will replace AzureRM. You can install this module by running 'Install-Module Az' in an elevated PowerShell prompt.") Write-Warning ("If you see error 'AzureRM.Profile already loaded. Az and AzureRM modules cannot be imported in the same session', You would need to close the current session and start new one.") exit } Function Private:ScriptVersionCheck() { $InstalledScriptVersion = (Get-InstalledScript -Name 'AzureAppGWMigration').Version $LatestScriptVersion = (Find-Script -Name 'AzureAppGWMigration').Version if(!$InstalledScriptVersion) { Write-Warning("You have manually downloaded the migration script. The stable version of this script is $LatestScriptVersion, which contains critical fixes and bugs that may not be present in the version you have installed. It is recommended to use the stable version. You can find more information about the currently installed version and how to download the stable version at https://aka.ms/migrationscriptdownload.") $confirmation = Read-Host "Are you Sure You Want To Proceed? Press 'y' for continue, any other key for existing" if ($confirmation -ne 'y') { exit; } } else { if($InstalledScriptVersion -ne $LatestScriptVersion) { Write-Warning("You have installed the migration script version : $InstalledScriptVersion. It is recommended to use the stable version of the script : $LatestScriptVersion. This version contains critical bug fixes that may not be present in the version you are currently using. You can install the stable version by running 'UnInstall-Script -Name 'AzureAppGWMigration' -Force; Install-Script -Name 'AzureAppGWMigration' -RequiredVersion $LatestScriptVersion -Force'") $confirmation = Read-Host "Are you Sure You Want To Proceed? Press 'y' for continue, any other key for existing" if ($confirmation -ne 'y') { exit; } } } } ScriptVersionCheck $sw = [Diagnostics.Stopwatch]::StartNew() #Validating resourceId $matchResponse = $resourceId -match "/subscriptions/(.*?)/resourceGroups/" if(!$matchResponse) { Write-Warning("Invalid ResourceId format $resourceId.") exit } #Validating set-context succeess $subscription = $matches[1] $context = Set-AzContext -Subscription $subscription -ErrorVariable contextFailure if ($contextFailure) { Write-Warning("Unable to set subscription $subscription in context. Please retry again") exit } $resource = Get-AzResource -ResourceId $resourceId -ErrorVariable getResourceFailure # Validating Get-Resource if($getResourceFailure -or !$resource) { Write-Warning("Unable to get resource for $resourceId. Please retry again") exit } $resourcegroup = $resource.ResourceGroupName $location = $resource.Location $V1AppGwName = $resource.Name $appendString = "_v2" $existingResourceIdFormat = "/resourceGroups/$resourcegroup/providers/Microsoft.Network/applicationGateways/$V1AppGwName/" $newResourceIdFormat = "/resourceGroups/ResourceGroupNotSet/providers/Microsoft.Network/applicationGateways/ApplicationGatewayNameNotSet/" $dict = @{} $migrationCompleted = $false $isNewSubnetCreated = $false $isNewIPCreated = $false $isWafPolicyCreated = $false $isNewResourceGroupCreated = $false $pip = $null if ( !$AppGwName ) { $AppGwName = $V1AppGwName + $appendString } if ( !$AppGwResourceGroupName ) { $AppGwResourceGroupName = $resourcegroup } else { # Create resource group if doesn't exist Get-AzResourceGroup -Name $AppGwResourceGroupName -ErrorVariable notPresent -ErrorAction SilentlyContinue if ($notPresent) { $isNewResourceGroupCreated = $true New-AzResourceGroup -Name $AppGwResourceGroupName -Location $location } } $AppGw = Get-AzApplicationGateway -Name $V1AppGwName -ResourceGroupName $resourcegroup -ErrorVariable getAppGwResourceFailure # Validating Get-AppGwResource Failure if($getAppGwResourceFailure -or !$AppGw) { Write-Warning("Unable to get application gateway resource for $resourceId. Please retry again") exit } if ($AppGw.ProvisioningState -eq "Failed") { Write-Warning ("Application gateway with provisioning state 'Failed' may result in V2 Application Gateway with failed state") } Write-Host "Creating Name:$AppGwName app gateway . . ." # cleanup resources Function Private:Cleanup() { if ($newAppGw) { Remove-AzApplicationGateway -Name $newAppGw.Name -ResourceGroupName $AppGwResourceGroupName -Force } if ($isNewIPCreated) { Write-Host ("Removing IP $PublicIpResourceName") Remove-AzPublicIpAddress -Name $PublicIpResourceName -ResourceGroupName $AppGwResourceGroupName -Force -ErrorAction SilentlyContinue } if ($isWafPolicyCreated) { Write-Host ("Removing WAF Policy $WafPolicyName") Remove-AzApplicationGatewayFirewallPolicy -Name $WafPolicyName -ResourceGroupName $AppGwResourceGroupName -Force -ErrorAction SilentlyContinue } if($isNewResourceGroupCreated) { Write-Host ("ResourceGroup $AppGwResourceGroupName is not deleted. Please clean up the resource group after verifying that resources inside resouce group are not used or not needed.") } if ($isNewSubnetCreated) { Write-Host ("Removing subnet $subnetname") $vnet = Remove-AzVirtualNetworkSubnetConfig -Name $subnetname -VirtualNetwork $vnet | Set-AzVirtualNetwork } Write-Host ("Resource Cleanup Finished") exit } Function Private:GetPrivateFrontendIp() { if (!$PrivateIpAddress) { $SubnetStartAddress = [ipaddress]$SubnetAddressRange.Split("/")[0] # select an ip address beyond reserved Ip address range $SubnetSize = [int][math]::pow( 2, (32 - [int]$SubnetAddressRange.Split("/")[1])) $AddressOffset = (Get-Random -Minimum 4 -Maximum ($SubnetSize - 2)) $IpAddressRangeToAdd = [ipaddress]"$AddressOffset" return New-Object System.Net.IPAddress($SubnetStartAddress.Address + $IpAddressRangeToAdd.Address) } else { return [ipaddress]$PrivateIPAddress } } Function Private:ValidateInput() { if (!$appgw -or !($appgw.sku.Tier -in "Standard","WAF")) { Write-Warning("Could not detect any V1 ('Standard' or 'WAF') resource as per your input parameters. Please double check input parameters.") exit } $Listeners = Get-AzApplicationGatewayHttpListener -ApplicationGateway $Appgw # ssl cert is necessary if you have 'https' enabled listeners in app gateway if (($Listeners | Where-Object { $_.Protocol -match "https" }).count -GT 0 -and (($null -EQ $SslCertificates) -or ($SslCertificates.count -EQ 0)) ) { Write-Warning ("Providing '-SslCertificates <cert>' is mandatory if you have 'https' listeners in your V1 ('Standard' or 'WAF') resource.") exit } if ($SslCertificates) { $SslCertificates | ForEach-Object { if (!$_ -or ($_.GetType() -NE (New-Object -TypeName Microsoft.Azure.Commands.Network.Models.PSApplicationGatewaySslCertificate).GetType())) { Write-Error ("Invalid input - 'SslCertificates'. Expected object of type : 'Microsoft.Azure.Commands.Network.Models.PSApplicationGatewaySslCertificate' ") exit } else { $_.Id = $_.Id -replace "/resourceGroups/.*/sslCertificates/",($newResourceIdFormat+"sslCertificates/") } } } if (!$TrustedRootCertificates -or ($TrustedRootCertificates.count -EQ 0)) { $TrustedRootCertificates = (New-Object System.Collections.Generic.List[Microsoft.Azure.Commands.Network.Models.PSApplicationGatewayTrustedRootCertificate]) } else { $TrustedRootCertificates | ForEach-Object { if (!$_ -or ($_.GetType() -NE (New-Object -TypeName Microsoft.Azure.Commands.Network.Models.PSApplicationGatewayTrustedRootCertificate).GetType())) { Write-Error ("Invalid input - 'TrustedRootCertificates'. Expected object of type : 'Microsoft.Azure.Commands.Network.Models.PSApplicationGatewayTrustedRootCertificate' ") exit } else { $_.Id = $_.Id -replace "/resourceGroups/.*/trustedRootCertificates/",($newResourceIdFormat+"trustedRootCertificates/") } } } if($Zones) { #Case when region do not support any zones if(($Zones.Length -gt 0 ) -and ($AvailabilityZones.Length -eq 0)) { Write-Error "Region do not support availabilty Zones, Invalid Values provided for optional parameter Zones" exit } else { foreach ($zone in $Zones) { if ($AvailabilityZones -notcontains $zone) { Write-Error "Invalid Zones specified: $Zones. Valid Zones are: $AvailabilityZones" exit } } } } #check if gateway WebApplicationFirewallConfiguration is having owasp rule set version 2.2.9 if ($appgw.WebApplicationFirewallConfiguration) { if ($appgw.WebApplicationFirewallConfiguration.RuleSetType -eq "OWASP" -and $appgw.WebApplicationFirewallConfiguration.RuleSetVersion -eq "2.2.9") { Write-Error ("The WAF V1 gateway you're attempting to migrate currently uses CRS version 2.2.9, which is no longer supported. To proceed with the migration, upgrade your WAF gateway to CRS version 3.0 or later.") exit } } } Function Private:GetApplicationGatewaySku($gwSkuTier) { if ($gwSkuTier -EQ "Standard") { return New-AzApplicationGatewaySku -Name Standard_v2 -Tier Standard_v2 } else { return New-AzApplicationGatewaySku -Name WAF_v2 -Tier WAF_v2 } } Function Private:GetCapacityUnits($AppgwSku) { # Min/Max Max Capacity for Autoscale $MinMaxCapacity = 2 $MaxMaxCapacity = 125 $MinCapacity = 1 $MaxCapacity = 2 switch($AppgwSku.Name) { {$_ -in "Standard_Small"} { $MinCapacity = [math]::floor($AppgwSku.Capacity/2); $MaxCapacity = $AppgwSku.Capacity; } {$_ -in "WAF_Medium","Standard_Medium"} { $MinCapacity = $AppgwSku.Capacity; $MaxCapacity = [math]::ceiling(1.5*$AppgwSku.Capacity); } {$_ -in "WAF_Large","Standard_Large"} { $MinCapacity = $AppgwSku.Capacity; $MaxCapacity = [math]::ceiling(2.5*$AppgwSku.Capacity); } {$_ -in "Standard_Small_V2"} { $MinCapacity = $AppgwSku.Capacity; $MaxCapacity = [math]::ceiling(1.5*$AppgwSku.Capacity); } {$_ -in "WAF_Medium_V2","Standard_Medium_V2"} { $MinCapacity = $AppgwSku.Capacity; $MaxCapacity = [math]::ceiling(2.5*$AppgwSku.Capacity); } {$_ -in "WAF_Large_V2","Standard_Large_V2"} { $MinCapacity = $AppgwSku.Capacity; $MaxCapacity = [math]::ceiling(4*$ppgwSku.Capacity); } default { $MinCapacity = $AppgwSku.Capacity; $MaxCapacity = [math]::ceiling(1.5*$AppgwSku.Capacity); } } if ($MaxCapacity -GT $MaxMaxCapacity) { Write-Warning ("Your current V1 ('Standard' or 'WAF') has a large number of instances that exceed the limit for provisioning equivalently scaled V2 instances using our V1->V2 SKU conversion factors. Please consider reducing the number of instances for your V1 Application Gateway/WAF resource, or contact Azure Support to increase your subscription limits.") exit } elseif ($MaxCapacity -LT $MinMaxCapacity) { $MaxCapacity = $MinMaxCapacity } return $MinCapacity, $MaxCapacity } Function Private:IsSslCertificateMatch($newSslCert, $existingSslCert) { $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 ([System.Convert]::FromBase64String($newSslCert.Data),$newSslCert.Password,4) $certCollection = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection $certCollection.Import([System.Convert]::FromBase64String($existingSslCert.PublicCertData)) if (($certCollection | Where-Object { $_.Equals($cert) }).Count -GT 0) { return $true } else { return $false } } $AttachVmNetworkInterface = { param($nicName, $rgname, $BackendPoolsToAdd) $nic = Get-AzNetworkInterface -Name $nicName -ResourceGroupName $rgname -ErrorAction SilentlyContinue if ($nic) { $nicipconfig = Get-AzNetworkInterfaceIpConfig -NetworkInterface $nic | Where-Object { $_.Primary -eq $True } foreach($backendPool in $BackendPoolsToAdd){ $BackendPoolToAdd = New-AzApplicationGatewayBackendAddressPool -Name $backendPool.Name $BackendPoolToAdd.Id = $backendPool.id $nicipconfig | ForEach-Object { if(!$_.ApplicationGatewayBackendAddressPools.id.Contains($BackendPoolToAdd.id)) { $_.ApplicationGatewayBackendAddressPools.Add($BackendPoolToAdd) } } } $retryCount = 0 do { Start-Sleep -s ($retryCount*5) $newnic = Set-AzNetworkInterface -NetworkInterface $nic $retryCount++ }while(($retryCount -LT 3) -and !$newnic) if($newnic) { Write-Host("VM Nic '$($nicName)' was added to backend pool.") return $true } } Write-Error("VM Nic '$($nicName)' could not be successfully added to the backend pool. Please retry the script after some time") return $false } $AttachVmssNetworkInterface = { param($vmssName, $nicList, $rgname, $Instances) $Instances = $Instances | Select-Object -Unique $vmss = Get-AzVmss -VMScaleSetName $vmssName -ResourceGroupName $rgname foreach ($nic in $nicList.Keys) { $backendPoolsToAdd = $nicList[$nic] $nicipconfig = $vmss.VirtualMachineProfile.NetworkProfile.NetworkInterfaceConfigurations ` | Where-Object { $_.Name -eq $nic } ` | Select-Object -ExpandProperty IpConfigurations | Where-Object { $_.Primary -eq $True } $nicipconfig | ForEach-Object { foreach ($backendPool in $backendPoolsToAdd) { if (!$_.ApplicationGatewayBackendAddressPools.id.Contains($backendPool.id)) { $_.ApplicationGatewayBackendAddressPools.Add($backendPool.id) } } } } Update-AzVmss -VirtualMachineScaleSet $vmss -Name $vmssName -ResourceGroupName $rgname -ErrorVariable errorDetails if ($errorDetails) { Write-Error ("Failed to migrate backend pool(s) associated to vmss '$($vmssName)'") return $false } Write-Host("Virtual machine scale set '$($vmssName)' was added to backend pool.") if (!$errorDetails -and ($vmss.UpgradePolicy.Mode -EQ "Manual") ) { Write-Host ("Upgrading all the instances of '$($vmssName)' for this change to work.") foreach($instance in $Instances){ $updateStatus = Update-AzVmssInstance -ResourceGroupName $rgname -VMScaleSetName $vmssName -InstanceId $instance if($updateStatus.Error) { Write-Warning "Failed to update instance : ", $instance, "in vmss : ", $vmssName, ". Users will need to manually upgrade their vmss instances, or as per their vmss upgrade policy" } } return $true } return $true } Function Private:GetAvailabilityZoneMappings { try { $response = Invoke-AzRestMethod -Method GET -Path "/subscriptions/$subscription/Providers/Microsoft.Compute?api-version=2022-12-01" # Check if the response contains an error if ($response.StatusCode -ne 200) { Write-Error "Failed to retrieve availability zone mappings, statuscode is $($response.StatusCode). Please try again later." exit } else { $data = ($response.Content | ConvertFrom-Json) # Get zoneMappings for virtualMachineScaleSets $zoneMappings = $data.resourceTypes | Where-Object { $_.resourceType -eq "virtualMachineScaleSets" } | Select-Object -ExpandProperty zoneMappings $zoneMappingForLocation = $zoneMappings | Where-Object { $_.location.Replace(' ','') -eq $location } $logicalZones = $zoneMappingForLocation.zones return $logicalZones } } catch { Write-Error "Failed to retrieve availability zone mappings. Please try again later." exit } } Function GetMatchingApplicationGatewayWAFRuleSet { param ( [array]$availableWAFRuleSets, [string]$appgwRuleSetType, [string]$appgwRuleSetVersion ) $matchingRuleSet = $availableWAFRuleSets.Value | Where-Object { $_.RuleSetType -eq $appgwRuleSetType -and $_.RuleSetVersion -eq $appgwRuleSetVersion } return $matchingRuleSet } Function GetRuleGroupRuleIdsFromWAFRuleSet { param ( [object]$wafRuleSet, [string]$ruleGroupName ) $matchingRuleGroup = $wafRuleSet.RuleGroups | Where-Object { $_.RuleGroupName -eq $ruleGroupName } return $matchingRuleGroup.Rules.RuleId } Function Private:CreateNewWAFPolicy () { #used in case of disabled RuleGroup case $avalilableWAFRuleSets = Get-AzApplicationGatewayAvailableWafRuleSets $appgwRuleSetType = $appgw.WebApplicationFirewallConfiguration.RuleSetType $appgwRuleSetVersion = $appgw.WebApplicationFirewallConfiguration.RuleSetVersion $ruleSet = GetMatchingApplicationGatewayWAFRuleSet $avalilableWAFRuleSets $appgwRuleSetType $appgwRuleSetVersion # Get the managedRule and PolicySettings $managedRule = New-AzApplicationGatewayFirewallPolicyManagedRule $policySetting = New-AzApplicationGatewayFirewallPolicySetting if ($appgw.WebApplicationFirewallConfiguration) { $ruleGroupOverrides = [System.Collections.ArrayList]@() if ($appgw.WebApplicationFirewallConfiguration.DisabledRuleGroups) { foreach ($disabled in $appgw.WebApplicationFirewallConfiguration.DisabledRuleGroups) { $rules = [System.Collections.ArrayList]@() if ($disabled.Rules.Count -gt 0) { foreach ($rule in $disabled.Rules) { $ruleOverride = New-AzApplicationGatewayFirewallPolicyManagedRuleOverride -RuleId $rule $_ = $rules.Add($ruleOverride) } } #Disabled RuleGroup case elseif ($disabled.Rules.Count -eq 0) { $disabledRuleGroupRuleId = GetRuleGroupRuleIdsFromWAFRuleSet $ruleSet $disabled.RuleGroupName foreach ($ruleId in $disabledRuleGroupRuleId) { $ruleOverride = New-AzApplicationGatewayFirewallPolicyManagedRuleOverride -RuleId $ruleId $_ = $rules.Add($ruleOverride) } } $ruleGroupOverride = New-AzApplicationGatewayFirewallPolicyManagedRuleGroupOverride -RuleGroupName $disabled.RuleGroupName -Rule $rules $_ = $ruleGroupOverrides.Add($ruleGroupOverride) } } $managedRuleSet = New-AzApplicationGatewayFirewallPolicyManagedRuleSet -RuleSetType $appgw.WebApplicationFirewallConfiguration.RuleSetType -RuleSetVersion $appgw.WebApplicationFirewallConfiguration.RuleSetVersion if ($ruleGroupOverrides.Count -ne 0) { $managedRuleSet = New-AzApplicationGatewayFirewallPolicyManagedRuleSet -RuleSetType $appgw.WebApplicationFirewallConfiguration.RuleSetType -RuleSetVersion $appgw.WebApplicationFirewallConfiguration.RuleSetVersion -RuleGroupOverride $ruleGroupOverrides } $exclusions = [System.Collections.ArrayList]@() if ($appgw.WebApplicationFirewallConfiguration.Exclusions) { foreach ($excl in $appgw.WebApplicationFirewallConfiguration.Exclusions) { if ($excl.MatchVariable -and $excl.SelectorMatchOperator -and $excl.Selector) { $exclusionEntry = New-AzApplicationGatewayFirewallPolicyExclusion -MatchVariable $excl.MatchVariable -SelectorMatchOperator $excl.SelectorMatchOperator -Selector $excl.Selector $_ = $exclusions.Add($exclusionEntry) } if ($excl.MatchVariable -and !$excl.SelectorMatchOperator -and !$excl.Selector) { # Equals Any exclusion $exclusionEntry = New-AzApplicationGatewayFirewallPolicyExclusion -MatchVariable $excl.MatchVariable -SelectorMatchOperator "EqualsAny" -Selector "*" $_ = $exclusions.Add($exclusionEntry) } } } $managedRule = New-AzApplicationGatewayFirewallPolicyManagedRule -ManagedRuleSet $managedRuleSet $exclCount = $exclusions.Count if ($exclCount -ne 0) { $managedRule = New-AzApplicationGatewayFirewallPolicyManagedRule -ManagedRuleSet $managedRuleSet -Exclusion $exclusions } $policySetting = New-AzApplicationGatewayFirewallPolicySetting -MaxFileUploadInMb $appgw.WebApplicationFirewallConfiguration.FileUploadLimitInMb -MaxRequestBodySizeInKb $appgw.WebApplicationFirewallConfiguration.MaxRequestBodySizeInKb -Mode Detection -State Disabled if ($appgw.WebApplicationFirewallConfiguration.FirewallMode -eq "Prevention") { $policySetting.Mode = "Prevention" } if ($appgw.WebApplicationFirewallConfiguration.Enabled) { $policySetting.State = "Enabled" } $policySetting.RequestBodyCheck = $appgw.WebApplicationFirewallConfiguration.RequestBodyCheck; } else { Write-Warning "This Application Gateway V1 does not have a Web Application Firewall (WAF) configuration attached. A WAF policy with default properties will be created and attached to the V2 application gateway, and its state will be set to disabled." $policySetting = New-AzApplicationGatewayFirewallPolicySetting -MaxFileUploadInMb 100 -MaxRequestBodySizeInKb 128 -Mode Detection -State Disabled $policySetting.RequestBodyCheck = $true $policySetting.RequestBodyInspectLimitInKB = 128 $policySetting.FileUploadEnforcement = $true $policySetting.RequestBodyEnforcement = $true $managedRuleSet = New-AzApplicationGatewayFirewallPolicyManagedRuleSet -RuleSetType "Microsoft_DefaultRuleSet" -RuleSetVersion "2.1" $managedRule = New-AzApplicationGatewayFirewallPolicyManagedRule -ManagedRuleSet $managedRuleSet } $wafPolicy = New-AzApplicationGatewayFirewallPolicy -Name $wafPolicyName -ResourceGroupName $AppGwResourceGroupName -PolicySetting $policySetting -ManagedRule $managedRule -Location $appgw.Location if (!$wafPolicy) { exit; } Write-Host "firewallPolicy: $wafPolicyName has been created successfully" return $wafPolicy } # Get Availability Zones for the region for validating the Zones parameter $AvailabilityZones = GetAvailabilityZoneMappings ValidateInput #If Zones parameter is NULL or empty, appgw would be deployed in all the availability zones supported by the region if ($Zones -and $Zones.Length -gt 0) { $appgwDeploymentZones = $Zones } else { $appgwDeploymentZones = $AvailabilityZones } # get public ip deployment zones if ($appgwDeploymentZones.Length -eq 1) { $publicIpDeploymentZones = $appgwDeploymentZones } elseif ($appgwDeploymentZones.Length -gt 1) { $publicIpDeploymentZones = $AvailabilityZones } try{ #define sku & autoscale $sku = GetApplicationGatewaySku($AppGw.Sku.Tier) $capacity = GetCapacityUnits($AppGw.Sku) if ($enableAutoscale) { $autoscaleConfig = New-AzApplicationGatewayAutoscaleConfiguration -MinCapacity $capacity[0] -MaxCapacity $capacity[1] } else { $sku.Capacity = $capacity[1] } # create subnet with appropiate nsg $GatewayConfig = Get-AzApplicationGatewayIPConfiguration -ApplicationGateway $AppGw $matchResponse = $GatewayConfig.subnet.id -match "/resourceGroups/(.*?)/.*/virtualNetworks/(.*?)/subnets/(.*)" $vnetname = $matches[2] $vnet = Get-AzvirtualNetwork -Name $vnetname -ResourceGroupName $matches[1] if(!$vnet) { Write-Warning ("Vnet $vnetname associated with $resourceId is not found. This is not expected. Please try again later.") return } $V1Subnet = Get-AzVirtualNetworkSubnetConfig -Name $matches[3] -VirtualNetwork $vnet if(!$V1Subnet) { Write-Warning ("Subnet associated with $resourceId is not found. This is not expected. Please try again later.") return } $agv2Subnet = Get-AzVirtualNetworkSubnetConfig -VirtualNetwork $vnet | Where-Object { $_.AddressPrefix -Match $SubnetAddressRange } if( $null -eq $agv2Subnet ) { $subnetname = $AppGwName + "Subnet" $vnet = Add-AzVirtualNetworkSubnetConfig -Name $subnetname -AddressPrefix $SubnetAddressRange -VirtualNetwork $vnet -NetworkSecurityGroupId $V1Subnet.NetworkSecurityGroup.Id $vnet = Set-AzVirtualNetwork -VirtualNetwork $vnet if (!$vnet) { Write-Warning ("Please check if you have provided the correct SubnetAddressRange") return } $agv2Subnet = Get-AzVirtualNetworkSubnetConfig -Name $subnetname -VirtualNetwork $vnet $isNewSubnetCreated = $true Write-Host ("Created Subnet $($agv2Subnet.Name) for V2 Application Gateway / WAF. Address Prefix : $SubnetAddressRange") } if (!$agv2Subnet) { Write-Warning ("Failed to create Subnet. This might happen if VNet resource is in failed state. Please correct that and retry execution") return } else { Write-Host ("Using Subnet: $($agv2Subnet.Name)") } # Create FrontendIpConfig if ($PublicIpResourceId) { $PublicIpResource = Get-AzResource -ResourceId $PublicIpResourceId -ErrorAction SilentlyContinue if($PublicIpResource) { $PublicIpResourceName = $PublicIpResource.Name $matchResponse = $PublicIpResourceId -match "/resourceGroups/(.*?)/providers" $pip = Get-AzPublicIpAddress -Name $PublicIpResourceName -ResourceGroupName $matches[1] -ErrorAction SilentlyContinue if(!$pip) { Write-Warning ("Failed to get Public Ip Resource with name $PublicIpResourceName. Please ensure that provided Public Ip resource exists") return } } else { Write-Warning ("Failed to get Public Ip Resource with Id $PublicIpResourceId. Please ensure that provided Public Ip resource exists") return } } if ( $null -eq $pip ) { $PublicIpResourceName = $AppGwName + "-IP" #Verify that public IP doesn't exist $getPip = Get-AzPublicIpAddress -ResourceGroupName $AppGwResourceGroupName -name $PublicIpResourceName if($getPip) { Write-Warning ("Public Ip Resource with Id $($getPip.Id) already exists. Please try again after deleting this public IP or providing an explicit public Ip Resource using PublicIpResourceId parameter") return } $pip = New-AzPublicIpAddress -ResourceGroupName $AppGwResourceGroupName -name $PublicIpResourceName -location $location -AllocationMethod "Static" -Sku Standard -Zone $publicIpDeploymentZones -Force $isNewIPCreated = $true } $fip = New-Object System.Collections.Generic.List[Microsoft.Azure.Commands.Network.Models.PSApplicationGatewayFrontendIPConfiguration] $fp = (Get-AzApplicationGatewayFrontendIPConfig -ApplicationGateway $AppGw | Where-Object { $_.PublicIPAddress -NE $null }) if ($fp) { if ($fp.count -NE 1) { Write-Error ("Multiple Public FrontendIP are not supported for AppGw v2.") exit } $fipName = $fp.Name } else { $fipName = $AppGwName + "PublicFrontendIPConfig" } # Compulsary create public frontend ip config in case of v2 $fip.Add((New-AzApplicationGatewayFrontendIPConfig -Name $fipName -PublicIPAddress $pip)) $fp | ForEach-Object { $dict[$_.Id] = $fip[0] } # Create private frontend ip config only if it is present in v1 also $fp = (Get-AzApplicationGatewayFrontendIPConfig -ApplicationGateway $AppGw | Where-Object { $_.PublicIPAddress -EQ $null }) if ($fp) { $fip.Add((New-AzApplicationGatewayFrontendIPConfig -Name $fp.Name -PrivateIPAddress $(GetPrivateFrontendIp).IPAddressToString -Subnet $agv2Subnet)) $dict[$fp.Id] = $fip[1] } if (!$fip) { Write-Warning ("Failed to create FrontendIpConfig. This should not have happened ideally. Please retry execution after sometime.") return } else { Write-Host ("Created FrontendIpConfiguration") } # Create Frontend ports $FrontEndPorts = Get-AzApplicationGatewayFrontendPort -ApplicationGateway $AppGw $FrontEndPorts | ForEach-Object {$dict[$_.Id] = $_;$_.Id = $_.Id.Replace($existingResourceIdFormat,$newResourceIdFormat); } # Create gatewayIpConfig $GatewayConfig = Get-AzApplicationGatewayIPConfiguration -ApplicationGateway $AppGw $gwIPconfig = New-AzApplicationGatewayIPConfiguration -Name $GatewayConfig.Name -Subnet $agv2Subnet if (!$gwIPconfig) { Write-Warning ("Failed to create GatewayIpConfig. This should not have happened ideally. Please retry execution after sometime.") return } else { Write-Host ("Created GatewayIpConfiguration") } # Create probes $probes = Get-AzApplicationGatewayProbeConfig -ApplicationGateway $appgw $probes | ForEach-Object { $dict[$_.Id] = $_; $_.Id = $_.Id.Replace($existingResourceIdFormat,$newResourceIdFormat); } Write-Host ("Created Health Probes") # Create BackendPools $BackendPools = Get-AzApplicationGatewayBackendAddressPool -ApplicationGateway $AppGw $BackendPools | ForEach-Object {$dict[$_.Id] = $_; $_.Id = $_.Id.Replace($existingResourceIdFormat,$newResourceIdFormat); } Write-Host ("Created Backend Pool") # Backend http settings $SettingsList = Get-AzApplicationGatewayBackendHttpSetting -ApplicationGateway $AppGw $SettingsList | ForEach-Object { $_.AuthenticationCertificates = $null if ($_.Protocol -EQ "https") { $_.TrustedRootCertificates = $TrustedRootCertificates if (($TrustedRootCertificates.Count -GT 0) -and !$_.HostName -and ($_.PickHostNameFromBackendAddress -EQ $False)) { Write-Warning ("For V2 sku, if trusted root cert is provided, ensure that either pickhostnamefrombackendaddress or hostname is provided") $hostname = Read-Host -Prompt 'Please Input Hostname for $_.Name BackendHttpSetting' $_.HostName = $hostname } } if($_.Probe -and $dict.ContainsKey($_.Probe.Id)) { $_.Probe = $dict[$_.Probe.Id]; } $dict[$_.Id] = $_; $_.Id = $_.Id.Replace($existingResourceIdFormat,$newResourceIdFormat); } Write-Host ("Created Backend HttpSettings") # Ssl Certs $existingSslCertificates = Get-AzApplicationGatewaySslCertificate -ApplicationGateway $appgw foreach ($certA in $existingSslCertificates) { $flag = $false; $dict[$certA.Id] = $SslCertificates[0].Id foreach($certB in $SslCertificates) { if(IsSslCertificateMatch $certB $certA) { $dict[$certA.Id] = $certB.id $flag = $true break } } if($flag -eq $false) { Write-Warning ("No ssl certificate provided for '$($certA.Name)'. Please ensure all SSL certificates used in V1 ('Standard' or 'WAF') resource are included.") } } # Create Listeners $v2listener = New-Object System.Collections.Generic.List[Microsoft.Azure.Commands.Network.Models.PSApplicationGatewayHttpListener] $Listeners = Get-AzApplicationGatewayHttpListener -ApplicationGateway $Appgw $Listeners | ForEach-Object { $command = "New-AzApplicationGatewayHttpListener -Name $($_.Name) -Protocol $($_.Protocol) -FrontendPortId $($dict[$_.FrontendPort.Id].id) -FrontendIpConfigurationId $($dict[$_.FrontendIpConfiguration.Id].id) -RequireServerNameIndication $($_.RequireServerNameIndication) ";` if ($_.HostName) { $command += " -Hostname $($_.HostName)" } if ($_.Protocol -EQ "https") { if ($dict.ContainsKey($_.SslCertificate.Id)) { $command = $command + " -SslCertificateId $($dict[$_.SslCertificate.Id])" } else { $command = $command + " -SslCertificateId $($SslCertificates[0].Id)" } } $z = Invoke-Expression $command; if ($z) { $customError = Get-AzApplicationGatewayHttpListenerCustomError -HttpListener $_ if ($customError) { $z.CustomErrorConfigurations = $customError } $v2listener.Add($z); $dict[$_.id] = $z; } } if ($v2listener.count -NE $listeners.count ) { Write-Warning ("Failed to create Listeners. Please check you have given correct inputs and retry.") return } else { Write-Host ("Created Listeners") } # RedirectionConfig $RedirectConfig = Get-AzApplicationGatewayRedirectConfiguration -ApplicationGateway $AppGw; $RedirectConfig | ForEach-Object { if ($_.TargetListener) { $_.TargetListener.Id = $dict[$_.TargetListener.Id].id } $dict[$_.id] = $_; $_.Id = $_.Id.Replace($existingResourceIdFormat,$newResourceIdFormat); } # Url path maps $urlpath = Get-AzApplicationGatewayUrlPathMapConfig -ApplicationGateway $appgw $urlpath | ForEach-Object { $_.PathRules | ForEach-Object { if ($_.BackendAddressPool) { $_.BackendAddressPool.id = $dict[$_.BackendAddressPool.id].id; } if ($_.RedirectConfiguration) { $_.RedirectConfiguration.id = $dict[$_.RedirectConfiguration.id].id; } if ($_.BackendHttpSettings) { $_.BackendHttpSettings.id = $dict[$_.BackendHttpSettings.id].id; } } if ($_.DefaultBackendAddressPool) { $_.DefaultBackendAddressPool.Id = $dict[$_.DefaultBackendAddressPool.Id].id } if ($_.DefaultBackendHttpSettings) { $_.DefaultBackendHttpSettings.Id = $dict[$_.DefaultBackendHttpSettings.Id].id } if($_.DefaultRedirectConfiguration) { $_.DefaultRedirectConfiguration.Id = $dict[$_.DefaultRedirectConfiguration.Id].id } $dict[$_.Id] = $_; $_.Id = $_.Id.Replace($existingResourceIdFormat,$newResourceIdFormat); } # Request Routing Rules $Rules = Get-AzApplicationGatewayRequestRoutingRule -ApplicationGateway $AppGW $v2Rules = New-Object System.Collections.Generic.List[Microsoft.Azure.Commands.Network.Models.PSApplicationGatewayRequestRoutingRule] $priority = 100 $Rules | ForEach-Object { if($dict.ContainsKey($_.HttpListener.Id)) { $command = "New-AzApplicationGatewayRequestRoutingRule -Name $($_.Name) -RuleType $($_.RuleType) -HttpListenerId $($dict[$_.HttpListener.Id].id) -Priority $($priority)"; if ($_.BackendHttpSettings -and $dict.ContainsKey($_.BackendHttpSettings.Id)) { $command += " -BackendHttpSettingsId $($dict[$_.BackendHttpSettings.Id].id) -backendAddressPoolId $($dict[$_.BackendAddressPool.Id].id)"; } elseif ($_.RedirectConfiguration.Id -and $dict.ContainsKey($_.RedirectConfiguration.Id)) { $command += " -RedirectConfigurationId $($dict[$_.RedirectConfiguration.Id].id)" } elseif ($_.UrlPathMap.Id -and $dict.ContainsKey($_.UrlPathMap.Id)) { $command += " -UrlPathMapId $($dict[$_.UrlPathMap.Id].id)" } else {Write-Error "No rule can be created for", $_.Name;} $z = Invoke-Expression ($command); if ($z) { $v2rules.Add($z); # Updating Rule Priority $priority += 50 } } } if ($v2Rules.count -NE $rules.count ) { Write-Warning ("Failed to create Request routing rules. Please check you have given correct input and retry. Please report if the problem continues.") return } else { Write-Host ("Created Request Routing Rules") } # AppGateway Custom Error Config $customError = Get-AzApplicationGatewayCustomError -ApplicationGateway $appgw $sslpolicy = Get-AzApplicationGatewaySslPolicy -ApplicationGateway $AppGw $wafConfig = Get-AzApplicationGatewayWebApplicationFirewallConfiguration -ApplicationGateway $AppGw if (!$appgw.Tag) { $appgw.Tag = @{} } $appgw.Tag.Add("MigratedBy", "AzureAppGWMigrationScript") $appgw.Tag.Add("MigratedFrom", $AppGw.Name) #Verify that AppGw of same name doesn't exist $getAppGw = Get-AzApplicationGateway -Name $appgwname -ResourceGroupName $AppGwResourceGroupName if($getAppGw) { Write-Warning ("AppGw with name $appgwname and resource group name $AppGwResourceGroupName already exists. Please provide correct parameters to the script.") return } # create a waf policy based on the WebApplicationFirewallConfiguration if it is a WAF V1 gateway if ($appgw.sku.Tier -eq "WAF") { if ( !$WafPolicyName ) { $WafPolicyName = $AppGwName + "_WAFPolicy" } #Verify that waf policy doesn't exist $getWAFPolicy = Get-AzApplicationGatewayFirewallPolicy -ResourceGroupName $AppGwResourceGroupName -name $WafPolicyName if($getWAFPolicy) { Write-Warning ("WAF Policy with WAF ID $($getWAFPolicy.Id) already exists. Please try again after deleting this WAF policy or providing a unique WAF Policy Name using WafPolicyName parameter") return } # create a waf policy based on the appgw v1 waf configuration $waf = CreateNewWAFPolicy if($waf) { $isWafPolicyCreated = $true } } # create app gateway $command = 'New-AzApplicationGateway -Name $appgwname -ResourceGroupName $AppGwResourceGroupName -Location $location -Sku $(Select-Object -InputObject $sku) -GatewayIPConfigurations $(Select-Object -InputObject $gwipconfig) -FrontendIpConfigurations $(Select-Object -InputObject $fip) ' $command += ' -FrontendPorts $(Select-Object -InputObject $FrontEndPorts) -BackendAddressPools $(Select-Object -InputObject $BackendPools) -BackendHttpSettingsCollection $(Select-Object -InputObject $SettingsList) -HttpListeners $(Select-Object -InputObject $v2listener) -RequestRoutingRules $(Select-Object -InputObject $v2rules) ' $command += ' -Tag $appgw.Tag -Force' if ($enableAutoscale) { $command += ' -AutoScaleConfiguration $(Select-Object -InputObject $autoscaleConfig)' } if ($appgw.EnableHttp2) { $command += ' -EnableHttp2 ' } if ($TrustedRootCertificates) { $command += ' -TrustedRootCertificate $TrustedRootCertificates'} if($urlpath.Count -gt 0) { $command += ' -UrlPathMaps $($urlpath)' } if($probes.Count -gt 0) { $command += ' -Probes $(Select-Object -InputObject $probes)' } if($RedirectConfig.Count -gt 0) { $command += ' -RedirectConfigurations $(Select-Object -InputObject $RedirectConfig)' } if ($SslCertificates.Count -gt 0 ) { $command += ' -SslCertificates $(Select-Object -InputObject $SslCertificates)' } if ($sslpolicy) { $command += ' -SslPolicy $(Select-Object -InputObject $sslpolicy) ' } if ($customError) { $command += ' -CustomErrorConfiguration $customError' } $command += ' -Zone $appgwDeploymentZones' if ($appgw.sku.Tier -eq "WAF" -and $isWafPolicyCreated) { $command += ' -FirewallPolicyId $waf.Id' } Write-Warning("Creating new V2 Application Gateway / WAF may take up to ~7mins. Please wait for the command to complete.") $newAppGw = Invoke-Expression ($command) if ( $newAppGw ) { Write-Host ("Successfully created V2 Application Gateway / WAF, Name : $($newAppGw.Name),` PublicIPAddress : $($pip.IpAddress),` Subnet Name (Prefix) : $($agv2Subnet.Name) ( $($agv2Subnet.AddressPrefix) )") } else { Write-Error ("Creation of V2 Application Gateway / WAF failed. Please retry after sometime. Please contact Azure Support if error persists after several retries.") return } # For Virtual Machine (VM) / Virtual Machine Scale Set (VMSS) as backend, # set VM/VMSS NIC to point to application gateway backend pool $ListOfNicsToAttachToV2 = @{} $BackendPools | ForEach-Object { if ($_.BackendIpConfigurations) { $BackendPoolToAdd = Get-AzApplicationGatewayBackendAddressPool -Name $_.Name -ApplicationGateway $newAppGw $_.BackendIpConfigurations | ForEach-Object { if ($_.Id -match "/resourceGroups/(.*?)/providers/Microsoft.Network/networkInterfaces/(.*?)/ipconfigurations/" ) { $key = "VM/$($matches[1])/$($matches[2])" if(!$ListOfNicsToAttachToV2.ContainsKey($key)){ $obj = @{ type = "VM" resourceGroup = $matches[1] nicname = $matches[2] BackendPool = @($BackendPoolToAdd) } $ListOfNicsToAttachToV2[$key] = $obj } else{ $ListOfNicsToAttachToV2[$key].BackendPool += $BackendPoolToAdd } } elseif ($_.Id -match "/resourceGroups/(.*?)/providers/Microsoft.Compute/virtualMachineScaleSets/(.*?)/virtualMachines/(.*?)/networkInterfaces/(.*?)/ipConfigurations/") { $key = "VMSS/$($matches[1])/$($matches[2])" if(!$ListOfNicsToAttachToV2.ContainsKey($key)) { $obj = @{ type = "VMSS" resourceGroup = $matches[1] vmssname = $matches[2] nicList = @{} instances = @($matches[3]) } $obj.nicList[$matches[4]] = @($BackendPoolToAdd) $ListOfNicsToAttachToV2[$key] = $obj } else { $ListOfNicsToAttachToV2[$key].instances += $matches[3] if(!$ListOfNicsToAttachToV2[$key].nicList.ContainsKey($matches[4])) { $ListOfNicsToAttachToV2[$key].nicList[$matches[4]] = @($BackendPoolToAdd) } else { $containsBackendPool = $false foreach ($pool in $ListOfNicsToAttachToV2[$key].nicList[$matches[4]]) { if ($pool.Id -eq $BackendPoolToAdd.Id) { $containsBackendPool = $true break } } if (-not $containsBackendPool) { $ListOfNicsToAttachToV2[$key].nicList[$matches[4]] += $BackendPoolToAdd } } } } else { Write-Error ("Unsupported backend address pool config for '$($BackendPoolToAdd.Name)', could not be migrated.") } } } } $jobs = @() $ListOfNicsToAttachToV2.Values | ForEach-Object { if ($_.type -eq "VM") { $jobs += Start-Job -ScriptBlock $AttachVmNetworkInterface -ArgumentList ($_.nicname, $_.resourceGroup, $_.backendpool) } else { $jobs += Start-Job -ScriptBlock $AttachVmssNetworkInterface -ArgumentList @($_.vmssname, $_.nicList, $_.resourceGroup, $_.instances) } } if ($jobs) { Write-Host "Attaching backend pool VM/VMSS NICs to v2 application gateway" Wait-Job -Job $jobs | Out-Null $jobResponses = Receive-Job -Job $jobs if (($jobResponses | Where-Object { $_ -eq $false }).count -NE 0) { Write-Error ("Could not sucessfully configure VM/VMSS in backend pool. Please retry the script after some time.") exit } } $sw.Stop() $migrationCompleted = $true if ($validateMigration) { # compare backend health for v1 and v2 app gateway $x = Get-AzApplicationGatewayBackendHealth -Name $V1AppGwName -ResourceGroupName $resourcegroup $y = Get-AzApplicationGatewayBackendHealth -Name $AppGwName -ResourceGroupName $AppGwResourceGroupName for ($i = 0; $i -lt $x.BackendAddressPools.Count; $i++) { $x1 = $x.BackendAddressPools[$i].BackendHttpSettingsCollection $y1 = $y.BackendAddressPools[$i].BackendHttpSettingsCollection $dict = @{} for ($j = 0; $j -lt $x1.Count; $j++) { $x1[$j].Servers | ForEach-Object { $dict[$_.Address] = $_.Health } $y1[$j].Servers | ForEach-Object { if ($_.Health -EQ $dict[$_.Address]) { Write-Host ("Backend Health reported equal for - $($_.Address) ") } else { Write-Warning ("Backend Health reported difference for - $($_.Address), v1 - $($dict[$_.Address]), v2 - $($_.health)") } } } } } return $newAppGw } catch [Exception] { Write-Output $_.Exception | format-list -force } finally { if ($migrationCompleted -EQ $false) { cleanup } else { Write-Host ("Migration Complete. TimeTaken : $($sw.Elapsed.TotalSeconds) seconds") } } # SIG # Begin signature block # MIInxAYJKoZIhvcNAQcCoIIntTCCJ7ECAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCiyIgTAnzSMiGT # SoVXeZ/hkMdu+UFhztYO3FrGTdJkDKCCDXYwggX0MIID3KADAgECAhMzAAADrzBA # 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 # /Xmfwb1tbWrJUnMTDXpQzTGCGaQwghmgAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp # Z25pbmcgUENBIDIwMTECEzMAAAOvMEAOTKNNBUEAAAAAA68wDQYJYIZIAWUDBAIB # BQCggbAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO # MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIClAHtvtLvrW+oyiesbBufhQ # 4WyHVhf36JKshqrnM/mrMEQGCisGAQQBgjcCAQwxNjA0oBSAEgBNAGkAYwByAG8A # cwBvAGYAdKEcgBpodHRwczovL3d3dy5taWNyb3NvZnQuY29tIDANBgkqhkiG9w0B # AQEFAASCAQBC4sdL5HsrI1Lnow8ubRqW76Cl2cWb3R6n5Kc0kip+aW89cOBxlK1F # WmQ76dTiPBMr8t3UaXRw35xXwq4RO1zpucN/UQ6SApwlNqiJharwhrqfTOO9dM9K # laflRYnTDqJr50/TaY7T5GDp77mwPP7bRJkqKfcbVoNemKOsOvoEQRfIW0w2d4xE # jq9KdQXEp/akD6JNeT0V5D51fPFAWBqlEl5o5jLDWx9gEvLVN/aYTG/3PNrXXDxm # QqLjKamOWG3c+t5Bj5gpnNpjPlAseJFWgg33gLhaYLEB5gB97kh6xC05wJqEWCad # ohJv+b4NQjsURPn4Pvpw0NZxS8pDv5iIoYIXLDCCFygGCisGAQQBgjcDAwExghcY # MIIXFAYJKoZIhvcNAQcCoIIXBTCCFwECAQMxDzANBglghkgBZQMEAgEFADCCAVkG # CyqGSIb3DQEJEAEEoIIBSASCAUQwggFAAgEBBgorBgEEAYRZCgMBMDEwDQYJYIZI # AWUDBAIBBQAEIH8RFNuiie3hR5wTVTBEpHpnh2/FaEB8oOsi0/WaZAUBAgZml9yv # 7KIYEzIwMjQwODA3MDgyMDA3Ljg1OVowBIACAfSggdikgdUwgdIxCzAJBgNVBAYT # AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29mdCBJ # cmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEmMCQGA1UECxMdVGhhbGVzIFRTUyBF # U046RDA4Mi00QkZELUVFQkExJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1w # IFNlcnZpY2WgghF7MIIHJzCCBQ+gAwIBAgITMwAAAdzB4IzCX1hejgABAAAB3DAN # BgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv # bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 # aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0y # MzEwMTIxOTA3MDZaFw0yNTAxMTAxOTA3MDZaMIHSMQswCQYDVQQGEwJVUzETMBEG # A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj # cm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBP # cGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOkQwODIt # NEJGRC1FRUJBMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNl # MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAi8izIDWyOD2RIonN6WtR # YXlKGphYvzdqafdITknIhU9QLsXqpNwumGEdn2J1/bV/RFoatTwQfJ0Xw3E8xHYp # U2IC0IY8lryRXUIa+fdt4YHabaW2aolqcbvWYDLCuQoBNieLAos9AsnTQSRfDlNL # B+Yldt2BAsWUfJ8DkqD6lSwlfOq6aQi8SvQNc++m0AaqR0UsrCjgFOUSCe/N5N9e # 6TNfy9C1MAt9Um5NSBFTvOg/9EVa3dZqBqFnpSWgjQULxeUFANUNfkl4wSzHuOAk # N0ScrjhjyAe4RZEOr5Ib1ejQYg6OK5NYPm6/e+USYgDJH/utIW9wufACox2pzL+K # pA8yUM5x3QBueI/yJrUFARSd9lPdTHIr2ssH9JGIo/IcOWDyhbBfKK/f5sYHp2Z0 # zrW6vqdS18N/nWU9wqErhWjzek4TX+eJaVWcQdBX00nn8NtRKpbZGpNRrY7Yq6+z # JEYwSCMYkDXb9KqtGqW8TZ+I3lmZlW2pI9ZohqzHtrQYH591PD6B5GfoyjZLr79t # kTBL/QgnmBwoaKc1t/JDXGu9Zc+1fMo5+OSHvmJG5ei6sZU9GqSbPlRjP5HnJswl # aP6Z9warPaFdXyJmcJkMGuudmK+cSsIyHkWV+Dzj3qlPSmGNRMfYYKEci8ThINKT # aHBY/+4cH2ASzyn/097+a30CAwEAAaOCAUkwggFFMB0GA1UdDgQWBBToc9IF3Q58 # Rfe41ax2RKtpQZ7d2zAfBgNVHSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBf # BgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3Bz # L2NybC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmww # bAYIKwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29m # dC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0El # MjAyMDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUF # BwMIMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAgEA2etvwTCvx5f8 # fWwq3eufBMPHgCqAduQw1Cj6RQbAIg1dLfLUZRx2qwr9HWDpN/u03HWrQ2kqTUlO # 6lQl8d0TEq2S6EcD7zaVPvIhKn9jvh2onTdEJPhD7yihBdMzPGJ7B8StUu3xZ595 # udxJPSLrKkq/zukJiTEzbhtupsz9X4zlUGmkJSztH5wROLP/MQDUBtkv++Je0eav # IDQIZ34+31z5p2xh+bup7lQydLR/9gmYQQyQSoZcLPIsr52H5SwWLR3iWR1wT5mr # kk2Mgd6xfXDO0ZUC29fQNgNl03ZZnWST6E4xuVRX8vyfVhbOE//ldCdiXTcB9cSu # f7URq3KWJ/N3cKEnXG4YbvphtaCJFecO8KLAOq9Ql69VFjWrLjLi+VUppKG1t1+A # /IZ54n9hxIE405zQM1NZuMxsvnSp4gQLSUdKkvatFg1W7eGwfMbyfm7kJBqM/DH0 # /Omxkh4VM0fJUXqS6MjhWj0287/MXw63jggyPgztRf1lrhDAZ/kHvXHns6NpfneD # FPi/Oge8QFcX2oKYdGBcEttGiYl8OfrRqXO/t2kJVAi5DTrafIhkqexfHO4oVvRO # NdbDo4WkbVuyNek6jkMweTKyuJvEeivhjPl1mNXIcA3IqjRtKsCVV6KFxobkXvhJ # lPwW3IcBboiAtznD/cP5HWhsOEpnbVYwggdxMIIFWaADAgECAhMzAAAAFcXna54C # m0mZAAAAAAAVMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UE # CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z # b2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZp # Y2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMy # MjVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH # EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNV # BAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0B # AQEFAAOCAg8AMIICCgKCAgEA5OGmTOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51 # yMo1V/YBf2xK4OK9uT4XYDP/XE/HZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY # 6GB9alKDRLemjkZrBxTzxXb1hlDcwUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9 # cmmvHaus9ja+NSZk2pg7uhp7M62AW36MEBydUv626GIl3GoPz130/o5Tz9bshVZN # 7928jaTjkY+yOSxRnOlwaQ3KNi1wjjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDua # Rr3tpK56KTesy+uDRedGbsoy1cCGMFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74 # kpEeHT39IM9zfUGaRnXNxF803RKJ1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2 # K26oElHovwUDo9Fzpk03dJQcNIIP8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5 # TI4CvEJoLhDqhFFG4tG9ahhaYQFzymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZk # i1ugpoMhXV8wdJGUlNi5UPkLiWHzNgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9Q # BXpsxREdcu+N+VLEhReTwDwV2xo3xwgVGD94q0W29R6HXtqPnhZyacaue7e3Pmri # Lq0CAwEAAaOCAd0wggHZMBIGCSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUC # BBYEFCqnUv5kxJq+gpE8RjUpzxD/LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJl # pxtTNRnpcjBcBgNVHSAEVTBTMFEGDCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIB # FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9y # eS5odG0wEwYDVR0lBAwwCgYIKwYBBQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUA # YgBDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU # 1fZWy4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2Ny # bC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIw # MTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDov # L3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0w # Ni0yMy5jcnQwDQYJKoZIhvcNAQELBQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/yp # b+pcFLY+TkdkeLEGk5c9MTO1OdfCcTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulm # ZzpTTd2YurYeeNg2LpypglYAA7AFvonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM # 9W0jVOR4U3UkV7ndn/OOPcbzaN9l9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECW # OKz3+SmJw7wXsFSFQrP8DJ6LGYnn8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4 # FOmRsqlb30mjdAy87JGA0j3mSj5mO0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3Uw # xTSwethQ/gpY3UA8x1RtnWN0SCyxTkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPX # fx5bRAGOWhmRaw2fpCjcZxkoJLo4S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVX # VAmxaQFEfnyhYWxz/gq77EFmPWn9y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGC # onsXHRWJjXD+57XQKBqJC4822rpM+Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU # 5nR0W2rRnj7tfqAxM328y+l7vzhwRNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEG # ahC0HVUzWLOhcGbyoYIC1zCCAkACAQEwggEAoYHYpIHVMIHSMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl # bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNO # OkQwODItNEJGRC1FRUJBMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBT # ZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQAcOf9zP7fJGQhQIl9Jsvd2OdASpqCBgzCB # gKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH # EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNV # BAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBBQUA # AgUA6l1fhjAiGA8yMDI0MDgwNzEwNTA0NloYDzIwMjQwODA4MTA1MDQ2WjB3MD0G # CisGAQQBhFkKBAExLzAtMAoCBQDqXV+GAgEAMAoCAQACAg1QAgH/MAcCAQACAhE1 # MAoCBQDqXrEGAgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAI # AgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJKoZIhvcNAQEFBQADgYEAkF6tzkbh9lit # rJrPTU8WJpiF9PUZAQ4tLWtMElIFOEjyfGEIzDBehmxQUF2O0ponkJxL4P7j4LVB # V9Mab1dEskcLBUwOsUMOxRWfdCT34Xgg1xV++ggcHVSeguSNDUyzsMGM7wLwu5Zd # UdavMWlOoIya3jCIiZjWJa0Tj5GPALUxggQNMIIECQIBATCBkzB8MQswCQYDVQQG # EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG # A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQg # VGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAdzB4IzCX1hejgABAAAB3DANBglghkgB # ZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3 # DQEJBDEiBCDwxh/UxdOAqslBZ4k3QLFwP1TkdxLbV9FCcL1+yB1XwTCB+gYLKoZI # hvcNAQkQAi8xgeowgecwgeQwgb0EIFOnF4pq2UQ/jLypnOO5YvQ67QirEQsOFfZM # vKXEgg03MIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0 # b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh # dGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMA # AAHcweCMwl9YXo4AAQAAAdwwIgQg50NdNO1bZ7PWUnlmyM/mDMzm9+VBn2KxvTau # QKOYpmcwDQYJKoZIhvcNAQELBQAEggIAPPJ8Y4UkBTVAsiYc9drgc6f+zOUCr1nl # 7VAMZ7FdO2PSucdg8eeoBTu3LCX99gXL8amEFFA6p8wbiNq7rDm2+x6xPZ/209Gy # Qpl7oGc24DOZCn/154fU0BA15QHmctFt2UM4fWkVPJqQp0zYLu/nLehT9V0aCZSq # on6Hf6ylRN1Fc4VfZ7zUFspXxySB0M/tsxwRR1rWCoxcxBqAiLwFwDOBykYmj3xX # GGSz4R2eVmswVzjUzxyZ66vT30OjwQITFwXknhnEk1PZF0XG3hWMTVHymaD9KoG8 # 1mWv/BX8SlQiUqfqbCttlUMeNqQ9Jqpe7iwg7v5AgQoj8YwQp2LUKdXSgf1qlMlW # p3exg2YG+Ku7cmyWJuk0giglBnCxyWzo1xweCx9uW+KkvAVtq5kcnlv1BkJWxpqK # alNo66OJ/0nOJL/NLdxArQg7CDyg4zsADWK4cDXTsklAnXkEy+pk+ythEGedh/D+ # /6zVbhR1Q2tzD9o7zWmStsTSPiKnYetR+6zsU2Ary4S+1CLU29oZMfxywUxSWzRb # kigxtd6AAoXeFUK3Je7R4m0hct2joeuTYpLwp29skow+Vzgym7SfWjZ4/nLSvCWp # pt/Y6M26blvZ/DHILMow8C6ljfub5WgIpYWU+97SvqjqZCJ3VyQpRzzCOJYeA9pA # 1KtoV5dxZ5w= # SIG # End signature block |