AzureAppGWIPMigrate.ps1

<#PSScriptInfo

.VERSION 1.0.0

.GUID 17258599-db9d-4035-b2dd-ae2a37654d9a

.AUTHOR Microsoft Corporation

.COMPANYNAME Microsoft Corporation

.COPYRIGHT Microsoft Corporation. All rights reserved.

.TAGS Azure, Az, ApplicationGateway, AzNetworking

.RELEASENOTES
1.0.0
 -- Attach the public IP of a V1 Application Gateway to V2 Application Gateway
#>


<#

.SYNOPSIS
Application Gateway v1 -> v2 IP Migration

.DESCRIPTION
This script will help you attach IP of a V1 SKU Application Gateway to a V2 SKU Application Gateway

.PARAMETER V1ResourceId
Application Gateway ResourceId, like "/subscriptions/<your-subscriptionId>/resourceGroups/<v1-app-gw-rgname>/providers/Microsoft.Network/applicationGateways/<v1-app-gw-name>"

.PARAMETER V2ResourceId
Application Gateway ResourceId, like "/subscriptions/<your-subscriptionId>/resourceGroups/<v2-app-gw-rgname>/providers/Microsoft.Network/applicationGateways/<v2-app-gw-name>"

.EXAMPLE
.\ipmigrate.ps1 -V1ResourceId "/subscriptions/<your-sub-id>/resourceGroups/<your-rg>/providers/Microsoft.Network/applicationGateways/<v1AppGatewayName>" -V2ResourceId "/subscriptions/<your-sub-id>/resourceGroups/<your-rg>/providers/Microsoft.Network/applicationGateways/<v2AppGatewayName>"

.INPUTS
String

.OUTPUTS
PSApplicationGateway

.LINK
https://aka.ms/appgwipmigratedoc
https://docs.microsoft.com/en-us/azure/application-gateway/
#>


#Requires -Module Az.Accounts
#Requires -Module Az.Network
#Requires -Module Az.Compute
#Requires -Module Az.Resources
Param(
    [Parameter(Mandatory = $True)][string] $V1ResourceId,
    [Parameter(Mandatory = $True)][string] $V2ResourceId
)

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 'AzureAppGWIPMigrate' -ErrorAction SilentlyContinue).Version
    $LatestScriptVersion = (Find-Script -Name 'AzureAppGWIPMigrate' -ErrorAction SilentlyContinue).Version
    if(!$InstalledScriptVersion)
    {
        Write-Warning("You have manually downloaded the IP 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/ipmigratescriptdownload")
        
        $confirmation = Read-Host "Are you sure you want to proceed? Press 'y' to continue, any other key for exiting"
        
        if ($confirmation -ne 'y')
        {
            exit;
            }
    }
    else
    {
        if($InstalledScriptVersion -ne $LatestScriptVersion)
        {
            Write-Warning("You have installed the IP 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 'AzureAppGWIPMigrate -Force; Install-Script -Name 'AzureAppGWIPMigrate' -RequiredVersion $LatestScriptVersion -Force'")
            
            $confirmation = Read-Host "Are you sure you want to proceed? Press 'y' to continue, any other key for exiting"
            
            if ($confirmation -ne 'y')
            {
                exit;
            }
        }
    }
}

ScriptVersionCheck

# Validations for V1 AppGw
$matchResponse = $V1ResourceId -match "/subscriptions/(.*?)/resourceGroups/"
if(!$matchResponse)
{
    Write-Warning("Invalid V1ResourceId format $V1ResourceId.")
    exit
}

# Validating set-context succeess
$subscription1 = $matches[1]
$context = Set-AzContext -Subscription $subscription1 -ErrorVariable contextFailure
if ($contextFailure)
{
    Write-Warning("Unable to set subscription $subscription1 in context. Please retry again")
    exit
}

$V1Resource = Get-AzResource -ResourceId $V1ResourceId -ErrorVariable getResourceFailure -ErrorAction SilentlyContinue

# Validating Get-Resource
if($getResourceFailure -or !$V1Resource)
{
    Write-Warning("Unable to get resource for $V1ResourceId. Please retry again")
    exit
}

$V1ResourceGroup = $V1Resource.ResourceGroupName
$V1AppGwName = $V1Resource.Name
$V1AppGw = Get-AzApplicationGateway -Name $V1AppGwName -ResourceGroupName $V1ResourceGroup -ErrorVariable getAppGwResourceFailure -ErrorAction SilentlyContinue

# Validating Get-AppGwResource Failure
if($getAppGwResourceFailure -or !$V1AppGw)
{
    Write-Warning("Unable to get application gateway resource for $V1ResourceId. Please retry again")
    exit
}


# Validations for V2 AppGw
$matchResponse = $V2ResourceId -match "/subscriptions/(.*?)/resourceGroups/"
if(!$matchResponse)
{
    Write-Warning("Invalid V2ResourceId format $V2ResourceId.")
    exit
}

# Validating set-context succeess
$subscription2 = $matches[1]
if ($subscription1 -NE $subscription2)
{
    Write-Warning("V1 AppGw subscription $subscription1 and V2 AppGw subscription $subscription2 is different. AppGw must be in same subscription")
    exit
}

$V2Resource = Get-AzResource -ResourceId $V2ResourceId -ErrorVariable getResourceFailure -ErrorAction SilentlyContinue

# Validating Get-Resource
if($getResourceFailure -or !$V2Resource)
{
    Write-Warning("Unable to get resource for $V2ResourceId. Please retry again")
    exit
}

$V2ResourceGroup = $V2Resource.ResourceGroupName
$V2AppGwName = $V2Resource.Name
$V2AppGw = Get-AzApplicationGateway -Name $V2AppGwName -ResourceGroupName $V2ResourceGroup -ErrorVariable getAppGwResourceFailure -ErrorAction SilentlyContinue

# Validating Get-AppGwResource Failure
if($getAppGwResourceFailure -or !$V2AppGw)
{
    Write-Warning("Unable to get application gateway resource for $V2ResourceId. Please retry again")
    exit
}

$V1fp = (Get-AzApplicationGatewayFrontendIPConfig -ApplicationGateway $V1AppGw | Where-Object { $_.PublicIPAddress -NE $null })
if ($V1fp)
{
    if ($V1fp.count -NE 1)
    {
        Write-Error ("Multiple Public FrontendIP are not supported for AppGw.")
        exit
    }

    $V1PipResourceId = $V1fp.PublicIPAddress.Id
}
else 
{
    Write-Error ("V1 AppGw does not have a frontend IP")
    exit
}

$V2fp = (Get-AzApplicationGatewayFrontendIPConfig -ApplicationGateway $V2AppGw | Where-Object { $_.PublicIPAddress -NE $null })
if ($V2fp)
{
    if ($V2fp.count -NE 1)
    {
        Write-Error ("Multiple Public FrontendIP are not supported for AppGw.")
        exit
    }

    $V2PipResourceId = $V2fp.PublicIPAddress.Id
}
else 
{
    Write-Error ("V2 AppGw does not have a frontend IP")
    exit
}

Write-Host("Attaching $V1PipResourceId to $V2AppGwName")

$migrationCompleted = $false
$sw = [Diagnostics.Stopwatch]::StartNew()

# Migrate IP REST API call
$path = $V1ResourceId + "/v1tov2Migration?api-version=2023-02-01"
$payload = @{V2GatewayResourceId=$V2ResourceId} | ConvertTo-Json
$response = Invoke-AzRestMethod -Method POST -Path $path -Payload $payload
$sc = $response.StatusCode
$data = ($response.Content | ConvertFrom-Json)

if ($sc -ne 202)
{
    $ErrorCode = $data.error.code
    $ErrorMessage = $data.error.message
    Write-Host "Encountered error while triggering migration" -ForegroundColor Red
    Write-Host "Error Code: $ErrorCode" -ForegroundColor Red
    Write-Host "Error Message: $ErrorMessage" -ForegroundColor Red
    exit
}

$StatusPathAndQuery = $response.Headers.Location.PathAndQuery

# polling for the status of the migration for 10 mins
$pollIntervalSeconds = 30
$maxAttempts = 20

Write-Host "Starting poll to check status of Application Gateway IP Migration Operation"
$attempt = 0

do {
    $attempt++
    Write-Host "Poll attempt $attempt of $maxAttempts..."

    $pollResponse = Invoke-AzRestMethod -Path $StatusPathAndQuery
    $sc = $pollResponse.StatusCode
    
    if ($sc -eq 200) {
        Write-Host "Application Gateway IP Migration completed successfully!"
        $sw.Stop()
        $migrationCompleted = $true
        break
    } 
    elseif ($sc -eq 201 -or $sc -eq 202) {
        Write-Host "Application Gateway IP Migration Operation is in progress."

        # Wait for the specified interval before the next poll
        if ($attempt -lt $maxAttempts) {
            Write-Host "Waiting for $pollIntervalSeconds seconds before next poll..."
            Start-Sleep -Seconds $pollIntervalSeconds
        }
    }
    else {
        Write-Host "Encountered error while polling migration status. Please contact Azure Customer Support." -ForegroundColor Red
        break
    }

} while ($attempt -lt $maxAttempts)

if ($attempt -ge $maxAttempts) 
{
    Write-Host "Maximum polling attempts reached. The Application Gateway may still be updating. Please contact Azure Customer Support" -ForegroundColor Red
}

if ($migrationCompleted -eq $true) 
{
    Write-Host "IP Migration Complete. TimeTaken : $($sw.Elapsed.TotalSeconds) seconds" -ForegroundColor Green
}
# SIG # Begin signature block
# MIIr4wYJKoZIhvcNAQcCoIIr1DCCK9ACAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCD7kQDZu9QDXYkL
# voFLTnPGJsn9NaCfY5hpRyI5qDLiaaCCEW4wggh+MIIHZqADAgECAhM2AAACAO38
# jbec3qFIAAIAAAIAMA0GCSqGSIb3DQEBCwUAMEExEzARBgoJkiaJk/IsZAEZFgNH
# QkwxEzARBgoJkiaJk/IsZAEZFgNBTUUxFTATBgNVBAMTDEFNRSBDUyBDQSAwMTAe
# Fw0yNDExMDgxMjQzMjhaFw0yNTExMDgxMjQzMjhaMCQxIjAgBgNVBAMTGU1pY3Jv
# c29mdCBBenVyZSBDb2RlIFNpZ24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
# AoIBAQC5L/UPrOpwYjxcoZC0TqqvMF1WUELvwXN+k27SrA5rohJknn7Cgbxg4hGT
# XKqpcdbtsVTN3ZY896SJ20uQ+INL5OVLzpW408nCNTPYg2LtGJbqHUjpNm0hLCJ+
# gO5Jn2T8DDzIJoUijGXj1m+hRLKb2nOIicCED2GuYBmuWXnaY7INmVEaU3peryty
# ZjDuxdyGDuiPURz8lW1SUiDzoszNp1oswVr+WjDvLDUx4HlxPsG8zUjIst0NnJ6o
# z4tNFKaUBDCetcMjQxpCETn29a1CuRddxZLjPHZHfcotr5sh1S6bNQdzVaMNsxV8
# L3wjHb7XJ6ZVm662mHEiPgpyNcLhAgMBAAGjggWKMIIFhjApBgkrBgEEAYI3FQoE
# HDAaMAwGCisGAQQBgjdbAQEwCgYIKwYBBQUHAwMwPQYJKwYBBAGCNxUHBDAwLgYm
# KwYBBAGCNxUIhpDjDYTVtHiE8Ys+hZvdFs6dEoFgg93NZoaUjDICAWQCAQ4wggJ2
# BggrBgEFBQcBAQSCAmgwggJkMGIGCCsGAQUFBzAChlZodHRwOi8vY3JsLm1pY3Jv
# c29mdC5jb20vcGtpaW5mcmEvQ2VydHMvQlkyUEtJQ1NDQTAxLkFNRS5HQkxfQU1F
# JTIwQ1MlMjBDQSUyMDAxKDIpLmNydDBSBggrBgEFBQcwAoZGaHR0cDovL2NybDEu
# YW1lLmdibC9haWEvQlkyUEtJQ1NDQTAxLkFNRS5HQkxfQU1FJTIwQ1MlMjBDQSUy
# MDAxKDIpLmNydDBSBggrBgEFBQcwAoZGaHR0cDovL2NybDIuYW1lLmdibC9haWEv
# QlkyUEtJQ1NDQTAxLkFNRS5HQkxfQU1FJTIwQ1MlMjBDQSUyMDAxKDIpLmNydDBS
# BggrBgEFBQcwAoZGaHR0cDovL2NybDMuYW1lLmdibC9haWEvQlkyUEtJQ1NDQTAx
# LkFNRS5HQkxfQU1FJTIwQ1MlMjBDQSUyMDAxKDIpLmNydDBSBggrBgEFBQcwAoZG
# aHR0cDovL2NybDQuYW1lLmdibC9haWEvQlkyUEtJQ1NDQTAxLkFNRS5HQkxfQU1F
# JTIwQ1MlMjBDQSUyMDAxKDIpLmNydDCBrQYIKwYBBQUHMAKGgaBsZGFwOi8vL0NO
# PUFNRSUyMENTJTIwQ0ElMjAwMSxDTj1BSUEsQ049UHVibGljJTIwS2V5JTIwU2Vy
# dmljZXMsQ049U2VydmljZXMsQ049Q29uZmlndXJhdGlvbixEQz1BTUUsREM9R0JM
# P2NBQ2VydGlmaWNhdGU/YmFzZT9vYmplY3RDbGFzcz1jZXJ0aWZpY2F0aW9uQXV0
# aG9yaXR5MB0GA1UdDgQWBBST/HE52ZUlmsYqZcZBdrXZ5u4ZnzAOBgNVHQ8BAf8E
# BAMCB4AwRQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3Jh
# dGlvbjEWMBQGA1UEBRMNMjM2MTY3KzUwMzE1NTCCAeYGA1UdHwSCAd0wggHZMIIB
# 1aCCAdGgggHNhj9odHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpaW5mcmEvQ1JM
# L0FNRSUyMENTJTIwQ0ElMjAwMSgyKS5jcmyGMWh0dHA6Ly9jcmwxLmFtZS5nYmwv
# Y3JsL0FNRSUyMENTJTIwQ0ElMjAwMSgyKS5jcmyGMWh0dHA6Ly9jcmwyLmFtZS5n
# YmwvY3JsL0FNRSUyMENTJTIwQ0ElMjAwMSgyKS5jcmyGMWh0dHA6Ly9jcmwzLmFt
# ZS5nYmwvY3JsL0FNRSUyMENTJTIwQ0ElMjAwMSgyKS5jcmyGMWh0dHA6Ly9jcmw0
# LmFtZS5nYmwvY3JsL0FNRSUyMENTJTIwQ0ElMjAwMSgyKS5jcmyGgb1sZGFwOi8v
# L0NOPUFNRSUyMENTJTIwQ0ElMjAwMSgyKSxDTj1CWTJQS0lDU0NBMDEsQ049Q0RQ
# LENOPVB1YmxpYyUyMEtleSUyMFNlcnZpY2VzLENOPVNlcnZpY2VzLENOPUNvbmZp
# Z3VyYXRpb24sREM9QU1FLERDPUdCTD9jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0
# P2Jhc2U/b2JqZWN0Q2xhc3M9Y1JMRGlzdHJpYnV0aW9uUG9pbnQwHwYDVR0jBBgw
# FoAUllGE4Gtve/7YBqvD8oXmKa5q+dQwHwYDVR0lBBgwFgYKKwYBBAGCN1sBAQYI
# KwYBBQUHAwMwDQYJKoZIhvcNAQELBQADggEBAEDd8Wf5RkHsB64vgn2slxDtHzSo
# It9xN/Dm3RdFjNZ0diTUPMgSPYQlSk8nIAfudnB9FLavGlvZLlyUpfrPSuikepj3
# i3pqNEFn6fNdNFv/wHMxv7hQTIDCmuoR1v1rX+w3oeleBPMnN3QmH4ff1NsynyV4
# dZdYgN9Cw9sC/S3pWZpJrbOs7YOM3vqyU6DciHhC4D9i2zByHCF2pu9nYfiQf5A2
# iUZenRvyo1E5rC+UP2VZXa4k7g66W20+zAajIKKIqEmRtWahekMkCcOIHFBY4RDA
# ybgPRSGur4VDAiZPjTXS90wQXrX9CwU20cfiCC6e76F4H95KtQjKYpzuNVAwggjo
# MIIG0KADAgECAhMfAAAAUeqP9pxzDKg7AAAAAABRMA0GCSqGSIb3DQEBCwUAMDwx
# EzARBgoJkiaJk/IsZAEZFgNHQkwxEzARBgoJkiaJk/IsZAEZFgNBTUUxEDAOBgNV
# BAMTB2FtZXJvb3QwHhcNMjEwNTIxMTg0NDE0WhcNMjYwNTIxMTg1NDE0WjBBMRMw
# EQYKCZImiZPyLGQBGRYDR0JMMRMwEQYKCZImiZPyLGQBGRYDQU1FMRUwEwYDVQQD
# EwxBTUUgQ1MgQ0EgMDEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJ
# mlIJfQGejVbXKpcyFPoFSUllalrinfEV6JMc7i+bZDoL9rNHnHDGfJgeuRIYO1LY
# /1f4oMTrhXbSaYRCS5vGc8145WcTZG908bGDCWr4GFLc411WxA+Pv2rteAcz0eHM
# H36qTQ8L0o3XOb2n+x7KJFLokXV1s6pF/WlSXsUBXGaCIIWBXyEchv+sM9eKDsUO
# LdLTITHYJQNWkiryMSEbxqdQUTVZjEz6eLRLkofDAo8pXirIYOgM770CYOiZrcKH
# K7lYOVblx22pdNawY8Te6a2dfoCaWV1QUuazg5VHiC4p/6fksgEILptOKhx9c+ia
# piNhMrHsAYx9pUtppeaFAgMBAAGjggTcMIIE2DASBgkrBgEEAYI3FQEEBQIDAgAC
# MCMGCSsGAQQBgjcVAgQWBBQSaCRCIUfL1Gu+Mc8gpMALI38/RzAdBgNVHQ4EFgQU
# llGE4Gtve/7YBqvD8oXmKa5q+dQwggEEBgNVHSUEgfwwgfkGBysGAQUCAwUGCCsG
# AQUFBwMBBggrBgEFBQcDAgYKKwYBBAGCNxQCAQYJKwYBBAGCNxUGBgorBgEEAYI3
# CgMMBgkrBgEEAYI3FQYGCCsGAQUFBwMJBggrBgEFBQgCAgYKKwYBBAGCN0ABAQYL
# KwYBBAGCNwoDBAEGCisGAQQBgjcKAwQGCSsGAQQBgjcVBQYKKwYBBAGCNxQCAgYK
# KwYBBAGCNxQCAwYIKwYBBQUHAwMGCisGAQQBgjdbAQEGCisGAQQBgjdbAgEGCisG
# AQQBgjdbAwEGCisGAQQBgjdbBQEGCisGAQQBgjdbBAEGCisGAQQBgjdbBAIwGQYJ
# KwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGGMBIGA1UdEwEB/wQI
# MAYBAf8CAQAwHwYDVR0jBBgwFoAUKV5RXmSuNLnrrJwNp4x1AdEJCygwggFoBgNV
# HR8EggFfMIIBWzCCAVegggFToIIBT4YxaHR0cDovL2NybC5taWNyb3NvZnQuY29t
# L3BraWluZnJhL2NybC9hbWVyb290LmNybIYjaHR0cDovL2NybDIuYW1lLmdibC9j
# cmwvYW1lcm9vdC5jcmyGI2h0dHA6Ly9jcmwzLmFtZS5nYmwvY3JsL2FtZXJvb3Qu
# Y3JshiNodHRwOi8vY3JsMS5hbWUuZ2JsL2NybC9hbWVyb290LmNybIaBqmxkYXA6
# Ly8vQ049YW1lcm9vdCxDTj1BTUVSb290LENOPUNEUCxDTj1QdWJsaWMlMjBLZXkl
# MjBTZXJ2aWNlcyxDTj1TZXJ2aWNlcyxDTj1Db25maWd1cmF0aW9uLERDPUFNRSxE
# Qz1HQkw/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdD9iYXNlP29iamVjdENsYXNz
# PWNSTERpc3RyaWJ1dGlvblBvaW50MIIBqwYIKwYBBQUHAQEEggGdMIIBmTBHBggr
# BgEFBQcwAoY7aHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraWluZnJhL2NlcnRz
# L0FNRVJvb3RfYW1lcm9vdC5jcnQwNwYIKwYBBQUHMAKGK2h0dHA6Ly9jcmwyLmFt
# ZS5nYmwvYWlhL0FNRVJvb3RfYW1lcm9vdC5jcnQwNwYIKwYBBQUHMAKGK2h0dHA6
# Ly9jcmwzLmFtZS5nYmwvYWlhL0FNRVJvb3RfYW1lcm9vdC5jcnQwNwYIKwYBBQUH
# MAKGK2h0dHA6Ly9jcmwxLmFtZS5nYmwvYWlhL0FNRVJvb3RfYW1lcm9vdC5jcnQw
# gaIGCCsGAQUFBzAChoGVbGRhcDovLy9DTj1hbWVyb290LENOPUFJQSxDTj1QdWJs
# aWMlMjBLZXklMjBTZXJ2aWNlcyxDTj1TZXJ2aWNlcyxDTj1Db25maWd1cmF0aW9u
# LERDPUFNRSxEQz1HQkw/Y0FDZXJ0aWZpY2F0ZT9iYXNlP29iamVjdENsYXNzPWNl
# cnRpZmljYXRpb25BdXRob3JpdHkwDQYJKoZIhvcNAQELBQADggIBAFAQI7dPD+jf
# XtGt3vJp2pyzA/HUu8hjKaRpM3opya5G3ocprRd7vdTHb8BDfRN+AD0YEmeDB5HK
# QoG6xHPI5TXuIi5sm/LeADbV3C2q0HQOygS/VT+m1W7a/752hMIn+L4ZuyxVeSBp
# fwf7oQ4YSZPh6+ngZvBHgfBaVz4O9/wcfw91QDZnTgK9zAh9yRKKls2bziPEnxeO
# ZMVNaxyV0v152PY2xjqIafIkUjK6vY9LtVFjJXenVUAmn3WCPWNFC1YTIIHw/mD2
# cTfPy7QA1pT+GPARAKt0bKtq9aCd/Ym0b5tPbpgCiRtzyb7fbNS1dE740re0COE6
# 7YV2wbeo2sXixzvLftH8L7s9xv9wV+G22qyKt6lmKLjFK1yMw4Ni5fMabcgmzRvS
# jAcbqgp3tk4a8emaaH0rz8MuuIP+yrxtREPXSqL/C5bzMzsikuDW9xH10graZzSm
# PjilzpRfRdu20/9UQmC7eVPZ4j1WNa1oqPHfzET3ChIzJ6Q9G3NPCB+7KwX0OQmK
# yv7IDimj8U/GlsHD1z+EF/fYMf8YXG15LamaOAohsw/ywO6SYSreVW+5Y0mzJutn
# BC9Cm9ozj1+/4kqksrlhZgR/CSxhFH3BTweH8gP2FEISRtShDZbuYymynY1un+Ry
# fiK9+iVTLdD1h/SxyxDpZMtimb4CgJQlMYIZyzCCGccCAQEwWDBBMRMwEQYKCZIm
# iZPyLGQBGRYDR0JMMRMwEQYKCZImiZPyLGQBGRYDQU1FMRUwEwYDVQQDEwxBTUUg
# Q1MgQ0EgMDECEzYAAAIA7fyNt5zeoUgAAgAAAgAwDQYJYIZIAWUDBAIBBQCgga4w
# GQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisG
# AQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIFV1nFla0rOPbOaV3WWqEhNtuyIAGbxA
# gRQuMLovBFLkMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8AcwBvAGYA
# dKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEBBQAEggEA
# XNLAPPFlnYtXCeUFDzmXzDk5nDpafqwRe1xx7MqmBeBfHprwxqm2RGo5tiAEQcE+
# 7vL4USMo6Kv618uGnVIbLLIqinq8RFhkRXBEZo7gLC4mSB8PpC6vifuqdwWvXE0/
# kETfrPujH8DdP1K8yavZQ4a56BpOpqV98/LNLU2+dh+CEHSp7Q3Cpxfd4KDuBY9B
# OPJ3IzOpeY6f1CNhTpndTuxWUAYxyf8OQSD0I3jEHqh2XcM1maH9XM/J+WyM/bAO
# C4dVuXGliR+WT/NTu/M7BnIeg29vlypDerNnac3VtMjoTY4fy0ObMh7PUx9Z39bQ
# bytSMBnbg735CqxWpMetYqGCF5MwghePBgorBgEEAYI3AwMBMYIXfzCCF3sGCSqG
# SIb3DQEHAqCCF2wwghdoAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsqhkiG9w0B
# CRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFlAwQCAQUA
# BCB/iDTZHKnWErGdoqXa7pwNkzaeMKYnWIXFKHOYa7CFEAIGaPAnmLX2GBMyMDI1
# MTAxNjA2MjU1Mi4wMjNaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJVUzETMBEG
# A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj
# cm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBP
# cGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046REMwMC0wNUUwLUQ5
# NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2WgghHpMIIH
# IDCCBQigAwIBAgITMwAAAgO7HlwAOGx0ygABAAACAzANBgkqhkiG9w0BAQsFADB8
# MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk
# bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1N
# aWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yNTAxMzAxOTQyNDZaFw0y
# NjA0MjIxOTQyNDZaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv
# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0
# aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMScwJQYD
# VQQLEx5uU2hpZWxkIFRTUyBFU046REMwMC0wNUUwLUQ5NDcxJTAjBgNVBAMTHE1p
# Y3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEBAQUAA4IC
# DwAwggIKAoICAQChl0MH5wAnOx8Uh8RtidF0J0yaFDHJYHTpPvRR16X1KxGDYfT8
# PrcGjCLCiaOu3K1DmUIU4Rc5olndjappNuOgzwUoj43VbbJx5PFTY/a1Z80tpqVP
# 0OoKJlUkfDPSBLFgXWj6VgayRCINtLsUasy0w5gysD7ILPZuiQjace5KxASjKf2M
# VX1qfEzYBbTGNEijSQCKwwyc0eavr4Fo3X/+sCuuAtkTWissU64k8rK60jsGRApi
# ESdfuHr0yWAmc7jTOPNeGAx6KCL2ktpnGegLDd1IlE6Bu6BSwAIFHr7zOwIlFqyQ
# uCe0SQALCbJhsT9y9iy61RJAXsU0u0TC5YYmTSbEI7g10dYx8Uj+vh9InLoKYC5D
# pKb311bYVd0bytbzlfTRslRTJgotnfCAIGMLqEqk9/2VRGu9klJi1j9nVfqyYHYr
# MPOBXcrQYW0jmKNjOL47CaEArNzhDBia1wXdJANKqMvJ8pQe2m8/cibyDM+1BVZq
# uNAov9N4tJF4ACtjX0jjXNDUMtSZoVFQH+FkWdfPWx1uBIkc97R+xRLuPjUypHZ5
# A3AALSke4TaRBvbvTBYyW2HenOT7nYLKTO4jw5Qq6cw3Z9zTKSPQ6D5lyiYpes5R
# R2MdMvJS4fCcPJFeaVOvuWFSQ/EGtVBShhmLB+5ewzFzdpf1UuJmuOQTTwIDAQAB
# o4IBSTCCAUUwHQYDVR0OBBYEFLIpWUB+EeeQ29sWe0VdzxWQGJJ9MB8GA1UdIwQY
# MBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCGTmh0dHA6
# Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUyMFRpbWUt
# U3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4wXAYIKwYB
# BQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2VydHMvTWlj
# cm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwGA1UdEwEB
# /wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQDAgeAMA0G
# CSqGSIb3DQEBCwUAA4ICAQCQEMbesD6TC08R0oYCdSC452AQrGf/O89GQ54CtgEs
# bxzwGDVUcmjXFcnaJSTNedBKVXkBgawRonP1LgxH4bzzVj2eWNmzGIwO1FlhldAP
# OHAzLBEHRoSZ4pddFtaQxoabU/N1vWyICiN60It85gnF5JD4MMXyd6pS8eADIi6T
# tjfgKPoumWa0BFQ/aEzjUrfPN1r7crK+qkmLztw/ENS7zemfyx4kGRgwY1WBfFqm
# /nFlJDPQBicqeU3dOp9hj7WqD0Rc+/4VZ6wQjesIyCkv5uhUNy2LhNDi2leYtAiI
# FpmjfNk4GngLvC2Tj9IrOMv20Srym5J/Fh7yWAiPeGs3yA3QapjZTtfr7NfzpBIJ
# Q4xT/ic4WGWqhGlRlVBI5u6Ojw3ZxSZCLg3vRC4KYypkh8FdIWoKirjidEGlXsNO
# o+UP/YG5KhebiudTBxGecfJCuuUspIdRhStHAQsjv/dAqWBLlhorq2OCaP+wFhE3
# WPgnnx5pflvlujocPgsN24++ddHrl3O1FFabW8m0UkDHSKCh8QTwTkYOwu99iExB
# VWlbYZRz2qOIBjL/ozEhtCB0auKhfTLLeuNGBUaBz+oZZ+X9UAECoMhkETjb6YfN
# aI1T7vVAaiuhBoV/JCOQT+RYZrgykyPpzpmwMNFBD1vdW/29q9nkTWoEhcEOO0L9
# NzCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZIhvcNAQEL
# BQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH
# EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNV
# BAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMB4X
# DTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMCVVMxEzAR
# BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p
# Y3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3Rh
# bXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDk4aZM
# 57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25PhdgM/9cT8dm
# 95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPFdvWGUNzB
# RMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6GnszrYBb
# fowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBpDco2LXCO
# Mcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50ZuyjLVwIYw
# XE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3EXzTdEonW
# /aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0lBw0gg/w
# EPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1qGFphAXPK
# Z6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ+QuJYfM2
# BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PAPBXbGjfH
# CBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkwEgYJKwYB
# BAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxGNSnPEP8v
# BO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARVMFMwUQYM
# KwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWljcm9zb2Z0
# LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAKBggrBgEF
# BQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD
# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvXzpoYxDBW
# BgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny
# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYIKwYBBQUH
# AQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtp
# L2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG9w0BAQsF
# AAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0xM7U518Jx
# Nj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmCVgADsAW+
# iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449xvNo32X2
# pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wMnosZiefw
# C2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDSPeZKPmY7
# T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2dY3RILLFO
# Ry3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxnGSgkujhL
# mm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+CrvsQWY9af3L
# wUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokLjzbaukz5
# m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL6Xu/OHBE
# 0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNMMIICNAIB
# ATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEnMCUGA1UE
# CxMeblNoaWVsZCBUU1MgRVNOOkRDMDAtMDVFMC1EOTQ3MSUwIwYDVQQDExxNaWNy
# b3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQDNrxRX/iz6
# ss1lBCXG8P1LFxD0e6CBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX
# YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg
# Q29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAy
# MDEwMA0GCSqGSIb3DQEBCwUAAgUA7JqmEjAiGA8yMDI1MTAxNTIzMDAzNFoYDzIw
# MjUxMDE2MjMwMDM0WjBzMDkGCisGAQQBhFkKBAExKzApMAoCBQDsmqYSAgEAMAYC
# AQACAQgwBwIBAAICEjEwCgIFAOyb95ICAQAwNgYKKwYBBAGEWQoEAjEoMCYwDAYK
# KwYBBAGEWQoDAqAKMAgCAQACAwehIKEKMAgCAQACAwGGoDANBgkqhkiG9w0BAQsF
# AAOCAQEALVGcvMACFbHH0o/2V7PO8OjUThc06KaSB5ahQ0xsHJrpZ1KUUI1nqbnH
# TUdNW0DgceTlCTA5hekgGBdLZFpvBJ/ipTQus8F8XztVOMZ5g1Y62OvjVc1kusRU
# 5o9aXEe1KwUClmzxRFnlNZx3WEiqt9T2MnfOXovfXkLFXctScMgZrrpAEco2SefR
# dtWwWpmU9D5BmXhRU74D93lpf+TJS3l4BQ5DOOeaw/GjpYDLrDI2sC3qSuYPNPiT
# R49uGE1CEu82YV1nUy2aI+NFtUKMpqas7gr+RuMImSoi2SbyL+w5OQ0RV/HStbm0
# qd5uvnmL1Rv5oRkwDIRf51nJpEf2ATGCBA0wggQJAgEBMIGTMHwxCzAJBgNVBAYT
# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD
# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBU
# aW1lLVN0YW1wIFBDQSAyMDEwAhMzAAACA7seXAA4bHTKAAEAAAIDMA0GCWCGSAFl
# AwQCAQUAoIIBSjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwLwYJKoZIhvcN
# AQkEMSIEIBnipHfGhIofsjQwqS9mkuKNtDfJNV/77eAjJXT+XCROMIH6BgsqhkiG
# 9w0BCRACLzGB6jCB5zCB5DCBvQQgSwPdG3GW9pPEU5lmelDDQOSw+ZV26jlLIr2H
# 3D76Ey0wgZgwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv
# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0
# aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAA
# AgO7HlwAOGx0ygABAAACAzAiBCBU2yqyZJj3q29SEvTQl2zR9yn54iUn+v73y5e7
# PaJEhzANBgkqhkiG9w0BAQsFAASCAgAgYOkimTsceX00KHRyMpo06TAMBY3sR6Oo
# YQIHWdSAb1RnZ2xZ4KBDr+Yrk07RoSOGTUbbT5iklKOUbwr3zJhrbKSLMcjSrNDo
# qRtKSEQEWVEjJCvrqy8E4FUwG337SkUpkf+IaVYELbbvhB2gz0AIIP2qT9Qnhptu
# sKzs2Hmu2oaDx69n0//SqhH9/4fWWh0UnR1N4IP7VmSvvLDnQI422GyGgQBWUP22
# MS/7Hs16o4Cq4pXbrqozQM8Fe4L2WHrl8/PSD/2V4Y3pIRNR54ZmMbatxAlX8TG3
# MJnI4QRqc5FCqXyjIzTEzsmlWcQlKZY9rDUlrqjGLFiR7VMDOIrjlxWz1V75XMH7
# lYRRrm9bxvminffLVW1ciSXkJANZ9sFbttfXpSh4nw83VyR1RHZN+MGsIOuMjCXF
# 6KCgP0K3vvvtJGOKRGlxYKSaURLCRPP94Ev83T+zu2vnRP4/VlCqUnrB0o0s3udM
# nTg/mXPjVB4RBLqFApbm4NhfciB51SWq3vH+IljUekyaebsg4kF0wVEsblqi3djF
# DZX0Wb0uYEiSydM+sxFnjYoiaSIze/1TtMu1/AE/LLGP8HuScmKKQ4OrQV4tVgaQ
# KNnzuy5zen37sWWrqqRxbdXbaoumC8WvuVpuJNG0mEDmn4jaOHmWf/QGcm2cxnCu
# usTIcfk8CQ==
# SIG # End signature block