AzureAppGWMigration.ps1

<#PSScriptInfo
 
.VERSION 1.0.7
 
.GUID be3b84b4-e9c5-46fb-a050-699c68e16119
 
.AUTHOR Microsoft Corporation
 
.COMPANYNAME Microsoft Corporation
 
.COPYRIGHT Microsoft Corporation. All rights reserved.
 
.TAGS Azure, Az, ApplicationGateway, AzNetworking
 
#>


<#
 
.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
 
.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
)

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
}

$sw = [Diagnostics.Stopwatch]::StartNew()
$resource = Get-AzResource -ResourceId $resourceId
$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
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
    }
}
# Connect-AzAccount
$matchResponse = $resourceId -match "/subscriptions/(.*?)/resourceGroups/"
$subscription = $matches[1]
$context = Set-AzContext -Subscription $subscription
$AppGw = Get-AzApplicationGateway -Name $V1AppGwName -ResourceGroupName $resourcegroup

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($isNewResourceGroupCreated)
    {
        Write-Host ("Removing ResourceGroup $AppGwResourceGroupName")
        Remove-AzResourceGroup -Name $AppGwResourceGroupName -Force -ErrorAction SilentlyContinue | out-null
    }
    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/")
            }
        }
    }
}

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
}

ValidateInput
Write-Host ("Input parameters validated")
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]
    $V1Subnet = Get-AzVirtualNetworkSubnetConfig -Name $matches[3] -VirtualNetwork $vnet
    $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 ( $null -eq  $pip )
    {
        $PublicIpResourceName = $AppGwName + "-IP"
        $pip = New-AzPublicIpAddress -ResourceGroupName $AppGwResourceGroupName -name $PublicIpResourceName -location $location -AllocationMethod "Static" -Sku Standard -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]
    $Rules | ForEach-Object {
        if($dict.ContainsKey($_.HttpListener.Id))
        {
            $command = "New-AzApplicationGatewayRequestRoutingRule -Name $($_.Name) -RuleType $($_.RuleType) -HttpListenerId $($dict[$_.HttpListener.Id].id)";
            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);
            }
        }
    }

    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)

    # 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($wafConfig)
    {   $command += ' -WebApplicationFirewallConfiguration $wafConfig' }
    if ($customError)
    {   $command += ' -CustomErrorConfiguration $customError' }
    if($appgw.Zones.Count -GT 0)
    {
        $command += ' -Zone $appgw.Zones'
    }

    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
# MIIkbQYJKoZIhvcNAQcCoIIkXjCCJFoCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCIX8Ca7sFIaWTa
# vrJpsHvBwUWg4NFSA7G3VHKBi4kNQKCCDYEwggX/MIID56ADAgECAhMzAAABUZ6N
# j0Bxow5BAAAAAAFRMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMTkwNTAyMjEzNzQ2WhcNMjAwNTAyMjEzNzQ2WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQCVWsaGaUcdNB7xVcNmdfZiVBhYFGcn8KMqxgNIvOZWNH9JYQLuhHhmJ5RWISy1
# oey3zTuxqLbkHAdmbeU8NFMo49Pv71MgIS9IG/EtqwOH7upan+lIq6NOcw5fO6Os
# +12R0Q28MzGn+3y7F2mKDnopVu0sEufy453gxz16M8bAw4+QXuv7+fR9WzRJ2CpU
# 62wQKYiFQMfew6Vh5fuPoXloN3k6+Qlz7zgcT4YRmxzx7jMVpP/uvK6sZcBxQ3Wg
# B/WkyXHgxaY19IAzLq2QiPiX2YryiR5EsYBq35BP7U15DlZtpSs2wIYTkkDBxhPJ
# IDJgowZu5GyhHdqrst3OjkSRAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUV4Iarkq57esagu6FUBb270Zijc8w
# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1
# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU0MTM1MB8GA1UdIwQYMBaAFEhu
# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu
# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w
# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx
# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAWg+A
# rS4Anq7KrogslIQnoMHSXUPr/RqOIhJX+32ObuY3MFvdlRElbSsSJxrRy/OCCZdS
# se+f2AqQ+F/2aYwBDmUQbeMB8n0pYLZnOPifqe78RBH2fVZsvXxyfizbHubWWoUf
# NW/FJlZlLXwJmF3BoL8E2p09K3hagwz/otcKtQ1+Q4+DaOYXWleqJrJUsnHs9UiL
# crVF0leL/Q1V5bshob2OTlZq0qzSdrMDLWdhyrUOxnZ+ojZ7UdTY4VnCuogbZ9Zs
# 9syJbg7ZUS9SVgYkowRsWv5jV4lbqTD+tG4FzhOwcRQwdb6A8zp2Nnd+s7VdCuYF
# sGgI41ucD8oxVfcAMjF9YX5N2s4mltkqnUe3/htVrnxKKDAwSYliaux2L7gKw+bD
# 1kEZ/5ozLRnJ3jjDkomTrPctokY/KaZ1qub0NUnmOKH+3xUK/plWJK8BOQYuU7gK
# YH7Yy9WSKNlP7pKj6i417+3Na/frInjnBkKRCJ/eYTvBH+s5guezpfQWtU4bNo/j
# 8Qw2vpTQ9w7flhH78Rmwd319+YTmhv7TcxDbWlyteaj4RK2wk3pY1oSz2JPE5PNu
# Nmd9Gmf6oePZgy7Ii9JLLq8SnULV7b+IP0UXRY9q+GdRjM2AEX6msZvvPCIoG0aY
# HQu9wZsKEK2jqvWi8/xdeeeSI9FN6K1w4oVQM4Mwggd6MIIFYqADAgECAgphDpDS
# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0
# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla
# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT
# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG
# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S
# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz
# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7
# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u
# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33
# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl
# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP
# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB
# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF
# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM
# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ
# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud
# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO
# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0
# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p
# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB
# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw
# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA
# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY
# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj
# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd
# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ
# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf
# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ
# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j
# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B
# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96
# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7
# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I
# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIWQjCCFj4CAQEwgZUwfjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAVGejY9AcaMOQQAAAAABUTAN
# BglghkgBZQMEAgEFAKCBwDAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgrMlV3Hts
# 7wMQO7IIGIEQ6rc3POoXqiFLUdqioftpgWcwVAYKKwYBBAGCNwIBDDFGMESgKIAm
# AEEAegB1AHIAZQBBAHAAcABHAHcATQBpAGcAcgBhAHQAaQBvAG6hGIAWaHR0cHM6
# Ly9taWNyb3NvZnQuY29tIDANBgkqhkiG9w0BAQEFAASCAQBPMbMK62ejCTO0I9Vu
# g1dhbvupeBymCvp5hWvwPKAqkvviE8WPXbrTsjpIMMzbYvbrXpU5UyiiVUy3eBZd
# EaVlVUkFMLNdg9/gawFEbrj7G7tGAjt7ibYdAGhAeOmZzVdLPUFVjs2wmMbg9+tO
# c1Lk4K3dfCuPr0DUa3Y0Ne+pLHrbbrGEWuEgC/cR+PWU0kaBAgF7LuuHVzntczMN
# PwKhwh1PYrZNfM+2Pl51bCPwyJn3ZDt7L+ltHOaoT9tprPLwuYzjie8gBGfyPzfm
# zWngnwxEPJZ9U9E0Ls/n4B8R8ZFjcIGFoo2CDqKeMP7PzDLv4a2D1O6Jwy65Avg8
# /M1FoYITujCCE7YGCisGAQQBgjcDAwExghOmMIITogYJKoZIhvcNAQcCoIITkzCC
# E48CAQMxDzANBglghkgBZQMEAgEFADCCAVgGCyqGSIb3DQEJEAEEoIIBRwSCAUMw
# ggE/AgEBBgorBgEEAYRZCgMBMDEwDQYJYIZIAWUDBAIBBQAEIAeYfGyOI7Sy8DSq
# 7uoi62VODUcZd5boGI++cAoqqnBcAgZeemsR3ZoYEzIwMjAwMzMwMDYzNDQ2Ljcx
# OFowBwIBAYACAfSggdSkgdEwgc4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo
# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y
# cG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBS
# aWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjpGNTI4LTM3NzctOEE3NjElMCMG
# A1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaCCDyIwggT1MIID3aAD
# AgECAhMzAAABApFjXMW0Wbk8AAAAAAECMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNV
# BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w
# HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29m
# dCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTE5MDkwNjIwNDExNloXDTIwMTIwNDIw
# NDExNlowgc4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD
# VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAn
# BgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQL
# Ex1UaGFsZXMgVFNTIEVTTjpGNTI4LTM3NzctOEE3NjElMCMGA1UEAxMcTWljcm9z
# b2Z0IFRpbWUtU3RhbXAgU2VydmljZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
# AQoCggEBAMR9cB7FDTMrBI8h/TzUcyyH/WMnyW+TxPx308rF22K65K6d0Cg/VQyr
# 3xtoT+ir0MEhZ/hvXY5sO8F4HSu2frknt30PYRTQW0I1gzgNc7TggbcxfY4JcXSt
# qM0/3NGZusiKKDl8UvFV85irGYuiP/b36nqe6T5zk1gVIGHx5nFIdfPyHjsnoWX6
# gOxfqIDavfFeb/Ak7lKqZAHUgdAZU08KCYkVKYLtZbaRyQ2W1/KA7cPfcT17u+r6
# dJHZNfMqnCWriLZz9sTdkpTnQgvBr6LdLJ8b0e24taMX98ySqyenc1bBfoa49ras
# Kev/Ao17wc3sTO1POEkJQzOib6OwiNcCAwEAAaOCARswggEXMB0GA1UdDgQWBBQ/
# AgaO19V67EZWg1gyCfv3uVC1tjAfBgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNo
# WoVtVTBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v
# cGtpL2NybC9wcm9kdWN0cy9NaWNUaW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYI
# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpL2NlcnRzL01pY1RpbVN0YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMB
# Af8EAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQCd
# MoMxXiGN6lYPaFv/uIVhdPr50PRE0H+4jZwUEOrTU8vJLF7ARizMeK/ZmxczuJPQ
# hm7KSZBJXp+FmrX5jRE+gD7+gkPlTaRTiy+A/3jVOFJiPChh17Zxz/fSqtbKlejk
# G7LJv4Ptg/1u7qVI3bNGge85BkDt0xlTUsK8VxA2zGQSq4JfkF5TSPCGHQjmKdgJ
# TfiZadCWQ2j/K5W0QAzPxNhrj3QetJp9Dqlr04EiV1IvZNAhY00TUByBGGhTlEcl
# YTCzhGG7Agv2+qGkOv1tmeRjqLCETuF3/+WQWjxEzHfjMRsbDfhrcuAlAXZMrJkt
# Br+87FwXNzt/81FwkOOkMIIGcTCCBFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG
# 9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO
# BgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEy
# MDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIw
# MTAwHhcNMTAwNzAxMjEzNjU1WhcNMjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGlt
# ZS1TdGFtcCBQQ0EgMjAxMDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
# AKkdDbx3EYo6IOz8E5f1+n9plGt0VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vw
# FVMnBDEfQRsalR3OCROOfGEwWbEwRA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNF
# DdDq9UeBzb8kYDJYYEbyWEeGMoQedGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC7
# 32H8RsEnHSRnEnIaIYqvS2SJUGKxXf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOW
# RH7v0Ev9buWayrGo8noqCjHw2k4GkbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJ
# k3jN/LzAyURdXhacAQVPIk0CAwEAAaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEA
# MB0GA1UdDgQWBBTVYzpcijGQ80N7fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4K
# AFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSME
# GDAWgBTV9lbLj+iiXGJo0T2UkFvXzpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRw
# Oi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJB
# dXRfMjAxMC0wNi0yMy5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5o
# dHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8y
# MDEwLTA2LTIzLmNydDCBoAYDVR0gAQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEw
# PQYIKwYBBQUHAgEWMWh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9D
# UFMvZGVmYXVsdC5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABv
# AGwAaQBjAHkAXwBTAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQAD
# ggIBAAfmiFEN4sbgmD+BcQM9naOhIW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/1
# 5/B4vceoniXj+bzta1RXCCtRgkQS+7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRW
# S3TvQhDIr79/xn/yN31aPxzymXlKkVIArzgPF/UveYFl2am1a+THzvbKegBvSzBE
# JCI8z+0DpZaPWSm8tv0E4XCfMkon/VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/
# 3cVKC5Em4jnsGUpxY517IW3DnKOiPPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9
# nhquBEKDuLWAmyI4ILUl5WTs9/S/fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5H
# moDF0M2n0O99g/DhO3EJ3110mCIIYdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv3
# 3nJ+YWtvd6mBy6cJrDm77MbL2IK0cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI
# 5pgt6o3gMy4SKfXAL1QnIffIrE7aKLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0
# MkvfY3v1mYovG8chr1m1rtxEPJdQcdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0e
# GTgvvM9YBS7vDaBQNdrvCScc1bN+NR4Iuto229Nfj950iEkSoYIDsDCCApgCAQEw
# gf6hgdSkgdEwgc4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw
# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
# KTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYD
# VQQLEx1UaGFsZXMgVFNTIEVTTjpGNTI4LTM3NzctOEE3NjElMCMGA1UEAxMcTWlj
# cm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaIlCgEBMAkGBSsOAwIaBQADFQAX6b/t
# hBTl/jMeKcc4lhOUcT39r6CB3jCB26SB2DCB1TELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMgTWljcm9zb2Z0IE9wZXJhdGlvbnMg
# UHVlcnRvIFJpY28xJzAlBgNVBAsTHm5DaXBoZXIgTlRTIEVTTjo0REU5LTBDNUUt
# M0UwOTErMCkGA1UEAxMiTWljcm9zb2Z0IFRpbWUgU291cmNlIE1hc3RlciBDbG9j
# azANBgkqhkiG9w0BAQUFAAIFAOIsCDQwIhgPMjAyMDAzMzAxMzU1MDBaGA8yMDIw
# MDMzMTEzNTUwMFowdzA9BgorBgEEAYRZCgQBMS8wLTAKAgUA4iwINAIBADAKAgEA
# AgIG6gIB/zAHAgEAAgIaKjAKAgUA4i1ZtAIBADA2BgorBgEEAYRZCgQCMSgwJjAM
# BgorBgEEAYRZCgMBoAowCAIBAAIDFuNgoQowCAIBAAIDB6EgMA0GCSqGSIb3DQEB
# BQUAA4IBAQBj+Z/5j10+PsrabUXuDHUscXPfr5ziZ/oxWRpjhqee3pV4+qp9c/XK
# 3RyBfbA+6cwnLC9hvdU5VMiN4kgbYe8d9MGix9pt8nmBa/IG9+eyDJwGa8j3IDS+
# ToLDP/EkYYPPnZTnfeXVGi+wTXrWnXrGosRIlTREAFm/R0Up7VwqL2txjUUDQ3dr
# Bgnph6U86z+Xi6u9LT/Xp3EpKj6zaa8DOyYVLy5eQ76z0RqpZ7QFMM4Mp6hnhG0m
# glybPoJY3leJcS/9D/KY7d2/qIEkYE90z2qf5FnDu5qquCEVAJ9XXjqBNQvnQBNV
# feQurIK/gYqId5+OcdNt2ewl/u4rwMXyMYIC9TCCAvECAQEwgZMwfDELMAkGA1UE
# BhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAc
# BgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0
# IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAECkWNcxbRZuTwAAAAAAQIwDQYJYIZI
# AWUDBAIBBQCgggEyMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG
# 9w0BCQQxIgQgBVryeveLNGlUR65GwsSuLc45J7TmKpQMFNyJ11rpeL4wgeIGCyqG
# SIb3DQEJEAIMMYHSMIHPMIHMMIGxBBQX6b/thBTl/jMeKcc4lhOUcT39rzCBmDCB
# gKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH
# EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNV
# BAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABApFjXMW0Wbk8
# AAAAAAECMBYEFJ64pNZ27wgxP0L24SmvDluHu7YuMA0GCSqGSIb3DQEBCwUABIIB
# AEI77WvuBKbSlhaAoDKcJdMmgeMBefU8PuXPZYcKc4tBG2tihlhTgmFjojyMv8de
# ttFEaTgj8b7iix1dInGgNuXAhda/5S8UpvhAOu63HRb0VPy+M/CTmzGdlBa5JcvU
# TUOHMMTLVec46Qxe46TbbiQI5p/IgPOnWCgT00ladLonY3dKKDjbZFGEQFRw9UgS
# QZHV9NcuUvrJShMN1/x/q24hKSHXUqvLL/F23MgP5oUvzT4hisEo0OqW4GLXCSYP
# qYtHqe/oMMefd/nKDkVO4LMK6kbgG4317ruhQ7hO47Cu6NYQbDflfNaQ7Qw0jeYn
# CNwNJd5KmdIg9rdYbcDI9uU=
# SIG # End signature block