install-azurevpnclient.psm1

<#
 .Synopsis
  Downloads and configures the Azure VPN Client
 
 .Description
  As well as downloading the VPN client, any private DNS resolver will be automatically configured.
 
 .Parameter Gateway
  (optional) The name of the Azure VPN Gateway resource. Can be specified if there are more than
  one gateway deployed to the subscription. If there are more than one and this parameter is not
  specified, the user is prompted to select a gateway.
 
 .Parameter Name
  (optional) The name of the VPN in the Azure VPN client will default to the name of the virtual network.
  Use this parameter to override the name that appears in the client.
 
 .Parameter AdditionalDNS
  (optional) A list of additional DNS servers to be added to the VPN configuration. Specified as an
  array of strings with IP v4 addresses.
 
 .Example
   # Install the VPN client where there is only one gateway in the subscription.
   Install-AzureVpnClient
 
 .Example
   # Install the VPN client where there is only one gateway in the subscription, overiding the name
   Install-AzureVpnClient -Name 'My VPN'
 
 .Example
   # Install the VPN client specifying a gateway in the subscription, and overiding the name
   Install-AzureVpnClient -Gateway 'gw-devgateway' -Name 'My Dev VPN'
 
 .Example
   # Install the VPN client where there is only one gateway in the subscription, overiding the name and
   # specifying additional custom DNS
   Install-AzureVpnClient -Name 'My VPN' -AdditionalDNS '10.10.2.5'
#>

function Install-AzureVpnClient {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [string] $Gateway = $null,

        [Parameter(Mandatory = $false)]
        [string] $Name = $null,

        [Parameter(Mandatory = $false)]
        [array] $AdditionalDNS = $null
    )

    # Check prerequitistes

    # Azure VPN
    if ((Get-Command -Type Application -Name 'azurevpn' -ErrorAction SilentlyContinue).Count -eq 0) {
        Write-Error "Please install the Azure VPN Client first."
        Start-Process 'ms-windows-store://pdp/?ProductId=9np355qt2sqb'
        return
    }

    # Azcopy
    $azcopyInstalled = $false
    if ((Get-Command -Type Application -Name 'azcopy' -ErrorAction SilentlyContinue).Count -gt 0) {
        $azcopyInstalled = $true
    }

    $vpnProcess = Get-Process -Name AzVpnAppx -ErrorAction SilentlyContinue
    if ($null -ne $vpnProcess) {
        Write-Error "Azure VPN Client is running. Please close the app first."
        return
    }

    # Check Azure Powershell and signed in.
    $azpwshSignedin = $false;
    if ($null -ne (Get-Module -ListAvailable -Name 'Az.Accounts' -Refresh)) {
        $azCtx = Get-AzContext
        if ($null -ne $azCtx) {
            Write-Verbose "Azure Powershell subscription set to `"$($azCtx.Subscription.Name)`""
            $azpwshSignedin = $true;
        }
    }

    if (!$azpwshSignedin) {
        Write-Error "Please ensure Azure Powershell is installed and signed in."
        return
    }

    if ($null -eq (Get-Module -ListAvailable -Name 'Az.DnsResolver')) {
        Write-Error "Please ensure the module Az.DnsResolver is installed."
        return
    }

    $gateways = Get-AzResource -ResourceType 'Microsoft.Network/virtualNetworkGateways'

    if ($gateways.Length -eq 0) {
        Write-Error "There are no Virtual Network Gateways in the subscription."
        return
    }

    if ($gateways.Length -gt 1) {
        if ([string]::IsNullOrEmpty($Gateway)) {
            $n = 0
            $gwChoices = $gateways | ForEach-Object { $n++; [System.Management.Automation.Host.ChoiceDescription]::new("&$($n) $($_.Name)", $_.Id) }
            $choice = $Host.UI.PromptForChoice("Select Gateway", "There are more than one Virtual Network Gateways in the subscription.", $gwChoices, 0)

            $rgName = $gateways[$choice].ResourceGroupName
            $Gateway = $gateways[$choice].Name
        }
        else {
            $gw = $gateways | Where-Object Name -EQ $Gateway
            if ($null -eq $gw) {
                Write-Error "The gateway `"$Gateway`" could not be found in the subscription."
                return
            }
            $rgName = $gw.ResourceGroupName
            $Gateway = $gw.Name
        }
    }
    else {
        $rgName = $gateways[0].ResourceGroupName
        $Gateway = $gateways[0].Name
    }

    Write-Verbose "Setting up client for VPN gateway `"$Gateway`" in RG `"$rgName`""

    $vng = Get-AzVirtualNetworkGateway -ResourceGroupName $rgName -Name $Gateway

    if ($null -eq $vng) {
        Write-Error "Cannot find virtual network gateway"
        return
    }

    $subNetId = $vng.IpConfigurations[0].Subnet.Id
    $vnetId = $subNetId.Substring(0, $subNetId.IndexOf('/subnets/'))

    $dnsResolverInbound = Get-AzResource -ResourceType 'Microsoft.Network/dnsResolvers/inboundEndpoints'

    foreach ($d in $dnsResolverInbound) {
        $nameparts = $d.Name.Split('/')
        $dnsep = Get-AzDnsResolverInboundEndpoint -ResourceGroupName $d.ResourceGroupName -DnsResolverName $nameparts[0] -Name $nameparts[1]
        $dnsResolverSubnetId = $dnsep.IpConfiguration[0].SubnetId
        $dnsResolverVnetId = $dnsResolverSubnetId.Substring(0, $dnsResolverSubnetId.IndexOf('/subnets/'))

        if ($dnsResolverVnetId -eq $vnetId) {
            Write-Verbose "Found Private DNS Resolver `"$($nameparts[0])`" in RG $($d.ResourceGroupName) that matches the vnet"
            $dnsEndpoint = $dnsep
            break;
        }
    }

    if ($null -eq $dnsEndpoint) {
        Write-Error "Cannot find DNS private resolver endpoint, contining without custom DNS."
    }

    Write-Output "Generating the VPN Client configuration. This may take a minute, or so."
    $clientConfig = New-AzVpnClientConfiguration -ResourceGroupName $rgName -Name $Gateway -AuthenticationMethod "EapTls"

    $tmpFile = New-TemporaryFile
    $tmpFolder = New-TemporaryFile
    Remove-Item $tmpFolder -Force

    # Use azcopy if available to download, otherwaise fall-back to Invoke-WebRequest
    if ($azcopyInstalled) {
        azcopy cp $clientConfig.VPNProfileSASUrl $tmpFile.FullName --output-level=quiet --log-level=NONE
    }
    else {
        Invoke-WebRequest -Uri $clientConfig.VPNProfileSASUrl -OutFile $tmpFile.FullName
    }

    Expand-Archive -Path $tmpFile.FullName $tmpFolder.FullName

    Remove-Item $tmpFile -Force

    if (Test-Path "$($tmpFolder.FullName)/AzureVPN/azurevpnconfig.xml" -PathType Leaf) {
        [xml]$vpnconf = get-content "$($tmpFolder.FullName)/AzureVPN/azurevpnconfig.xml"
    }
    elseif (Test-Path "$($tmpFolder.FullName)/AzureVPN/azurevpnconfig_aad.xml" -PathType Leaf) {
        [xml]$vpnconf = get-content "$($tmpFolder.FullName)/AzureVPN/azurevpnconfig_aad.xml"
    }

    if ($null -eq $vpnconf) {
        Write-Error "Cannot find vpn config file"
        return
    }

    $configFile = "$env:localappdata\Packages\Microsoft.AzureVpn_8wekyb3d8bbwe\LocalState\azurevpnconfig.xml"

    $dnsServerList = @()

    if ($null -ne $dnsEndpoint) {
        $dnsServerList += $dnsEndpoint.IPConfiguration.PrivateIPAddress
    }

    foreach ($dns in $AdditionalDNS)
    {
        $address=[IPAddress]$null
        if ([IPAddress]::TryParse($dns, [ref]$address))
        {
            $dnsServerList += $dns
        }
    }

    if ($dnsServerList.Length -gt 0)
    {
        $dnsservers = $vpnconf.AzVpnProfile.clientconfig.AppendChild($vpnconf.CreateElement("dnsservers", "http://schemas.datacontract.org/2004/07/"))
        foreach ($dns in $dnsServerList)
        {
            $dnsEntry = $dnsservers.AppendChild($vpnconf.CreateElement("dnsserver", "http://schemas.datacontract.org/2004/07/"))
            $dnsEntry.InnerText = $dns
        }
        $vpnconf.AzVpnProfile.clientconfig.RemoveAllAttributes()
    }

    if (![string]::IsNullOrEmpty($Name)) {
        $vpnconf.AzVpnProfile.name = $Name
    }

    $vpnconf.Save($configFile)

    Remove-Item $tmpFolder.FullName -Recurse -Force

    & azurevpn -i azurevpnconfig.xml

    Remove-Item $configFile -Force
}

# SIG # Begin signature block
# MIIoAgYJKoZIhvcNAQcCoIIn8zCCJ+8CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCJPp3iewCsWA+X
# e3EX35PngFXXdHqDtsoo76qGOdZ4UaCCIQUwggWNMIIEdaADAgECAhAOmxiO+dAt
# 5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV
# BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBa
# Fw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy
# dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD
# ZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
# ggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3E
# MB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKy
# unWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsF
# xl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU1
# 5zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJB
# MtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObUR
# WBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6
# nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxB
# YKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5S
# UUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+x
# q4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIB
# NjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwP
# TzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMC
# AYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp
# Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv
# bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0
# aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB
# LmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0Nc
# Vec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnov
# Lbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65Zy
# oUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFW
# juyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPF
# mCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9z
# twGpn1eqXijiuZQwggauMIIElqADAgECAhAHNje3JFR82Ees/ShmKl5bMA0GCSqG
# SIb3DQEBCwUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx
# GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRy
# dXN0ZWQgUm9vdCBHNDAeFw0yMjAzMjMwMDAwMDBaFw0zNzAzMjIyMzU5NTlaMGMx
# CzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMy
# RGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcg
# Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDGhjUGSbPBPXJJUVXH
# JQPE8pE3qZdRodbSg9GeTKJtoLDMg/la9hGhRBVCX6SI82j6ffOciQt/nR+eDzMf
# UBMLJnOWbfhXqAJ9/UO0hNoR8XOxs+4rgISKIhjf69o9xBd/qxkrPkLcZ47qUT3w
# 1lbU5ygt69OxtXXnHwZljZQp09nsad/ZkIdGAHvbREGJ3HxqV3rwN3mfXazL6IRk
# tFLydkf3YYMZ3V+0VAshaG43IbtArF+y3kp9zvU5EmfvDqVjbOSmxR3NNg1c1eYb
# qMFkdECnwHLFuk4fsbVYTXn+149zk6wsOeKlSNbwsDETqVcplicu9Yemj052FVUm
# cJgmf6AaRyBD40NjgHt1biclkJg6OBGz9vae5jtb7IHeIhTZgirHkr+g3uM+onP6
# 5x9abJTyUpURK1h0QCirc0PO30qhHGs4xSnzyqqWc0Jon7ZGs506o9UD4L/wojzK
# QtwYSH8UNM/STKvvmz3+DrhkKvp1KCRB7UK/BZxmSVJQ9FHzNklNiyDSLFc1eSuo
# 80VgvCONWPfcYd6T/jnA+bIwpUzX6ZhKWD7TA4j+s4/TXkt2ElGTyYwMO1uKIqjB
# Jgj5FBASA31fI7tk42PgpuE+9sJ0sj8eCXbsq11GdeJgo1gJASgADoRU7s7pXche
# MBK9Rp6103a50g5rmQzSM7TNsQIDAQABo4IBXTCCAVkwEgYDVR0TAQH/BAgwBgEB
# /wIBADAdBgNVHQ4EFgQUuhbZbU2FL3MpdpovdYxqII+eyG8wHwYDVR0jBBgwFoAU
# 7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoG
# CCsGAQUFBwMIMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29j
# c3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdp
# Y2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8EPDA6MDig
# NqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9v
# dEc0LmNybDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwDQYJKoZI
# hvcNAQELBQADggIBAH1ZjsCTtm+YqUQiAX5m1tghQuGwGC4QTRPPMFPOvxj7x1Bd
# 4ksp+3CKDaopafxpwc8dB+k+YMjYC+VcW9dth/qEICU0MWfNthKWb8RQTGIdDAiC
# qBa9qVbPFXONASIlzpVpP0d3+3J0FNf/q0+KLHqrhc1DX+1gtqpPkWaeLJ7giqzl
# /Yy8ZCaHbJK9nXzQcAp876i8dU+6WvepELJd6f8oVInw1YpxdmXazPByoyP6wCeC
# RK6ZJxurJB4mwbfeKuv2nrF5mYGjVoarCkXJ38SNoOeY+/umnXKvxMfBwWpx2cYT
# gAnEtp/Nh4cku0+jSbl3ZpHxcpzpSwJSpzd+k1OsOx0ISQ+UzTl63f8lY5knLD0/
# a6fxZsNBzU+2QJshIUDQtxMkzdwdeDrknq3lNHGS1yZr5Dhzq6YBT70/O3itTK37
# xJV77QpfMzmHQXh6OOmc4d0j/R0o08f56PGYX/sr2H7yRp11LB4nLCbbbxV7HhmL
# NriT1ObyF5lZynDwN7+YAN8gFk8n+2BnFqFmut1VwDophrCYoCvtlUG3OtUVmDG0
# YgkPCr2B2RP+v6TR81fZvAT6gt4y3wSJ8ADNXcL50CN/AAvkdgIm2fBldkKmKYcJ
# RyvmfxqkhQ/8mJb2VVQrH4D6wPIOK+XW+6kvRBVK5xMOHds3OBqhK/bt1nz8MIIG
# sDCCBJigAwIBAgIQCK1AsmDSnEyfXs2pvZOu2TANBgkqhkiG9w0BAQwFADBiMQsw
# CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
# ZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQw
# HhcNMjEwNDI5MDAwMDAwWhcNMzYwNDI4MjM1OTU5WjBpMQswCQYDVQQGEwJVUzEX
# MBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0
# ZWQgRzQgQ29kZSBTaWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMIICIjAN
# BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1bQvQtAorXi3XdU5WRuxiEL1M4zr
# PYGXcMW7xIUmMJ+kjmjYXPXrNCQH4UtP03hD9BfXHtr50tVnGlJPDqFX/IiZwZHM
# gQM+TXAkZLON4gh9NH1MgFcSa0OamfLFOx/y78tHWhOmTLMBICXzENOLsvsI8Irg
# nQnAZaf6mIBJNYc9URnokCF4RS6hnyzhGMIazMXuk0lwQjKP+8bqHPNlaJGiTUyC
# EUhSaN4QvRRXXegYE2XFf7JPhSxIpFaENdb5LpyqABXRN/4aBpTCfMjqGzLmysL0
# p6MDDnSlrzm2q2AS4+jWufcx4dyt5Big2MEjR0ezoQ9uo6ttmAaDG7dqZy3SvUQa
# khCBj7A7CdfHmzJawv9qYFSLScGT7eG0XOBv6yb5jNWy+TgQ5urOkfW+0/tvk2E0
# XLyTRSiDNipmKF+wc86LJiUGsoPUXPYVGUztYuBeM/Lo6OwKp7ADK5GyNnm+960I
# HnWmZcy740hQ83eRGv7bUKJGyGFYmPV8AhY8gyitOYbs1LcNU9D4R+Z1MI3sMJN2
# FKZbS110YU0/EpF23r9Yy3IQKUHw1cVtJnZoEUETWJrcJisB9IlNWdt4z4FKPkBH
# X8mBUHOFECMhWWCKZFTBzCEa6DgZfGYczXg4RTCZT/9jT0y7qg0IU0F8WD1Hs/q2
# 7IwyCQLMbDwMVhECAwEAAaOCAVkwggFVMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYD
# VR0OBBYEFGg34Ou2O/hfEYb7/mF7CIhl9E5CMB8GA1UdIwQYMBaAFOzX44LScV1k
# TN8uZz/nupiuHA9PMA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcD
# AzB3BggrBgEFBQcBAQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2lj
# ZXJ0LmNvbTBBBggrBgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29t
# L0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcnQwQwYDVR0fBDwwOjA4oDagNIYyaHR0
# cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcmww
# HAYDVR0gBBUwEzAHBgVngQwBAzAIBgZngQwBBAEwDQYJKoZIhvcNAQEMBQADggIB
# ADojRD2NCHbuj7w6mdNW4AIapfhINPMstuZ0ZveUcrEAyq9sMCcTEp6QRJ9L/Z6j
# fCbVN7w6XUhtldU/SfQnuxaBRVD9nL22heB2fjdxyyL3WqqQz/WTauPrINHVUHmI
# moqKwba9oUgYftzYgBoRGRjNYZmBVvbJ43bnxOQbX0P4PpT/djk9ntSZz0rdKOtf
# JqGVWEjVGv7XJz/9kNF2ht0csGBc8w2o7uCJob054ThO2m67Np375SFTWsPK6Wrx
# oj7bQ7gzyE84FJKZ9d3OVG3ZXQIUH0AzfAPilbLCIXVzUstG2MQ0HKKlS43Nb3Y3
# LIU/Gs4m6Ri+kAewQ3+ViCCCcPDMyu/9KTVcH4k4Vfc3iosJocsL6TEa/y4ZXDlx
# 4b6cpwoG1iZnt5LmTl/eeqxJzy6kdJKt2zyknIYf48FWGysj/4+16oh7cGvmoLr9
# Oj9FpsToFpFSi0HASIRLlk2rREDjjfAVKM7t8RhWByovEMQMCGQ8M4+uKIw8y4+I
# Cw2/O/TOHnuO77Xry7fwdxPm5yg/rBKupS8ibEH5glwVZsxsDsrFhsP2JjMMB0ug
# 0wcCampAMEhLNKhRILutG4UI4lkNbcoFUCvqShyepf2gpx8GdOfy1lKQ/a+FSCH5
# Vzu0nAPthkX0tGFuv2jiJmCG6sivqf6UHedjGzqGVnhOMIIGwjCCBKqgAwIBAgIQ
# BUSv85SdCDmmv9s/X+VhFjANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJVUzEX
# MBUGA1UEChMORGlnaUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0
# ZWQgRzQgUlNBNDA5NiBTSEEyNTYgVGltZVN0YW1waW5nIENBMB4XDTIzMDcxNDAw
# MDAwMFoXDTM0MTAxMzIzNTk1OVowSDELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRp
# Z2lDZXJ0LCBJbmMuMSAwHgYDVQQDExdEaWdpQ2VydCBUaW1lc3RhbXAgMjAyMzCC
# AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKNTRYcdg45brD5UsyPgz5/X
# 5dLnXaEOCdwvSKOXejsqnGfcYhVYwamTEafNqrJq3RApih5iY2nTWJw1cb86l+uU
# UI8cIOrHmjsvlmbjaedp/lvD1isgHMGXlLSlUIHyz8sHpjBoyoNC2vx/CSSUpIIa
# 2mq62DvKXd4ZGIX7ReoNYWyd/nFexAaaPPDFLnkPG2ZS48jWPl/aQ9OE9dDH9kgt
# XkV1lnX+3RChG4PBuOZSlbVH13gpOWvgeFmX40QrStWVzu8IF+qCZE3/I+PKhu60
# pCFkcOvV5aDaY7Mu6QXuqvYk9R28mxyyt1/f8O52fTGZZUdVnUokL6wrl76f5P17
# cz4y7lI0+9S769SgLDSb495uZBkHNwGRDxy1Uc2qTGaDiGhiu7xBG3gZbeTZD+BY
# QfvYsSzhUa+0rRUGFOpiCBPTaR58ZE2dD9/O0V6MqqtQFcmzyrzXxDtoRKOlO0L9
# c33u3Qr/eTQQfqZcClhMAD6FaXXHg2TWdc2PEnZWpST618RrIbroHzSYLzrqawGw
# 9/sqhux7UjipmAmhcbJsca8+uG+W1eEQE/5hRwqM/vC2x9XH3mwk8L9CgsqgcT2c
# kpMEtGlwJw1Pt7U20clfCKRwo+wK8REuZODLIivK8SgTIUlRfgZm0zu++uuRONhR
# B8qUt+JQofM604qDy0B7AgMBAAGjggGLMIIBhzAOBgNVHQ8BAf8EBAMCB4AwDAYD
# VR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAgBgNVHSAEGTAXMAgG
# BmeBDAEEAjALBglghkgBhv1sBwEwHwYDVR0jBBgwFoAUuhbZbU2FL3MpdpovdYxq
# II+eyG8wHQYDVR0OBBYEFKW27xPn783QZKHVVqllMaPe1eNJMFoGA1UdHwRTMFEw
# T6BNoEuGSWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRH
# NFJTQTQwOTZTSEEyNTZUaW1lU3RhbXBpbmdDQS5jcmwwgZAGCCsGAQUFBwEBBIGD
# MIGAMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wWAYIKwYB
# BQUHMAKGTGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0
# ZWRHNFJTQTQwOTZTSEEyNTZUaW1lU3RhbXBpbmdDQS5jcnQwDQYJKoZIhvcNAQEL
# BQADggIBAIEa1t6gqbWYF7xwjU+KPGic2CX/yyzkzepdIpLsjCICqbjPgKjZ5+PF
# 7SaCinEvGN1Ott5s1+FgnCvt7T1IjrhrunxdvcJhN2hJd6PrkKoS1yeF844ektrC
# QDifXcigLiV4JZ0qBXqEKZi2V3mP2yZWK7Dzp703DNiYdk9WuVLCtp04qYHnbUFc
# jGnRuSvExnvPnPp44pMadqJpddNQ5EQSviANnqlE0PjlSXcIWiHFtM+YlRpUurm8
# wWkZus8W8oM3NG6wQSbd3lqXTzON1I13fXVFoaVYJmoDRd7ZULVQjK9WvUzF4UbF
# KNOt50MAcN7MmJ4ZiQPq1JE3701S88lgIcRWR+3aEUuMMsOI5ljitts++V+wQtaP
# 4xeR0arAVeOGv6wnLEHQmjNKqDbUuXKWfpd5OEhfysLcPTLfddY2Z1qJ+Panx+VP
# NTwAvb6cKmx5AdzaROY63jg7B145WPR8czFVoIARyxQMfq68/qTreWWqaNYiyjvr
# moI1VygWy2nyMpqy0tg6uLFGhmu6F/3Ed2wVbK6rr3M66ElGt9V/zLY4wNjsHPW2
# obhDLN9OTH0eaHDAdwrUAuBcYLso/zjlUlrWrBciI0707NMX+1Br/wd3H3GXREHJ
# uEbTbDJ8WC9nR2XlG3O2mflrLAZG70Ee8PBf4NvZrZCARK+AEEGKMIIHRDCCBSyg
# AwIBAgIQAtn9muwul8xnLPeaJzbCuzANBgkqhkiG9w0BAQsFADBpMQswCQYDVQQG
# EwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0
# IFRydXN0ZWQgRzQgQ29kZSBTaWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0Ex
# MB4XDTIzMDYxOTAwMDAwMFoXDTI2MDcwNTIzNTk1OVowTDELMAkGA1UEBhMCR0Ix
# DTALBgNVBAcTBEJhdGgxFjAUBgNVBAoTDVN0ZXBoZW4gQXNrZXcxFjAUBgNVBAMT
# DVN0ZXBoZW4gQXNrZXcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDY
# 4EU96pEX+4pFhYOI+xZ9Aok38vbTyXGNdvU0LAeGQO/UhLld1tFKHAgqtzsivgDR
# 1dNAocUCLITBqclH+n1HqiCFENi3oJQr1aDIeZBu6YINUcfHS2j9whENsyvnufwe
# 1TIUk4LtsnZ7Y1px1w3B/L8NnLk9gQqJuTmH6eEk060rVUDY87uV/apyXxLa9dqV
# jupqJ4opoazKp3JRdrbav0RFwO7F4D9lREn0Bhuo1kz4Cj0J5y36E+Xl7W+tji05
# jQY4yMv2QZMIkiaByASSpbx8FqBQG5Oe+zUzu0ObRuTPodmgSXWHj+T0YhFj+QiL
# tf88cJZTZQlbOX890ea3maqU6+2dj78bhJyIaOLu3ckPnQNyfWtpsnWJXP3/Xt5T
# KSNK+/dvLYypdtDCEI/9cbiYu9OBjUVHzKb/qOW8zR5mlyEHqSE0ZBHmM0M0QQAO
# 2GmzZxvwgLfxajLmKdGhgDpoj2uWpj7rJ+cZ2sa1nFN/CYUpulp2be5bGF1LSZPw
# Je//c/ONdaHqv49m1M0XNzFlgoViqVJtiPnzLRx15pl0WQqFpkfJcjGG6BWS7aPg
# A99Iw2somSj5Ye6gK9nRlrRMIBGCOUDN12QVFko9ameuOs289a+uFzhpeFYTrgrk
# ErmZwTYlXq6pZinKqSgbjhUUlgb8kY98UszhpkEOdwIDAQABo4ICAzCCAf8wHwYD
# VR0jBBgwFoAUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHQYDVR0OBBYEFMr7jHp8BCat
# ACLnS+004YGanySKMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcD
# AzCBtQYDVR0fBIGtMIGqMFOgUaBPhk1odHRwOi8vY3JsMy5kaWdpY2VydC5jb20v
# RGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQwOTZTSEEzODQyMDIxQ0Ex
# LmNybDBToFGgT4ZNaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1
# c3RlZEc0Q29kZVNpZ25pbmdSU0E0MDk2U0hBMzg0MjAyMUNBMS5jcmwwPgYDVR0g
# BDcwNTAzBgZngQwBBAEwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3dy5kaWdpY2Vy
# dC5jb20vQ1BTMIGUBggrBgEFBQcBAQSBhzCBhDAkBggrBgEFBQcwAYYYaHR0cDov
# L29jc3AuZGlnaWNlcnQuY29tMFwGCCsGAQUFBzAChlBodHRwOi8vY2FjZXJ0cy5k
# aWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQwOTZT
# SEEzODQyMDIxQ0ExLmNydDAJBgNVHRMEAjAAMA0GCSqGSIb3DQEBCwUAA4ICAQBu
# oO8d7/9MnGq39+hMdsroT9j8I3GzJMmIz97UAZyEAGi3jOX4lK8SIYYhCWO2EDUV
# IrrQ97yC5596+faDCkKIXnFlOoElT0epBETNB3Y8C6g4XNKT9oofUhZdM3h22FVn
# Iw0ZoDDWOKJmc1ceuDuq9V0D9esaojc+3BLES3o36srKQVCRvF9N6Bb6K8AHYVC4
# IoE1nuRXgRwqk7pZGsNVCznL7j2WctTNYe67LIQeiKixVKkL/o53c5Rr/nng9GHY
# K792SLH9zelAtxKEJYUKRZbSD09bY6+DmxBY6KyqqnSE7asjMfuUkX55ERLdDp7d
# Mr2igKd58WLH9zb3Pz70fXu1XKmZKtvbNDTIb/q5gghvSWihWLTUEZtFXakjFSj8
# R46HQpqg7b4VMCB3tx0vpyUN74GU/TJyJZRUF13W0Cil7a3/YybpxYi4OYQIqEXh
# oIEK40yqJzk041fOVRrM7a/DlWCxgODS+QIj9Qg6xX8XfPnVArp91nd8+cotGZ9r
# lhvVn/FQzduvu+cv/Cf9D8uzrO4NldRzNMeP+C9XHTorjaKB0UR453T0DVO3OPMK
# IeS6glH/PIJWvKCvyjqAM4wD1cQUdT60it3o1DLUNCU6R4R9l3RQCKYTKBaY99r9
# 5AQb30arTW7ySroVh5Z2VJcr5gn7BVo3ho3iRaDcjjGCBlMwggZPAgEBMH0waTEL
# MAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYDVQQDEzhE
# aWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBSU0E0MDk2IFNIQTM4NCAy
# MDIxIENBMQIQAtn9muwul8xnLPeaJzbCuzANBglghkgBZQMEAgEFAKCBhDAYBgor
# BgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEE
# MBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCDS
# OmIg3uWZYXUe7FEuwHvCzCqQCRODjJJh41OX45akMjANBgkqhkiG9w0BAQEFAASC
# AgBYTLweVVz7UC7J0LMzanUud4FTeEvZurS+i1fD9frSgepK5sQCb49lCaQ3oimV
# 4e94TdtEY2KUK0fK01ymnbpBWBK5bptoU1JChfNiETfuLfoovbcv8qhDNfj233Hy
# NWNSbyKrEYiW4YV8ZNJ3Q2yYwo2xCor/fI8gfnZDXJtNjyTs6RSOfngbDbTl/ev1
# 3dSL4G46YwzO3DN9izEYoeXyorIXN0VrgOYQCyeJ2iOOHjdypeYCCboXrKB7ktPw
# KnaU5zIyyUnkVV3lfPkyEz6QRcu7w6bZmfFjBg58L8GkEQ/tjw27qAZoie3wK9+a
# bDH7LIk9kqFhgMSmIadQaQtdkALq6SN0tVdxCoRylxIoslHNdjsp7nzgtbCFIYxs
# pgLMBapClJtuAvN31CyTUgG2+ilkFLM488SuXZqrAR6LHyJXTiEF1q2t4iyJsheV
# XYcc09jzZCgj/AmURpeLE3ZD9fO4YWVKUBdtJenfHMMO8FsbydTRNCFygYn9lzcS
# SNh3J+lgHYKjPzjZLDzT4kweulYCmR5HxFnVvoESLKqg3zNitV2nqZkBx+GuV8PM
# rPNyefW/J0EBL1bHnIwvsvTZurfaN9LnaQtHAczIXdzaDG4gUyJ9DS+ilUpDdtEq
# LaJUm9VxA8gGyRHPHLMpPt4ECZ+dCDFmcnAUfN1qZR7BwaGCAyAwggMcBgkqhkiG
# 9w0BCQYxggMNMIIDCQIBATB3MGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdp
# Q2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2
# IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0ECEAVEr/OUnQg5pr/bP1/lYRYwDQYJYIZI
# AWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJ
# BTEPFw0yNDA2MjcxNzE0MzlaMC8GCSqGSIb3DQEJBDEiBCDNLO8Pt83YdY8vo3R+
# TJnxoiwmYToZXLC7bwLFntf1VTANBgkqhkiG9w0BAQEFAASCAgBriA0FDDws6SXy
# moAQIMvc9WTqoydGLZ0i2QX0PcREhLhYqj9VS9gFiv5+9cL7CmW73qsDAfSVUhLb
# 7jNkeZCR603/RtUpMjEGPlsobKQXMLxvvBYIW4VvsfKMO3drJwZhHgBfmo9n8Sx0
# dEq6L5zE+EpIMlSNQlLeMqpHt6eseLhK9kDTu1hmfzHvOHbMY6YRAFEfCl7938RY
# an/Q5q4KioAKgEykoX4o9Qv31oxWCLocW92Qfs7uy7REltYCGzTHavNCkwC48cB3
# kcz0wWDHPb3/nFez0ZbaArGZ0d/MFD1bPyor25SrxNhmqIUArbolJpEnl2SXRc0A
# YiGfW94H1bkG36FlVb0BzHjBjb45632FWP54c82NK1hNP4zySFC9wmj6WJz05iLt
# toyv5+9ekC7hpxlJcIdKTpAFBKXsJz1hWipbN36pbT8D93afivqPH65Zuxq0i+Fl
# IETPToWCKpi2L6mxE7OjdPnhIlIm9nIizJY98zuzUAzaFq2z1UtUY06NOyO0D2y3
# XmAfNfVZL2OnoY9B6d7jRlrgy5m4yq0Pnii2u1PcvJUHKgsEmoMmUN0INJtKOdSs
# CDm0Rimt2ty0yIQvlqvNCVkQzHcB7qpjEK4iPGMqOWw8SyOWUBmJq4xNwlCXDx/E
# LrTgoSZojcjxgl9jLJQACbP4WvocAw==
# SIG # End signature block