NetworkValidation/NetworkChecker/networktester.ps1

#Requires -RunAsAdministrator


<#
 
    .SYNOPSIS
    Network Validation is intened to be performed using networktester.ps1 script. The script is intended to be ran from the HLH in conjuction with the configuration applied to the BMC switch from <NamePrefix>-Rack01-BMC-1-PostValidation.txt file generated by The Partner Toolkit.
 
    .DESCRIPTION
    The script checks the following:
        BMCNetwork using the DVM's IP address
            -Check if the BMC gateway is reachable
            -Check if all Point To Point IP addresses respond to ping
            -Check DNS resolution (AAD)
            -Check time sync
            -Check internet access to Azure Endpoints (Internet is tested in AAD Only)
        Public Vips network using an SLB/Public VIP IP address
            -Check if the gateway is reachable. The gateway is created on the BMC switch using the commands in <NamePrefix>-Rack01-BMC-1-PostValidation.txt
            -Check DNS resolution (AAD)
            -Check time sync
            -Check internet access to Azure Endpoints (Internet is tested in AAD Only)
 
    .EXAMPLE
    .\networktester.ps1 .\configurationdata.json -VirtualSwitchName "contoso"
    #1 Test both public vips and dvm network.
 
 
    .NOTES
 
 
    .LINK
 
#>



[CmdletBinding()]
Param(
    [Parameter(Position = 1,
        Mandatory = $true,
        ParameterSetName = "combined"
    )]
    [ValidateScript( { test-path $_ })]
    # All the configuration data required for AzureStack
    [string]
    $DeploymentDataJson,

    # The name of the virtual switch being used on the HLH
    [Parameter(Mandatory = $true)]
    [string]
    $VirtualSwitchName,

    # No Uplinks required, if the P2P interfaces do not ping to the Border this is the override to use.
    [bool]
    $NoUplinksRequired,

    # Azure Resource Manager endpoint URI for custom cloud
    [Parameter()]
    [System.Uri]
    $CustomCloudArmEndpoint

)

$ErrorActionPreference = 'Stop'

Import-Module $PSScriptRoot\NetworkValidationModules -Force -Global
Import-Module $PSScriptRoot\..\..\Microsoft.AzureStack.ReadinessChecker.Reporting.psm1 -Force -Global

$configJson = (Get-Content -Raw $DeploymentDataJson | ConvertFrom-Json ).Configdata
$fullJson = (Get-Content -Raw $DeploymentDataJson | ConvertFrom-Json )

$global:cloudId = Get-CloudID -DeploymentData $DeploymentData

#AzureAD or ADFS. If ADFS no DNS or webCheck is performed.
$connectToAzure = ($configJson.InputData.Cloud |Where-Object {$_.Id -eq $cloudId}).ConnectToAzure

if ($connectToAzure -eq "Azure Active Directory") {
    Write-AzsReadinessLog -Message "Active Directory is using $connectToAzure" -ToScreen
}
else {
    Write-AzsReadinessLog -Message "Active Directory is using $connectToAzure" -ToScreen
}

$OEM = ($configJson.EnvironmentData.Switch | where-object {$_.Type -eq "TOR1" }).make | Sort-Object -Unique


function main {
    <#
        .SYNOPSIS
            Create a VNic for testing public vips and dvm prior to Azure Stack Deployment
 
        .EXAMPLE
            main -NicName "AzSPreValNic-DVM" -VirtualSwitchName "VSwitch"
 
        .EXAMPLE
            main -NicName "AzSPreValNic-Public" -VirtualSwitchName "VSwitch"
 
    #>

    [CmdletBinding()]
    param(

        # The name of the virtual NIC
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [string]
        $NicName,

        # The name of the virtual switch
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [string]
        $VirtualSwitchName,

        # OEM for AzureStack
        [Parameter(Mandatory = $True)]
        [string]
        $OEM,

        # AAD, ADFS, ADFS Connected
        [Parameter(Mandatory = $True)]
        [string]
        $ConnectToAzure,

        # ConfigurtionDataJson produced from the PTK
        [Parameter(Mandatory = $True)]
        [PSCustomObject]
        $ConfigJson,

        # Full Json produced from the PTK
        [Parameter(Mandatory = $True)]
        [PSCustomObject]
        $FullJson,

        # No Uplinks required, if the P2P interfaces do not ping to the Border this is the override to use.
        [bool]
        $NoUplinksRequired,

        # Azure Resource Manager endpoint URI for custom cloud
        [Parameter()]
        [System.Uri]
        $CustomCloudArmEndpoint

    )

    try {
        $validationResults = @()
        $scaleUnits = $fullJson.ScaleUnits
        $routing = $ConfigJson.InputData.BorderConnectivity
        $extendedStorage = $ConfigJson.InputData.IsExtendedStorage
        $check = Invoke-CheckForVnic -Name $NicName
        # Bool value used to determine if there is no BMC switch
        $noBmc = $ConfigJson.InputData.IsNoBmc

        if (!$Check.Success) {
            Write-AzsReadinessLog -Message "$($Check.Message)" -Type 'Error' -ToScreen
            return $Check
        }

        Write-AzsReadinessLog -Message "Creating new vNIC $NicName"

        if ($NicName -eq "AzSPreValNic-Public") {
            $global:ipInfo = Get-PublicVipInfo -ConfigJson $ConfigJson
        }
        else {
            $global:ipInfo = Get-DvmIpInfo -ScaleUnits $scaleUnits -ConfigJson $ConfigJson
        }
        Write-AzsReadinessLog -Message "Executing Network Tests for BMC Management Network using the DVM's IP $($ipInfo.testIPv4Address)." -ToScreen

        $adapterInfo = Invoke-CreateNet -Name $NicName -IPAddr $ipInfo.testIPv4Address -SubnetMaskPrefix $ipInfo.IPv4MaskBits -VirtualSwitchName $VirtualSwitchName -Verbose:$VerbosePreference

        if ($adapterInfo.Success -eq $False) {
            Write-AzsReadinessLog -Message "$($adapterInfo.Message)" -Type 'Error' -ToScreen
            return $adapterInfo
        }

        $nicReady = Invoke-TestNicReady -IPAddr $ipInfo.testIPv4Address -NicName $NicName -Verbose:$VerbosePreference
        if (!$nicReady.Success) {

            Write-AzsReadinessLog -Message "removing vNic $NicName and associated configuration due to address conflict"

            Remove-VMNetworkAdapter -ManagementOS -Name $NicName

            Write-AzsReadinessLog -Message "$($nicReady.Message)" -Type 'Error' -ToScreen

            return $nicReady
        }

        Write-AzsReadinessLog -Message "vNIC $NicName is now active"
        Write-AzsReadinessLog -Message "Testing connectivity to vNIC $NicName gateway $($ipInfo.IPV4Gateway)..."

        $nicTest = Invoke-TestNicGateway -gateway $ipInfo.IPV4Gateway -networkName $ipInfo.networkName
        if (!$nicTest.Success) {
            Write-AzsReadinessLog -Message "$($nicTest.Message)" -Type 'Error' -ToScreen
            return $nicTest
        }
        else {
            Write-AzsReadinessLog -Message $($nicTest.Message)
        }

        if ($NicName -ne "AzSPreValNic-Public") {
            #PingTest only from DVM IP to avoid being blocked by ACL's
            $splat = @{
                IpConfigData = $ConfigJson.IPConfigData
                AdapterInfo = $adapterInfo.adapterInfo
                IpInfo = $ipInfo
                OEM = $OEM
                Routing = $routing
                ExtendedStorage = $extendedStorage
                IsNoBMC = $noBmc
                NoUplinksRequired = $NoUplinksRequired
                Verbose = $VerbosePreference
            }
            $pingTesting = Invoke-PingTesting @splat
            if ($pingTesting.Success -eq $false) {
                return $pingTesting
            }
        }

        #test dvm ntp time sync ability
        $timeServer = [string]$($($ConfigJson.inputData.cloud | Where-Object {$_.ID -match $cloudId} ).TimeServer )

        #set routes for ntp server and test
        Write-AzsReadinessLog -Message "Source IP $($ipInfo.testIPv4Address) for testing Customer Provided NTP Server: $($timeServer)"

        $ntpResult = Invoke-NtpTesting -adapterInfo ($adapterInfo.adapterInfo) -timeServer $timeServer -ipInfo $ipInfo -Verbose:$VerbosePreference
        if ($ntpResult.Success) {
            Write-AzsReadinessLog -Message "$($ntpResult.Message)"
            $hash = @{
                Test = 'dvm_ntpServer'
                Result = 'OK'
                FailureDetail = $null
                OutputObject = $null
            }
            Write-AzsResult -in $hash
            $validationResults += $hash
        }
        else {
            Write-AzsReadinessLog -Message "$($ntpResult.Message)" -Type 'Error'

            $traceRT = @{
                "TraceRoute" = (Trace-ToDestination -configJson $ConfigJson -DestIP $timeServer -BmcGatewayIP $ipInfo.IPV4Gateway).TraceRoute
            }

            $ntpResult += $traceRT
            $ntpMessage = $ntpResult.Message
            Write-AzsReadinessLog -Message "$($ntpMessage)" -Type 'Error'
            $hash = @{
                Test = 'dvm_ntpServer'
                Result = 'Fail'
                FailureDetail = $ntpMessage
                OutputObject = $null
            }
            Write-AzsResult -in $hash
            $validationResults += $hash

            return $ntpResult
        }

        $dnsServers = $($($ConfigJson.inputData.cloud | Where-object {$_.ID -match $cloudId} ).DNSForwarder )

        if ( $ConnectToAzure -eq "Azure Active Directory" ) {

            #Get the Azure Env from ConfigJson
            $azureEnvironment = ($configJson.InputData.Cloud |Where-Object {$_.Id -eq $cloudId}).InfraAzureEnvironment

            if ($azureEnvironment -eq 'CustomCloud') {

                # Test DNS to CustomCloudArmEndpoint
                $customCloudRoutes = Invoke-DnsTesting -AdapterInfo $adapterInfo.adapterInfo -DnsServers $dnsServers -IpInfo $ipInfo -HostnameToResolve (([System.Uri]$CustomCloudArmEndpoint).Host) -Verbose:$VerbosePreference

                $checkCustomCloudRoutes = Invoke-DnsCheck -Routes $customCloudRoutes -ConfigJson $ConfigJson -IpInfo $ipInfo
                if ($checkCustomCloudRoutes.Success -eq $False) {
                    $hash = @{
                        Test = "dvm_dnsServer"
                        Result = 'Fail'
                        FailureDetail = $customCloudRoutes.Message
                        OutputObject = $null
                    }
                    Write-AzsResult -in $hash
                    $validationResults += $hash
                    return $checkCustomCloudRoutes
                }

                # Then Get-AzureURIs because DNS and routing need to be established prior to making the webrequest to Azure.
                $azureEndpoints = Get-AzureURIs -AzureEnvironment $azureEnvironment -CustomCloudARMEndpoint $CustomCloudArmEndpoint -Ips $customCloudRoutes.ips

            }
            else {
                $azureEndpoints = Get-AzureURIs -AzureEnvironment $azureEnvironment
            }


            foreach ($endPoint in $azureEndpoints.keys) {
                $uri = $azureEndpoints.$endPoint
                if ([system.uri]::IsWellFormedUriString($uri,[System.UriKind]::Absolute) -eq $false){
                    $msg = "The URI is not valid or is null: URI=$uri"
                    Write-AzsReadinessLog -Message  $msg
                    Throw $msg
                }

                Write-AzsReadinessLog -Message  "Attempting DNS resolution for URI: $uri"

                $routes = Invoke-DnsTesting -AdapterInfo $adapterInfo.adapterInfo -DnsServers $dnsServers -IpInfo $ipInfo -HostnameToResolve (([System.Uri]$uri).Host) -Verbose:$VerbosePreference

                $checkDns = Invoke-DnsCheck -Routes $routes -ConfigJson $ConfigJson -IpInfo $ipInfo
                if ($checkDns.Success -eq $False) {
                    $hash = @{
                        Test = "dvm_dnsServer"
                        Result = 'Fail'
                        FailureDetail = $routes.Message
                        OutputObject = $null
                    }
                    Write-AzsResult -in $hash
                    $validationResults += $hash
                    return $checkDns
                }

                #Azure Active Directory requires internet access for deployment
                Write-AzsReadinessLog -Message  "Source IP $($ipInfo.testIPv4Address) for Testing $uri($($routes.ips[0]))"

                $webCheck = Invoke-WebCheck -Ips $routes.ips -IpInfo $ipInfo -Domain (([System.Uri]$uri).Host) -Verbose:$VerbosePreference
                Write-AzsReadinessLog -Message $($webCheck.Message)

                Invoke-WebResults -WebCheck $webCheck -Routes $routes -ConfigJson $ConfigJson -IpInfo $ipInfo
                if (!$webCheck.Success) {

                    # also include the dns results
                    $hash = @{
                        Test = "dvm_dnsServer"
                        Result = 'OK'
                        FailureDetail = $null
                        OutputObject = $null
                    }
                    Write-AzsResult -in $hash
                    $validationResults += $hash

                    # WebRequest Results.
                    $hash = @{
                        Test = "dvm_webRequests"
                        Result = 'Fail'
                        FailureDetail = $webCheck.Message
                        OutputObject = $null
                    }
                    Write-AzsResult -in $hash
                    $validationResults += $hash
                    return $webCheck
                }
                Remove-Variable webCheck
            }

            # after the loop we know dns and webrequests are working.
            $hash = @{
                Test = "dvm_dnsServer"
                Result = 'OK'
                FailureDetail = $null
                OutputObject = $null
            }
            Write-AzsResult -in $hash
            $validationResults += $hash
            $hash = @{
                Test = "dvm_webRequests"
                Result = 'OK'
                FailureDetail = $null
                OutputObject = $null
            }
            Write-AzsResult -in $hash
            $validationResults += $hash

            $returnData = @{ "success" = $True }
        }
        else {

            #no web check needed, setting success to true.
            $returnData = @{ "success" = $True }
        }

        Write-AzsReadinessLog -Message "removing vNic $nicName and associated configuration"
        Remove-VMNetworkAdapter -ManagementOS -Name $nicName
        $returnData.ValidationResults = $validationResults
        return $returnData
    }
    catch {
        $file = $PSItem.InvocationInfo.ScriptName
        $line = $($PSItem.InvocationInfo.ScriptLineNumber)
        $message = $($PSItem.Exception.Message)
        Write-Verbose -Message ("ERROR: " + $file + ":" + $line) -Verbose
        Write-Verbose -Message $message -Verbose
        Write-Error -Message $message
    }
}

#dvm check first
if ($CustomCloudArmEndpoint){
    $dvm = main -NicName "$CloudID-AzSPreValNic-DVM" -VirtualSwitchName $VirtualSwitchName -OEM $OEM -ConfigJson $configJson -ConnectToAzure $connectToAzure -FullJson $fullJson -NoUplinksRequired $NoUplinksRequired -CustomCloudArmEndpoint $CustomCloudArmEndpoint
}
else {
    $dvm = main -NicName "$CloudID-AzSPreValNic-DVM" -VirtualSwitchName $VirtualSwitchName -OEM $OEM -ConfigJson $configJson -ConnectToAzure $connectToAzure -FullJson $fullJson -NoUplinksRequired $NoUplinksRequired
}


if (!$dvm.Success) {
    $returnData = [PSCustomObject]@{
        Success = $False
        Message = $dvm.Message
        ValidationResults = $dvm.ValidationResults
    }
    return $returnData
}

#All network connectivity works at this point.
$returnData = [PSCustomObject]@{
    Success = $True
    Message = "BMC Management Network using the DVM($($ipInfo.testIPv4Address)) has the proper network connectivity."
    ValidationResults = $dvm.ValidationResults
}

Write-AzsReadinessLog -Message $($returnData.Message)

return $returnData
# SIG # Begin signature block
# MIIjhgYJKoZIhvcNAQcCoIIjdzCCI3MCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDbNqCpWKWRM9ni
# WsWzLsUTXLOuYlBrJ3NAkDMTGciW76CCDYEwggX/MIID56ADAgECAhMzAAACUosz
# qviV8znbAAAAAAJSMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjEwOTAyMTgzMjU5WhcNMjIwOTAxMTgzMjU5WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDQ5M+Ps/X7BNuv5B/0I6uoDwj0NJOo1KrVQqO7ggRXccklyTrWL4xMShjIou2I
# sbYnF67wXzVAq5Om4oe+LfzSDOzjcb6ms00gBo0OQaqwQ1BijyJ7NvDf80I1fW9O
# L76Kt0Wpc2zrGhzcHdb7upPrvxvSNNUvxK3sgw7YTt31410vpEp8yfBEl/hd8ZzA
# v47DCgJ5j1zm295s1RVZHNp6MoiQFVOECm4AwK2l28i+YER1JO4IplTH44uvzX9o
# RnJHaMvWzZEpozPy4jNO2DDqbcNs4zh7AWMhE1PWFVA+CHI/En5nASvCvLmuR/t8
# q4bc8XR8QIZJQSp+2U6m2ldNAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUNZJaEUGL2Guwt7ZOAu4efEYXedEw
# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1
# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDY3NTk3MB8GA1UdIwQYMBaAFEhu
# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu
# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w
# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx
# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAFkk3
# uSxkTEBh1NtAl7BivIEsAWdgX1qZ+EdZMYbQKasY6IhSLXRMxF1B3OKdR9K/kccp
# kvNcGl8D7YyYS4mhCUMBR+VLrg3f8PUj38A9V5aiY2/Jok7WZFOAmjPRNNGnyeg7
# l0lTiThFqE+2aOs6+heegqAdelGgNJKRHLWRuhGKuLIw5lkgx9Ky+QvZrn/Ddi8u
# TIgWKp+MGG8xY6PBvvjgt9jQShlnPrZ3UY8Bvwy6rynhXBaV0V0TTL0gEx7eh/K1
# o8Miaru6s/7FyqOLeUS4vTHh9TgBL5DtxCYurXbSBVtL1Fj44+Od/6cmC9mmvrti
# yG709Y3Rd3YdJj2f3GJq7Y7KdWq0QYhatKhBeg4fxjhg0yut2g6aM1mxjNPrE48z
# 6HWCNGu9gMK5ZudldRw4a45Z06Aoktof0CqOyTErvq0YjoE4Xpa0+87T/PVUXNqf
# 7Y+qSU7+9LtLQuMYR4w3cSPjuNusvLf9gBnch5RqM7kaDtYWDgLyB42EfsxeMqwK
# WwA+TVi0HrWRqfSx2olbE56hJcEkMjOSKz3sRuupFCX3UroyYf52L+2iVTrda8XW
# esPG62Mnn3T8AuLfzeJFuAbfOSERx7IFZO92UPoXE1uEjL5skl1yTZB3MubgOA4F
# 8KoRNhviFAEST+nG8c8uIsbZeb08SeYQMqjVEmkwggd6MIIFYqADAgECAgphDpDS
# 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/BvW1taslScxMNelDNMYIVWzCCFVcCAQEwgZUwfjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAlKLM6r4lfM52wAAAAACUjAN
# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgo3Rqf50a
# qMEaigZSxbBcgyUrjVGY3f9CCGDz6OutYMYwQgYKKwYBBAGCNwIBDDE0MDKgFIAS
# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN
# BgkqhkiG9w0BAQEFAASCAQAdB6sdOr7behJ8N5PBJ9h6wn5i6wO0QTRnR9Qyv5kt
# aDuaqYPTaFrHhN44tWKEwFeG+Fjhj20lI9tFEELR1W+nSKPwG5CAcReQ+5iTXrjR
# 283V9o3AfmflHN3ka9/HPl5ePfNzltiOmcD+QbqVDnVP6YeN6YSPW8MpKOKZYWc1
# 3sBfnuEvgWhu2s+YBR1VMc6XOYIFc1btN87L0EokKg/wOogc10te1Q5x8x3RLgT9
# xB9YFqe2nWcw8t34/l5P7aCUuZDeNMQj/25CQ8TeDQOeYwRiMLLxkt9XFWzXhbxp
# aiPBjAjZxd9Zl9qNSXv4lVjkfP5HLor4kzyBEpAjiBzSoYIS5TCCEuEGCisGAQQB
# gjcDAwExghLRMIISzQYJKoZIhvcNAQcCoIISvjCCEroCAQMxDzANBglghkgBZQME
# AgEFADCCAVEGCyqGSIb3DQEJEAEEoIIBQASCATwwggE4AgEBBgorBgEEAYRZCgMB
# MDEwDQYJYIZIAWUDBAIBBQAEID1En1UtkYvtuetYeU0eB0H3TkC/t6J9YKCz+dk5
# C7yVAgZhgBtpOX0YEzIwMjExMTE1MTQ1NjEwLjc3NFowBIACAfSggdCkgc0wgcox
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1p
# Y3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMxJjAkBgNVBAsTHVRoYWxlcyBUU1Mg
# RVNOOjEyQkMtRTNBRS03NEVCMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFt
# cCBTZXJ2aWNloIIOPDCCBPEwggPZoAMCAQICEzMAAAFT0oJyRWxX44sAAAAAAVMw
# DQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0
# b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh
# dGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcN
# MjAxMTEyMTgyNjA1WhcNMjIwMjExMTgyNjA1WjCByjELMAkGA1UEBhMCVVMxEzAR
# BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p
# Y3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2Eg
# T3BlcmF0aW9uczEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046MTJCQy1FM0FFLTc0
# RUIxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggEiMA0G
# CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC167Z0B7Mb+bNCAuXC6EGJsFvgVB6i
# 30/jl4gmtUwoQG5z3WpxQHNJj+t2I4fpETQQLoxioHSiR+nbJFRnT3WCjc6UwbGB
# MnQJgzgzVXofVPHICimiYNEiHy86PvCEpWT8vb6jfFcaOVoMe+4wN2NqblOWA4o6
# wmEQUFmrbpKZB+cRVJF7WE1L6NDfRiPE9XRfRk+zzZd+MJhs3eWRr7jVdG10vVEl
# V3hq0YFzfho7guP3L0bMxikRy3pPe4W6g7Qwf5McxDpM0Aatz9CNkscAPyh4jZOF
# 0f7diL1BGET/c4vp8iQTlfKqEhCbt3Fi/Hq54cwXjE1aUxzQva5b/ebtAgMBAAGj
# ggEbMIIBFzAdBgNVHQ4EFgQUxqKLc0gqqbMwGUC34HBtrDFWRHUwHwYDVR0jBBgw
# FoAU1WM6XIoxkPNDe3xGG8UzaFqFbVUwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDov
# L2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljVGltU3RhUENB
# XzIwMTAtMDctMDEuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0
# cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNUaW1TdGFQQ0FfMjAx
# MC0wNy0wMS5jcnQwDAYDVR0TAQH/BAIwADATBgNVHSUEDDAKBggrBgEFBQcDCDAN
# BgkqhkiG9w0BAQsFAAOCAQEAlGi9eXz6IEQ+xwFzSaJR3JxHsaiegym9DsRXcpfd
# 7frlTUZ/5iVkyFq8YXyKoEI+7bVOzJlTf3PF8UdYCy4ysWae6szI+bsz29boYoRl
# wSaxdQOg9hwlX1MkfdMnhJ+Iiw35EVYp7rMVjdN9i6aZHldKP/PDQNu+uzEHobKN
# LpZsxe+LI/gdxuIiFJDO3O9eTun07xWHfhnIxJ2wS7y5PEHpdxZTQX2nvWR0bjdm
# t5r6hy5X0sND1XZiaU2apl1Wb9Ha2bnO4A8lre7wZjS+i8XBVVJaE4LXmcTFeQll
# Zldvq8yBmvDo4wvjFOFVckqyBSpCMMJpRqzkodGDxJ06NDCCBnEwggRZoAMCAQIC
# CmEJgSoAAAAAAAIwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRp
# ZmljYXRlIEF1dGhvcml0eSAyMDEwMB4XDTEwMDcwMTIxMzY1NVoXDTI1MDcwMTIx
# NDY1NVowfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV
# BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQG
# A1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggEiMA0GCSqGSIb3
# DQEBAQUAA4IBDwAwggEKAoIBAQCpHQ28dxGKOiDs/BOX9fp/aZRrdFQQ1aUKAIKF
# ++18aEssX8XD5WHCdrc+Zitb8BVTJwQxH0EbGpUdzgkTjnxhMFmxMEQP8WCIhFRD
# DNdNuDgIs0Ldk6zWczBXJoKjRQ3Q6vVHgc2/JGAyWGBG8lhHhjKEHnRhZ5FfgVSx
# z5NMksHEpl3RYRNuKMYa+YaAu99h/EbBJx0kZxJyGiGKr0tkiVBisV39dx898Fd1
# rL2KQk1AUdEPnAY+Z3/1ZsADlkR+79BL/W7lmsqxqPJ6Kgox8NpOBpG2iAg16Hgc
# sOmZzTznL0S6p/TcZL2kAcEgCZN4zfy8wMlEXV4WnAEFTyJNAgMBAAGjggHmMIIB
# 4jAQBgkrBgEEAYI3FQEEAwIBADAdBgNVHQ4EFgQU1WM6XIoxkPNDe3xGG8UzaFqF
# bVUwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud
# EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186aGMQwVgYD
# VR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwv
# cHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEB
# BE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9j
# ZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwgaAGA1UdIAEB/wSBlTCB
# kjCBjwYJKwYBBAGCNy4DMIGBMD0GCCsGAQUFBwIBFjFodHRwOi8vd3d3Lm1pY3Jv
# c29mdC5jb20vUEtJL2RvY3MvQ1BTL2RlZmF1bHQuaHRtMEAGCCsGAQUFBwICMDQe
# MiAdAEwAZQBnAGEAbABfAFAAbwBsAGkAYwB5AF8AUwB0AGEAdABlAG0AZQBuAHQA
# LiAdMA0GCSqGSIb3DQEBCwUAA4ICAQAH5ohRDeLG4Jg/gXEDPZ2joSFvs+umzPUx
# vs8F4qn++ldtGTCzwsVmyWrf9efweL3HqJ4l4/m87WtUVwgrUYJEEvu5U4zM9GAS
# inbMQEBBm9xcF/9c+V4XNZgkVkt070IQyK+/f8Z/8jd9Wj8c8pl5SpFSAK84Dxf1
# L3mBZdmptWvkx872ynoAb0swRCQiPM/tA6WWj1kpvLb9BOFwnzJKJ/1Vry/+tuWO
# M7tiX5rbV0Dp8c6ZZpCM/2pif93FSguRJuI57BlKcWOdeyFtw5yjojz6f32WapB4
# pm3S4Zz5Hfw42JT0xqUKloakvZ4argRCg7i1gJsiOCC1JeVk7Pf0v35jWSUPei45
# V3aicaoGig+JFrphpxHLmtgOR5qAxdDNp9DvfYPw4TtxCd9ddJgiCGHasFAeb73x
# 4QDf5zEHpJM692VHeOj4qEir995yfmFrb3epgcunCaw5u+zGy9iCtHLNHfS4hQEe
# gPsbiSpUObJb2sgNVZl6h3M7COaYLeqN4DMuEin1wC9UJyH3yKxO2ii4sanblrKn
# QqLJzxlBTeCG+SqaoxFmMNO7dDJL32N79ZmKLxvHIa9Zta7cRDyXUHHXodLFVeNp
# 3lfB0d4wwP3M5k37Db9dT+mdHhk4L7zPWAUu7w2gUDXa7wknHNWzfjUeCLraNtvT
# X4/edIhJEqGCAs4wggI3AgEBMIH4oYHQpIHNMIHKMQswCQYDVQQGEwJVUzETMBEG
# A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj
# cm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBP
# cGVyYXRpb25zMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjoxMkJDLUUzQUUtNzRF
# QjElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcG
# BSsOAwIaAxUAikpN7ccO1k9PKCGCvaQWKyJ50ZuggYMwgYCkfjB8MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQg
# VGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIFAOU8ZdQwIhgPMjAy
# MTExMTUxMjUxMzJaGA8yMDIxMTExNjEyNTEzMlowdzA9BgorBgEEAYRZCgQBMS8w
# LTAKAgUA5Txl1AIBADAKAgEAAgIMrAIB/zAHAgEAAgIRdjAKAgUA5T23VAIBADA2
# BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIB
# AAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAJiSmEKxZKbVbzMex1HG4XPsEfMLikUv
# Q/fvVrUDhIlfxbOKqNQM2xLSo0sIN1hivBPHalobhtqF+atSL0L9NWgODeh4uOHn
# yAQxG4qulUoZLm/M+vd/jaB6yWSk6MBfLtJvz3ua5Bhpf5VtKJEzuiWnLD6DXeMS
# hqx1HM2UfGHTMYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
# Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
# dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB
# IDIwMTACEzMAAAFT0oJyRWxX44sAAAAAAVMwDQYJYIZIAWUDBAIBBQCgggFKMBoG
# CSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgf8do8YEh
# pQ1iTUUwdITxzBGcONAVklOJC/7zzFm08fYwgfoGCyqGSIb3DQEJEAIvMYHqMIHn
# MIHkMIG9BCBQwQqPObQgCAn8l6GQQ6aaHSyDHG9sDh9qxAVojbWPADCBmDCBgKR+
# MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT
# HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABU9KCckVsV+OLAAAA
# AAFTMCIEIJUuCnsxyaow3KJWmJLGLoxYb83AqyU/h9IUnL2j03EcMA0GCSqGSIb3
# DQEBCwUABIIBAI2d5uJCq5gJHx+J3OI4YVaUeyzYW2MOBYN+EwoDge3DfSwYUPw5
# h4rKJTxlbJqz2ZbMGXmv4KQ74zzHahGzgjlvtQjXUZmQtUk6LND3n+O8J1tIoVzW
# 6vPhGG8pnUD51sAVCyRenXIxfvm9JNGsagw74W3pkdzD88V86tz2Ueq+vtmSCVg5
# eaJkzMhtEAK56yFFWvVmplGgEzIFkLjW9T0NkWTOq+X6SbBsCwmE47W3387dwHm2
# MaNMHEJg8bSeRrChDbLDBDIOk0Y1FJwNJSbUqNqOzHtNDaUt3WW75JyDCVaEsBCu
# 9CVQXKTcEQyt7lIW/TRXfSGLTeXjDr1nk5c=
# SIG # End signature block