AzureAppGWMigration.ps1

<#PSScriptInfo
 
.VERSION 1.0.12
 
.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.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, $BackendPool)
    $nic = Get-AzNetworkInterface -Name $nicName -ResourceGroupName $rgname -ErrorAction SilentlyContinue
    if ($nic)
    {
        $nicipconfig = Get-AzNetworkInterfaceIpConfig -NetworkInterface $nic
        $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, $BackendPoolToAdd, $Instances)
    $nicList = $nicList | Select-Object -Unique
    $Instances = $Instances | Select-Object -Unique
    $vmss = Get-AzVmss -VMScaleSetName $vmssName -ResourceGroupName $rgname
    $vmss.VirtualMachineProfile.NetworkProfile.NetworkInterfaceConfigurations `
        | Where-Object { $_.Name -in $nicList }`
        | Select-Object -ExpandProperty IpConfigurations | ForEach-Object { if(!$_.ApplicationGatewayBackendAddressPools.id.Contains($BackendPoolToAdd.id)) { $_.ApplicationGatewayBackendAddressPools.Add($BackendPoolToAdd.id) } }
    Update-AzVmss -VirtualMachineScaleSet $vmss -Name $vmssName -ResourceGroupName $rgname -ErrorVariable errorDetails
    if ($errorDetails)
    {
        Write-Error ("Failed to migrate backend pool '$($BackendPoolToAdd.Name)'")
        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;
    }

    $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])"
                    $obj = @{
                        type = "VM"
                        resourceGroup = $matches[1]
                        nicname = $matches[2]
                        backendpool = $BackendPoolToAdd
                    }
                    $ListOfNicsToAttachToV2[$key] = $obj
                }
                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 = @($matches[4])
                            instances = @($matches[3])
                            backendpool = $BackendPoolToAdd
                        }
                        $ListOfNicsToAttachToV2[$key] = $obj
                    }
                    else
                    {
                        $ListOfNicsToAttachToV2[$key].nicList += $matches[4]
                        $ListOfNicsToAttachToV2[$key].instances += $matches[3]
                    }
                }
                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, $_.backendpool, $_.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
# MIIoLAYJKoZIhvcNAQcCoIIoHTCCKBkCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDOYehz8zPqIwUz
# qK9oqcM6Z31LHqIH+y/neDAivD8Ck6CCDXYwggX0MIID3KADAgECAhMzAAADrzBA
# 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
# /Xmfwb1tbWrJUnMTDXpQzTGCGgwwghoIAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAAOvMEAOTKNNBUEAAAAAA68wDQYJYIZIAWUDBAIB
# BQCggbAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIHOndhwyTBmrX9ebE3NGFL1P
# HU/B4kH4eddrPl+AoG1fMEQGCisGAQQBgjcCAQwxNjA0oBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEcgBpodHRwczovL3d3dy5taWNyb3NvZnQuY29tIDANBgkqhkiG9w0B
# AQEFAASCAQCsVnidClotj9x7h49mfJUa1WpT6UBLYLzNLwNRy3J1AzIVThaEovE7
# 7aae0dle8sbH9xbH9vvTLwzbCqaJCuWLLa45lqG3zeyQVcOsGI6UlV9pB7h4sCBk
# rht9rtClQymovoUQJWqm7RHssyBaO+vfyUdiacw/vUG47atVv/a1ZYY4y5A4FMGV
# oYwbq4XFYCTF+hPRO5GrugWmHhLEP30WB/Z1Is+IeOM4F5VUFnTPWVIdNsVKNN8/
# Co2E8u0g9UTWV9gphUmJn5fwtLXVXekHWKnwUMOybtuGaVvnBK//7QxSktfPO7/9
# BJCBfTSKalqyuZYVOBXEE2Ut10vZ7WgUoYIXlDCCF5AGCisGAQQBgjcDAwExgheA
# MIIXfAYJKoZIhvcNAQcCoIIXbTCCF2kCAQMxDzANBglghkgBZQMEAgEFADCCAVIG
# CyqGSIb3DQEJEAEEoIIBQQSCAT0wggE5AgEBBgorBgEEAYRZCgMBMDEwDQYJYIZI
# AWUDBAIBBQAEIJOFP7Ylty7OnPCI0bA9LPT1LDjrTZbQs/239X+h0zCJAgZmRla4
# MDkYEzIwMjQwNTI5MDkzNTA2LjA2MlowBIACAfSggdGkgc4wgcsxCzAJBgNVBAYT
# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD
# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1pY3Jvc29mdCBB
# bWVyaWNhIE9wZXJhdGlvbnMxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjozMzAz
# LTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj
# ZaCCEeowggcgMIIFCKADAgECAhMzAAAB5tlCnuoA+H3hAAEAAAHmMA0GCSqGSIb3
# DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD
# VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAk
# BgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTIzMTIwNjE4
# NDUxNVoXDTI1MDMwNTE4NDUxNVowgcsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX
# YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg
# Q29ycG9yYXRpb24xJTAjBgNVBAsTHE1pY3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlv
# bnMxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjozMzAzLTA1RTAtRDk0NzElMCMG
# A1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCCAiIwDQYJKoZIhvcN
# AQEBBQADggIPADCCAgoCggIBAL2+mHzi2CW4TOb/Ck0qUCNwSUbN+W8oANnUP7Z3
# +J5hgS0XYcoysoYUM4uktZYbkMTKIpuVgqsTae3njQ4a7flnHSBckETTNZqdkQCM
# KO3h4YGL65qRmyTvTMdNAcfJ8/4HebYFJI0U+GCxUg+nq+j/23o5417MjBfkTn5X
# AQbfudmAR7FAXZ9BlhvFDUBq6oO9F1exKkrV2HVQG30RoyzO65xpHmczBA3qwOMb
# 30XN0r0C3NufhKaWygtS1ECH/vrywp3RjWEyYpUfAhfz/gm5RFQFFnQla7Q1hAGn
# ySGS7XxDwIBDnTS0UHtUfekPzOgDiVwDsmTFMag8qu5+b6VFkADiIyBtwtnY//FJ
# 2coXFTy8vfVGg2VkmIYvkypNe+/IEvP4xE/gSf03J7U3zH+UkPWy102jnAkb6aBe
# wT/N/ODYZpWpBzMUeDQ2Xxukiqc0VRF5BGrcLWNVgwJJx6A3Md5i3Dk6Zn/t5WdG
# aNeUKwu92zE7NzVhWfqdkuRAPnLfUdisH2Ige6zCFoy/aEk02NWd2SlbL3fg8hm5
# ZMyTfrSSNc8XCXZa/VPOb206sKrz6XjTwogvon55+gY2RHxgHcz67W1h5UM79Nw5
# sYfFoYUHpBnEBSmd8Hk38yYE3Ew6rMbU3xCLBbyC2OMwmIUF/qJhisKO1HAXsg91
# AsW1AgMBAAGjggFJMIIBRTAdBgNVHQ4EFgQU5QQxee03nj7XVkz5C7tDmuDcVz0w
# HwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXwYDVR0fBFgwVjBUoFKg
# UIZOaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0
# JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3JsMGwGCCsGAQUFBwEBBGAw
# XjBcBggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9j
# ZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcnQw
# DAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAOBgNVHQ8BAf8E
# BAMCB4AwDQYJKoZIhvcNAQELBQADggIBAGFu6iBNqlGy7BKRoUxDp3K7xkJhSlZD
# yIituLjS1TaErqkeC7SGPTP/3MVFHHkN+G6SO9uMD91LlVh/HPUQhs+W3z3swnaw
# EY7ZgtjBh6V8mkPBsHRdL1mSuqnOrpf+WYNAOfcbm9xilhAInnksu/IWUnX3kBWj
# hbLxRfmnuD1bcyA0dAykz4RXrj5yzOPgejlpCZ4oa0rLvDvZ5Fj+9YO6m2u/Ou4U
# 2YoIi3XZRwDkE6xenU+2SPHbJGwKPvsNKaXTNViOpb8hJaSsaPJ5Un6SHNy3FouS
# SVXALGKCiQPp+RZvLSEIQpM5M8zOG6A8gBzFwexHazHTVhFr2kfbO912y4ER9IUb
# oKPRBK8Rn8z2Yn6HiaJpBJHsARtUYNvJEqRifzRL7cCZGWHdk574EWonns5d14gN
# Idu8fMnuhOobz3qXd5SE+xmDr182DFPGW9E2ZET/7rViPtnW4HRdhA/rSuwwt1OV
# VgTJlSXkwtMvku+oWjNmVLZeiOLgEQ/p11VPOYcnih05kxZNN5DQjCdYb3y9a/+u
# g96AKvUbrUVWt1csTcBch+3hk3hmQNOegCE/DsNk09GVJbhNtWP8vDRe+ctg3AxQ
# D2i5j/DH215Nony9ORuBjJo5goXPqs1Fdnhp/p7chfAwJ98JqykpRcLvZgy7lbwv
# /PJPGw1QSAFtMIIHcTCCBVmgAwIBAgITMwAAABXF52ueAptJmQAAAAAAFTANBgkq
# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
# IDIwMTAwHhcNMjEwOTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1WjB8MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQg
# VGltZS1TdGFtcCBQQ0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
# ggIBAOThpkzntHIhC3miy9ckeb0O1YLT/e6cBwfSqWxOdcjKNVf2AX9sSuDivbk+
# F2Az/1xPx2b3lVNxWuJ+Slr+uDZnhUYjDLWNE893MsAQGOhgfWpSg0S3po5GawcU
# 88V29YZQ3MFEyHFcUTE3oAo4bo3t1w/YJlN8OWECesSq/XJprx2rrPY2vjUmZNqY
# O7oaezOtgFt+jBAcnVL+tuhiJdxqD89d9P6OU8/W7IVWTe/dvI2k45GPsjksUZzp
# cGkNyjYtcI4xyDUoveO0hyTD4MmPfrVUj9z6BVWYbWg7mka97aSueik3rMvrg0Xn
# Rm7KMtXAhjBcTyziYrLNueKNiOSWrAFKu75xqRdbZ2De+JKRHh09/SDPc31BmkZ1
# zcRfNN0Sidb9pSB9fvzZnkXftnIv231fgLrbqn427DZM9ituqBJR6L8FA6PRc6ZN
# N3SUHDSCD/AQ8rdHGO2n6Jl8P0zbr17C89XYcz1DTsEzOUyOArxCaC4Q6oRRRuLR
# vWoYWmEBc8pnol7XKHYC4jMYctenIPDC+hIK12NvDMk2ZItboKaDIV1fMHSRlJTY
# uVD5C4lh8zYGNRiER9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6bMURHXLvjflSxIUX
# k8A8FdsaN8cIFRg/eKtFtvUeh17aj54WcmnGrnu3tz5q4i6tAgMBAAGjggHdMIIB
# 2TASBgkrBgEEAYI3FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQWBBQqp1L+ZMSavoKR
# PEY1Kc8Q/y8E7jAdBgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXAYDVR0g
# BFUwUzBRBgwrBgEEAYI3TIN9AQEwQTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5t
# aWNyb3NvZnQuY29tL3BraW9wcy9Eb2NzL1JlcG9zaXRvcnkuaHRtMBMGA1UdJQQM
# MAoGCCsGAQUFBwMIMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQE
# AwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQ
# W9fOmhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNv
# bS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBa
# BggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0
# LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MA0GCSqG
# SIb3DQEBCwUAA4ICAQCdVX38Kq3hLB9nATEkW+Geckv8qW/qXBS2Pk5HZHixBpOX
# PTEztTnXwnE2P9pkbHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6U03dmLq2HnjYNi6c
# qYJWAAOwBb6J6Gngugnue99qb74py27YP0h1AdkY3m2CDPVtI1TkeFN1JFe53Z/z
# jj3G82jfZfakVqr3lbYoVSfQJL1AoL8ZthISEV09J+BAljis9/kpicO8F7BUhUKz
# /AyeixmJ5/ALaoHCgRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTpkbKpW99Jo3QMvOyR
# gNI95ko+ZjtPu4b6MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0sHrYUP4KWN1APMdU
# bZ1jdEgssU5HLcEUBHG/ZPkkvnNtyo4JvbMBV0lUZNlz138eW0QBjloZkWsNn6Qo
# 3GcZKCS6OEuabvshVGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJsWkBRH58oWFsc/4K
# u+xBZj1p/cvBQUl+fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7Fx0ViY1w/ue10Cga
# iQuPNtq6TPmb/wrpNPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0dFtq0Z4+7X6gMTN9
# vMvpe784cETRkPHIqzqKOghif9lwY1NNje6CbaUFEMFxBmoQtB1VM1izoXBm8qGC
# A00wggI1AgEBMIH5oYHRpIHOMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz
# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
# cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z
# MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046MzMwMy0wNUUwLUQ5NDcxJTAjBgNV
# BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2WiIwoBATAHBgUrDgMCGgMV
# AOJY0F4Un2O9oSs3rgPUbzp4vSa7oIGDMIGApH4wfDELMAkGA1UEBhMCVVMxEzAR
# BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p
# Y3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3Rh
# bXAgUENBIDIwMTAwDQYJKoZIhvcNAQELBQACBQDqAU8mMCIYDzIwMjQwNTI5MDY1
# MjIyWhgPMjAyNDA1MzAwNjUyMjJaMHQwOgYKKwYBBAGEWQoEATEsMCowCgIFAOoB
# TyYCAQAwBwIBAAICBk4wBwIBAAICE6gwCgIFAOoCoKYCAQAwNgYKKwYBBAGEWQoE
# AjEoMCYwDAYKKwYBBAGEWQoDAqAKMAgCAQACAwehIKEKMAgCAQACAwGGoDANBgkq
# hkiG9w0BAQsFAAOCAQEAO7chV/VOV9AqUyZe0vCj7xjfjpChqb60GeCkY/M8FB3n
# KsRpkmUSC9Eq0G5Z2ofMYz1Yzu66o6stqRVhpQJKmhZnALho88GCtWoUAMfOyNCl
# XruN7UZdUUeyi4rleZIBOYgtxP8FI+hBJL0pt99WnBqnnV+2xJISSr2JYLZmcbqM
# 4kBhuUHdUEAqXFA5b41l9hmNFpW5JN7aZGqqNezXCHzvm5h+OSKf5JIbfftzuLbc
# 6L2/JWXVnnHLrHx2JNvhK2De0Et167Fsv2IRs6Z9g2N29fLmkJQYIrafZn4nGSZo
# 4R8zA1Yp5oyj4vxuW01h77+H956yTm5ZXUA+W/zDCzGCBA0wggQJAgEBMIGTMHwx
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1p
# Y3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB5tlCnuoA+H3hAAEAAAHm
# MA0GCWCGSAFlAwQCAQUAoIIBSjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQw
# LwYJKoZIhvcNAQkEMSIEIGQaxrxsmCVAzWMBizuM9sGeqG0Y/jV2UThq1eJAvLcB
# MIH6BgsqhkiG9w0BCRACLzGB6jCB5zCB5DCBvQQgz7ujhqgeswlobyCcs7WrXqEh
# hxGejLoWc4JudIPSxlkwgZgwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0Eg
# MjAxMAITMwAAAebZQp7qAPh94QABAAAB5jAiBCD/D1N/wXVR59kvJJ2Id/8bZa7m
# k7GDGYwoYn1w6WeY4DANBgkqhkiG9w0BAQsFAASCAgCtSOwxRK9JB7y1+rbEv1sd
# 6081GM+5nmNV2JMW7PvICGYj8faxvHfAizTLCEgeix4fzqmV0MnziI+ed8kD1756
# fXnCkfMRcJKRCwqoPKkJEhVk108M5m7U1o1rcn1J5V7DWxUY1wIwVTYpQHo6JUZZ
# s+HGi1bzCjWHw/dirrHjdwuGvF75gykk6oSldmUOlt4Nc4zNN5HjjA/z7m+MeBE0
# kn2EGwbb/XG3N2NSqU2eUM8x5AJvKEXBmgvqEP4zTVj08GJJ4L6UzlyYxI9SJERx
# 7dTmvRM+M48I+RTTdsAC4E1SIlK+hL/ti0sc8udC90WjI59YmCdu2mpkvDWj/cWx
# 9lh7dh8KPtQRQwEn33z9p0VW7k3MSSQDkwIBv7xGBfjqWlMSdu8Rm5WrAo9ieX/N
# 9bExvOyxnNHyNDjS+t3g2/qvrA9ULhyOFFhXpeYCcceWiHvJbmTQXJUcYS+ssdH5
# JjkWOoooymBHyVHTYAhLbqMlS5/ZkyRgULEuA0QiJVhaQ4ZElsqiXNZSbpeP30fC
# rB0pQ3lsgA8iRO9bl/fAyzBvmdt5B0W15LthUyjgpiDfMvcchE/sWX1Q3CDU231K
# FQHTEsOrEUTm7QykNh8MFmnYZrSp/F5nBBVbQKQZ96YOjmFWm7B0E27JZ2ZnzsFt
# jmGSIMhGGpxa+vsgAMpOLA==
# SIG # End signature block