NetworkValidation/Microsoft.AzureStack.ReadinessChecker.NetworkValidation.psm1

<#############################################################
# #
# Copyright (C) Microsoft Corporation. All rights reserved. #
# #
#############################################################>


<#
.SYNOPSIS
    Verifies network infrastructure readiness for Azure Stack deployment.
.DESCRIPTION
    Performs series of network tests from a device connected to the border switches to verify that network configuration prerequisites are met.
.EXAMPLE
    Invoke-AzsNetworkValidation -SkipTests 'DnsServer' -TimeServer pool.ntp.org
    This example runs Azure Stack Edge network validation, skips the DNS Server test, and saves the report in the default location
.EXAMPLE
    Invoke-AzsNetworkValidation -DeploymentDataPath D:\AzureStack\DeploymentData.json -CleanReport -OutputPath C:\Temp\
    This example runs Azure Stack Hub network validation, creates a new report, and saves it to C:\Temp folder
.NOTES
    This feature is in beta and may not produce the expected results.
#>

function Invoke-AzsNetworkValidation {
    [CmdletBinding(DefaultParameterSetName = 'Edge')]
    param (
        # Path to Azure Stack Hub deployment configuration file created by the Deployment Worksheet.
        [Parameter(Mandatory = $true, ParameterSetName = 'Hub')]
        [System.String]
        $DeploymentDataPath,

        # List of test to run. Default is to run all tests.
        [Parameter()]
        [ValidateSet('LinkLayer', 'IPConfig', 'DnsServer', 'TimeServer')]
        [System.String[]]
        $RunTests,

        # List of test to skip. Default is to run all tests.
        [Parameter()]
        [ValidateSet('LinkLayer', 'IPConfig', 'DnsServer', 'TimeServer')]
        [System.String[]]
        $SkipTests,

        # DNS Server address(es)
        [Parameter(ParameterSetName = 'Edge')]
        [System.String[]]
        $DnsServer,

        # DNS name to resolve for DNS test
        [Parameter(ParameterSetName = 'Edge')]
        [Parameter(ParameterSetName = 'Hub')]
        [System.String]
        $DnsName = 'graph.windows.net',

        # Time Server address(es)
        [Parameter(ParameterSetName = 'Edge')]
        [System.String[]]
        $TimeServer,

        # Directory path for log and report output
        [Parameter(HelpMessage = 'Directory path for log and report output')]
        [string]$OutputPath = "$env:TEMP\AzsReadinessChecker",

        # Remove all previous progress and create a clean report
        [Parameter(HelpMessage = 'Remove all previous progress and create a clean report')]
        [switch]$CleanReport = $false
    )

    try {
        #region Initialization
        $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
        $ProgressPreference = [System.Management.Automation.ActionPreference]::SilentlyContinue
        $Global:OutputPath = $OutputPath
        Import-Module $PSScriptRoot\..\Microsoft.AzureStack.ReadinessChecker.Reporting.psm1 -Force
        Write-Header -Invocation $MyInvocation -Params $PSBoundParameters
        $readinessReport = Get-AzsReadinessProgress -Clean:$CleanReport
        $readinessReport = Add-AzsReadinessCheckerJob -Report $readinessReport
        $defaultHubTests = @('LinkLayer', 'IPConfig', 'DnsServer', 'TimeServer') # To be expanded
        $defaultEdgeTests = @('LinkLayer', 'IPConfig', 'DnsServer', 'TimeServer') # To be expanded
        $solutionType = $PSCmdlet.ParameterSetName
        Write-AzsReadinessLog -Message "Starting Azure Stack $solutionType network validation"

        # Determine which tests to run
        if (-not $RunTests) {
            Write-AzsReadinessLog -Message "Parameter RunTests not specified. Executing default set of tests for solution '$solutionType'."

            if ($solutionType -eq 'Edge') {
                $RunTests = $defaultEdgeTests
            }
            elseif ($solutionType -eq 'Hub') {
                $RunTests = $defaultHubTests
            }
        }

        foreach ($test in $RunTests) {
            if (($solutionType -eq 'Edge' -and $test -notin $defaultEdgeTests) -or ($solutionType -eq 'Hub' -and $test -notin $defaultHubTests)) {
                Write-AzsReadinessLog -Message "Test $test is not applicable to solution 'Azure Stack $solutionType'. It will be skipped." -Type 'Warning' -ToScreen
                $RunTests = $RunTests -ne $test
            }

            if ($test -in $SkipTests) {
                Write-AzsReadinessLog -Message "Will skip test '$test'"
                $RunTests = $RunTests -ne $test
            }
        }

        Write-AzsReadinessLog -Message "The following tests will be executed: $($RunTests -join ', ')" -ToScreen
        #endregion

        #region ParameterValidation
        # Read values from deployment data (if provided) and validate input parameters
        Write-AzsReadinessLog -Message "Validating input parameters" -ToScreen
        $paramValidation = $true

        if ($DeploymentDataPath) {
            Write-AzsReadinessLog -Message "Validating parameter 'DeploymentDataPath' value '$DeploymentDataPath'"

            if (Test-Path -Path $DeploymentDataPath -PathType Leaf) {
                Write-AzsReadinessLog -Message "Reading deployment configuration file '$DeploymentDataPath'"

                try {
                    $deploymentData = Get-Content -Path $DeploymentDataPath -Raw | ConvertFrom-Json
                    $DnsServer = $deploymentData.ScaleUnits.DeploymentData.DnsForwarder
                    $TimeServer = $deploymentData.ScaleUnits.DeploymentData.TimeServer
                }
                catch {
                    Write-AzsReadinessLog "Unable to read JSON from '$DeploymentDataPath'" -Type 'Error' -ToScreen
                    $paramValidation = $false
                }
            }
            else {
                Write-AzsReadinessLog -Message "Deployment Data file not found: $DeploymentDataPath" -Type 'Error' -ToScreen
                $paramValidation = $false
            }
        }

        if ($RunTests -contains 'DnsServer') {
            if ($DnsServer) {
                Write-AzsReadinessLog -Message "Validating parameter 'DnsServer' value '$DnsServer'"

                foreach ($serverAddress in $DnsServer) {
                    Write-AzsReadinessLog -Message "Parsing DNS server value '$serverAddress'"
                    $parseAddress = $null
                    if ([System.Net.IPAddress]::TryParse($serverAddress, [ref]$parseAddress)) {
                        Write-AzsReadinessLog -Message "'$serverAddress' is a valid IP address"
                    }
                    else {
                        Write-AzsReadinessLog -Message "'$serverAddress' is not a valid IP address" -Type 'Error' -ToScreen
                        $paramValidation = $false
                    }
                }
            }
            else {
                Write-AzsReadinessLog -Message "Parameter missing: DnsServer" -Type 'Error' -ToScreen
                $paramValidation = $false
            }

            Write-AzsReadinessLog -Message "Validating parameter 'DnsName' value '$DnsName'"

            if ([System.Uri]::CheckHostName($DnsName) -eq [System.UriHostNameType]::Dns) {
                Write-AzsReadinessLog -Message "'$DnsName' is a valid DNS name"
            }
            else {
                Write-AzsReadinessLog -Message "'$DnsName' is not a valid DNS name" -Type 'Error' -ToScreen
                $paramValidation = $false
            }
        }

        if ($RunTests -contains 'TimeServer') {
            if ($TimeServer) {
                Write-AzsReadinessLog -Message "Validating parameter 'TimeServer' value '$TimeServer'"

                foreach ($ntpAddress in $TimeServer) {
                    Write-AzsReadinessLog -Message "Parsing time server value '$ntpAddress'"
                    if ([System.Uri]::CheckHostName($ntpAddress) -in @([System.UriHostNameType]::Dns, [System.UriHostNameType]::IPv4)) {
                        Write-AzsReadinessLog -Message "'$ntpAddress' is a valid address of type '$([System.Uri]::CheckHostName($ntpAddress))'"
                    }
                    else {
                        Write-AzsReadinessLog -Message "'$ntpAddress' is not a valid IP address or DNS name" -Type 'Error' -ToScreen
                        $paramValidation = $false
                    }
                }
            }
            else {
                Write-AzsReadinessLog -Message "Parameter missing: TimeServer" -Type 'Error' -ToScreen
                $paramValidation = $false
            }
        }

        if (-not $paramValidation) {
            throw 'Parameter validation failed'
        }
        #endregion

        #region Main
        $result = @()

        if ($RunTests -contains 'LinkLayer') {
            $testResult = Test-LinkLayer
            Write-AzsResult -In $testResult
            $result += $testResult

            # If the link layer test fails, no other tests are applicable
            if ($testResult.Result -eq 'Fail') {
                throw 'No network connection'
            }
        }

        if ($RunTests -contains 'IPConfig') {
            $testResult = Test-IPConfig
            Write-AzsResult -In $testResult
            $result += $testResult

            # If the ip config test fails, no other tests are applicable
            if ($testResult.Result -eq 'Fail') {
                throw 'Invalid IP configuration'
            }
        }

        # If more than one interface is connected, all connectivity tests are performed from each interface, with all other interfaces shut down
        $ipRoutes = Get-NetRoute -AddressFamily IPv4 | Where-Object {$_.NextHop -ne '0.0.0.0'}
        $routedInterfaces = Get-NetIPInterface -AddressFamily IPv4 | Where-Object {$_.ConnectionState -eq 'Connected' -and $_.InterfaceIndex -in $ipRoutes.InterfaceIndex}
        $netAdapters = Get-NetAdapter | Where-Object {$_.InterfaceIndex -in $routedInterfaces.InterfaceIndex} | Select-Object -Unique | Sort-Object -Property 'Name'
        $disabledNetAdapters = @()

        foreach ($netAdapter in $netAdapters) {
            Write-AzsReadinessLog -Message "Using network adapter name '$($netAdapter.Name)', description '$($netAdapter.InterfaceDescription)'" -ToScreen

            if ((Get-NetAdapter -InterfaceIndex $netAdapter.InterfaceIndex).Status -ne 'Up') {
                Write-AzsReadinessLog -Message "Enabling network adapter name '$($netAdapter.Name)', description '$($netAdapter.InterfaceDescription)'"
                Enable-NetAdapter -InputObject $netAdapter
                $disabledNetAdapters = $disabledNetAdapters -ne $netAdapter.Name
            }

            foreach ($netAdapterToDisable in $netAdapters | Where-Object {$_.InterfaceIndex -ne $netAdapter.InterfaceIndex}) {
                if ((Get-NetAdapter -InterfaceIndex $netAdapterToDisable.InterfaceIndex).Status -eq 'Up') {
                    Write-AzsReadinessLog -Message "Disabling network adapter name '$($netAdapterToDisable.Name)', description '$($netAdapterToDisable.InterfaceDescription)'"
                    Disable-NetAdapter -InputObject $netAdapterToDisable -Confirm:$false
                    $disabledNetAdapters += $netAdapterToDisable.Name
                }
            }

            if ($RunTests -contains 'DnsServer') {
                $testResult = Test-DnsServer -DnsServer $DnsServer -ResolveName $DnsName
                Write-AzsResult -In $testResult
                $result += $testResult
            }

            if ($RunTests -contains 'TimeServer') {
                $testResult = Test-TimeServer -TimeServer $TimeServer
                Write-AzsResult -In $testResult
                $result += $testResult
            }
        }
        #endregion
    }
    catch {
        Write-AzsReadinessLog -Message "Test failed with error: $($_.Exception.Message)" -Type 'Error' -ToScreen
    }
    finally {
        if ($disabledNetAdapters) {
            Write-AzsReadinessLog -Message "Re-enabling previously disabled network adapters" -ToScreen
            Enable-NetAdapter -Name $disabledNetAdapters
        }

        # Write results to readiness report
        $readinessReport.NetworkValidation = $result
        $readinessReport = Close-AzsReadinessCheckerJob -Report $readinessReport
        Write-AzsReadinessProgress -Report $readinessReport
        Write-AzsReadinessReport -Report $readinessReport
        Write-Footer -invocation $MyInvocation
    }
}

<#
.SYNOPSIS
    Verifies link layer connectivity.
.DESCRIPTION
    Lists all network adapters and their link state and speed.
    Checks that at least one Ethernet network adapter is connected to a network.
#>

function Test-LinkLayer {
    [CmdletBinding()]
    param ()

    $test = 'Link Layer'
    Write-AzsReadinessLog -Message "Starting test '$test'"
    $netAdapterProperties = @(
        'Name'
        'InterfaceDescription'
        'InterfaceIndex'
        'MacAddress'
        'MediaType'
        'PhysicalMediaType'
        'Status'
        'AdminStatus'
        'LinkSpeed'
        'MediaConnectionState'
        'ConnectorPresent'
        'DriverInformation'
        'DriverFileName'
    )

    try {
        $netAdapters = Get-NetAdapter | Select-Object -Property $netAdapterProperties
        $outputObject = @{'NetAdapters' = $netAdapters}

        foreach ($netAdapter in $netAdapters) {
            Write-AzsReadinessLog -Message "Found network interface: Name='$($netAdapter.Name)', Description='$($netAdapter.InterfaceDescription)', Status='$($netAdapter.Status)', LinkSpeed=$($netAdapter.LinkSpeed)'"
        }

        if ($connectedEthernet = @($netAdapters | Where-Object {$_.PhysicalMediaType -eq '802.3' -and $_.Status -eq 'Up'})) {
            $result = 'OK'
            Write-AzsReadinessLog -Message "Found $($connectedEthernet.Count) connected physical network interfaces"
        }
        else {
            $result = 'Fail'
            $failureDetail = 'No connected ethernet adapters found'
            Write-AzsReadinessLog -Message "Test failed with error: $failureDetail" -Type 'Error'
        }
    }
    catch {
        $result = 'Fail'
        $failureDetail = $_.Exception.Message
        Write-AzsReadinessLog -Message "Test failed with error: $failureDetail" -Type 'Error'
    }

    Write-AzsReadinessLog -Message "Test '$test' finished, result '$result'"
    $hash = @{'Test' = $test; 'Result' = $result; 'FailureDetail' = $failureDetail; 'outputObject' = $outputObject}
    $object = New-Object -TypeName 'PSObject' -Property $hash
    return $object
}

<#
.SYNOPSIS
    Verifies IP configuration.
.DESCRIPTION
    Lists all network adapters that have an IP address assigned.
    Checks that at least one Ethernet network adapter is configured with a default gateway.
#>

function Test-IPConfig {
    [CmdletBinding()]
    param ()

    $test = 'IP Configuration'
    Write-AzsReadinessLog -Message "Starting test '$test'"
    $ipInterfaceProperties = @(
        'InterfaceIndex'
        'InterfaceAlias'
        'CompartmentId'
        'AddressFamily'
        'Forwarding'
        'ClampMss'
        'Advertising'
        'NlMtu'
        'AutomaticMetric'
        'InterfaceMetric'
        'NeighborDiscoverySupported'
        'NeighborUnreachabilityDetection'
        'BaseReachableTime'
        'ReachableTime'
        'RetransmitTime'
        'DadTransmits'
        'DadRetransmitTime'
        'RouterDiscovery'
        'ManagedAddressConfiguration'
        'OtherStatefulConfiguration'
        'WeakHostSend'
        'WeakHostReceive'
        'IgnoreDefaultRoutes'
        #'AdvertisedRouterLifetime'
        'AdvertiseDefaultRoute'
        'CurrentHopLimit'
        'ForceArpNdWolPattern'
        'DirectedMacWolPattern'
        'EcnMarking'
        'Dhcp'
        'ConnectionState'
    )
    $ipAddressProperties = @(
        'IPAddress'
        'InterfaceIndex'
        'InterfaceAlias'
        'AddressFamily'
        'Type'
        'PrefixLength'
        'PrefixOrigin'
        'SuffixOrigin'
        'AddressState'
        #'ValidLifetime'
        #'PreferredLifetime'
        'SkipAsSource'
    )
    $netRouteProperties = @(
        'DestinationPrefix'
        'InterfaceIndex'
        'InterfaceAlias'
        'CompartmentId'
        'AddressFamily'
        'NextHop'
        'Publish'
        'RouteMetric'
        'Protocol'
        #'ValidLifetime'
        #'PreferredLifetime'
    )

    try {
        $ipInterfaces = Get-NetIPInterface -AddressFamily IPv4 | Select-Object -Property $ipInterfaceProperties
        $ipAddresses = Get-NetIPAddress -AddressFamily IPv4 | Select-Object -Property $ipAddressProperties
        $ipRoutes = Get-NetRoute -AddressFamily IPv4 | Where-Object {$_.NextHop -ne '0.0.0.0'} | Select-Object -Property $netRouteProperties
        $outputObject = @{'IPInterfaces' = $ipInterfaces; 'IPAddresses' = $ipAddresses; 'IPRoutes' = $ipRoutes}

        foreach ($ipInterface in $ipInterfaces) {
            $ipAddress = $ipAddresses | Where-Object {$_.InterfaceIndex -eq $ipInterface.InterfaceIndex}
            $ipRoute = $ipRoutes | Where-Object {$_.InterfaceIndex -eq $ipInterface.InterfaceIndex -and $_.NextHop -ne '0.0.0.0'} | Select-Object -Property @{n = 'Route'; e = {$_.DestinationPrefix + ' > ' + $_.NextHop}}
            Write-AzsReadinessLog -Message "Found IP interface: Name='$($ipInterface.InterfaceAlias)', IPAddresses='$($ipAddress.IPAddress -join ', ')', DHCP='$($ipInterface.Dhcp)', ConnectionState='$($ipInterface.ConnectionState)', Routes='$($ipRoute.Route -join ', ')'"
        }

        if ($routedInterfaces = @($ipInterfaces | Where-Object {$_.ConnectionState -eq 'Connected' -and $_.InterfaceIndex -in $ipRoutes.InterfaceIndex})) {
            $result = 'OK'
            Write-AzsReadinessLog -Message "Found $($routedInterfaces.Count) connected IP-enabled interfaces with routing configuration"
        }
        else {
            $result = 'Fail'
            $failureDetail = 'No IP-enabled network interfaces with routing configuation are found'
            Write-AzsReadinessLog -Message "Test failed with error: $failureDetail" -Type 'Error'
        }
    }
    catch {
        $result = 'Fail'
        $failureDetail = $_.Exception.Message
        Write-AzsReadinessLog -Message "Test failed with error: $failureDetail" -Type 'Error'
    }

    Write-AzsReadinessLog -Message "Test '$test' finished, result '$result'"
    $hash = @{'Test' = $test; 'Result' = $result; 'FailureDetail' = $failureDetail; 'outputObject' = $outputObject}
    $object = New-Object -TypeName 'PSObject' -Property $hash
    return $object
}

<#
.SYNOPSIS
    Verifies DNS server connectivity and name resolution.
.DESCRIPTION
    Checks that each DNS server can be reached over ICMP.
    Checks that each DNS server can be reached over TCP port 53.
    Tries to resolve a DNS name.
#>

function Test-DnsServer {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [System.String[]]
        $DnsServer,

        [Parameter(Mandatory = $true)]
        [System.String]
        $ResolveName
    )

    $test = 'DNS Server'
    Write-AzsReadinessLog -Message "Starting test '$test'"
    $result = 'OK'
    $outputObject = @{}
    $failureDetail = @{}

    foreach ($serverAddress in $DnsServer) {
        $dnsTest = $null
        Write-AzsReadinessLog -Message "Testing DNS server '$serverAddress'"

        try {
            $tnc = Test-NetConnectionEx -RemoteAddress $serverAddress -Port 53
            $dnsTest = Resolve-DnsName -Name $ResolveName -DnsOnly
            Write-AzsReadinessLog -Message "Name '$ResolveName' resolved to IP addresses $($dnsTest.IPAddress -join ', ') using server $serverAddress"
        }
        catch {
            $result = 'Error'
            $failureDetail += @{$serverAddress = $_.Exception.Message}
            Write-AzsReadinessLog -Message "Error resolving DNS name: '$($_.Exception.Message)'" -Type 'Error'
        }
        finally {
            $outputObject += @{$serverAddress = @{'NetConnection' = $tnc; 'DNSTest' = $dnsTest}}
        }
    }

    Write-AzsReadinessLog -Message "Test '$test' finished, result '$result'"
    $hash = @{'Test' = $test; 'Result' = $result; 'FailureDetail' = $failureDetail; 'outputObject' = $outputObject}
    $object = New-Object -TypeName 'PSObject' -Property $hash
    return $object
}

<#
.SYNOPSIS
    Verifies time server connectivity and probes for current time.
.DESCRIPTION
    Checks that each time server can be reached over ICMP.
    Tries to obtain time from the time server.
#>

function Test-TimeServer {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [System.String[]]
        $TimeServer
    )

    $test = 'Time Server'
    Write-AzsReadinessLog -Message "Starting test '$test'"
    $result = 'OK'
    $outputObject = @{}
    $failureDetail = @{}

    foreach ($serverAddress in $TimeServer) {
        $stripchart = $null
        Write-AzsReadinessLog -Message "Testing time server '$serverAddress'"

        try {
            $tnc = Test-NetConnectionEx -RemoteAddress $serverAddress
            Write-AzsReadinessLog -Message "Trying to get NTP time from '$serverAddress'"
            $stripchart = & w32tm.exe /stripchart /computer:$serverAddress /dataonly /samples:1

            if ($stripchart.Count -ge 4 -and (($stripchart[3] | Select-String -Pattern ', +') -or ($stripchart[3] | Select-String -Pattern ', -'))) {
                Write-AzsReadinessLog -Message "Response received, current time and local offset: $($stripchart[3])"
            }
            else {
                $result = 'Error'
                $failureDetail += @{$serverAddress = $stripchart}
                Write-AzsReadinessLog -Message "Did not receive an expected response from time server '$serverAddress'. $stripchart" -Type 'Error'
            }
        }
        catch {
            $result = 'Error'
            $failureDetail += @{$serverAddress = $_.Exception.Message}
            Write-AzsReadinessLog -Message "Error occurred: '$($_.Exception.Message)'" -Type 'Error'
        }
        finally {
            $outputObject += @{$serverAddress = @{'Stripchart' = $stripchart; 'NetConnection' = $tnc}}
        }
    }

    Write-AzsReadinessLog -Message "Test '$test' finished, result '$result'"
    $hash = @{'Test' = $test; 'Result' = $result; 'FailureDetail' = $failureDetail; 'outputObject' = $outputObject}
    $object = New-Object -TypeName 'PSObject' -Property $hash
    return $object
}

<#
.SYNOPSIS
    Wrapper function for Test-NetConnection. For internal use.
.DESCRIPTION
    Tests network connection with trace route and TCP test. Writes to readiness log and returns a PSObject with results.
#>

function Test-NetConnectionEx {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [System.String]
        $RemoteAddress,

        [Parameter()]
        [System.Int32]
        $Port
    )

    $ipAddress = $null

    if ([System.Net.IPAddress]::TryParse($RemoteAddress, [ref]$ipAddress)) {
        Write-AzsReadinessLog -Message "'$RemoteAddress' is a valid IP address"
    }
    else {
        Write-AzsReadinessLog -Message "'$RemoteAddress' is a DNS name, attempting to resolve"
        $dnsName = Resolve-DnsName -Name $RemoteAddress -DnsOnly
        Write-AzsReadinessLog -Message "'$RemoteAddress' resolved to IP address '$($dnsName.IPAddress -join ', ')'"
    }

    $traceRoute = Test-NetConnection -ComputerName $RemoteAddress -TraceRoute -Hops 15 -WarningAction SilentlyContinue

    if ($traceRoute.PingSucceeded) {
        Write-AzsReadinessLog -Message "ICMP test to '$RemoteAddress' succeeded using interface '$($traceRoute.InterfaceAlias)' with source address '$($traceRoute.SourceAddress.IPAddress)'. Route: $($traceRoute.TraceRoute -join ', ')."
    }
    else {
        Write-AzsReadinessLog -Message "ICMP test to '$RemoteAddress' failed using interface '$($traceRoute.InterfaceAlias)' with source address '$($traceRoute.SourceAddress.IPAddress)'. Route: $($traceRoute.TraceRoute -join ', ')." -Type 'Warning'
    }

    if ($Port) {
        $tcpTest = Test-NetConnection -ComputerName $RemoteAddress -Port $Port -WarningAction SilentlyContinue

        if ($tcpTest.TcpTestSucceeded) {
            Write-AzsReadinessLog -Message "TCP connection to '$RemoteAddress' port '$Port' succeeded. Reverse lookup record '$($tcpTest.DnsOnlyRecords.NameHost)'."
        }
        else {
            Write-AzsReadinessLog -Message "TCP connection to '$RemoteAddress' port '$Port' failed" -Type 'Warning'
        }
    }

    $hash = @{
        'ComputerName'     = $traceRoute.ComputerName
        'RemotePort'       = $tcpTest.RemotePort
        'InterfaceAlias'   = $traceRoute.InterfaceAlias
        'PingSucceeded'    = $traceRoute.PingSucceeded
        'TcpTestSucceeded' = $tcpTest.TcpTestSucceeded
        'TraceRoute'       = $traceRoute.TraceRoute
    }
    $object = New-Object -TypeName 'PSObject' -Property $hash
    return $object
}

# SIG # Begin signature block
# MIIjkgYJKoZIhvcNAQcCoIIjgzCCI38CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCB1gshCM5entILI
# 2lqNv6GwZHKCo3f/yis5peswY18A2KCCDYEwggX/MIID56ADAgECAhMzAAABh3IX
# chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB
# znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH
# sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d
# weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ
# itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV
# Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw
# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1
# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu
# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu
# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w
# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx
# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy
# S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K
# NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV
# BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr
# qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx
# zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe
# yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g
# yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf
# AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI
# 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5
# GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea
# jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS
# 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/BvW1taslScxMNelDNMYIVZzCCFWMCAQEwgZUwfjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN
# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQg/6mTNavR
# W7CXNPFG2DpRWv1uzNh1D+0NeWshMvLoSoYwQgYKKwYBBAGCNwIBDDE0MDKgFIAS
# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN
# BgkqhkiG9w0BAQEFAASCAQAfWZR/0prXJP0gM0a/Noi8aT9zkyZ2E31r8m7wqUsz
# kig9VNuJrxvUg8JOzjx0by8lFAoyY9lvbwgv15cI8VS8lExXth64DjMchBGXt773
# Yjsv0QFX7s3MnSsweFU6hW7yez0D1IgqjAAbi8kHmALt31HB/jByxBX1gtkxQD32
# VJlMnT05LDhRbwOC1gkmrhC1nGmQ5ul85bNNgOR5C5UhSjFc+5QDKnrZQnpPOHu5
# SYIHEwSh2YyEjrHGld2qRHJIZGpDZOs4IrvWa8vrMeu5uG/1Lzt8LYMqbNFUlA7g
# ndoiBAuyIklpGJ1lbYetMnYyNWFsYSocRC24M+MOdMenoYIS8TCCEu0GCisGAQQB
# gjcDAwExghLdMIIS2QYJKoZIhvcNAQcCoIISyjCCEsYCAQMxDzANBglghkgBZQME
# AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB
# MDEwDQYJYIZIAWUDBAIBBQAEIEWDIdduJ+GZIw+aci8OqRylLTZZ6kMCCdHZQko/
# RGqbAgZfdJSPG0QYEzIwMjAxMDA3MTQwNjAwLjkzMVowBIACAfSggdSkgdEwgc4x
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p
# Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg
# VFNTIEVTTjozMkJELUUzRDUtM0IxRDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt
# U3RhbXAgU2VydmljZaCCDkQwggT1MIID3aADAgECAhMzAAABLqjSGQeT9GvoAAAA
# AAEuMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo
# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y
# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw
# MB4XDTE5MTIxOTAxMTUwNVoXDTIxMDMxNzAxMTUwNVowgc4xCzAJBgNVBAYTAlVT
# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK
# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy
# YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjozMkJE
# LUUzRDUtM0IxRDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj
# ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK7TTKJRU196LFIjMQ9q
# /UjpPhz43m5RnHgHAVp2YGni74+ltsYoO1nZ58rTbJhCQ8GYHy8B4devgbqqYPQN
# U3i+drpEtEcNLbsMr4MEq3SM+vO3a6QMFd1lDRy7IQLPJNLKvcM69Nt7ku1YyM5N
# nPNDcRJsnUb/8Yx/zcW5cWjnoj8s9fQ93BPf/J74qM1ql2CdzQV74PBisMP/tppA
# nSuNwo8I7+uWr6vfpBynSWDvJeMDrcsa62Xsm7DbB1NnSsPGAGt3RzlBV9KViciz
# e4U3fo4chdoB2+QLu17PaEmj07qq700CG5XJkpEYOjedNFiByApF7YRvQrOZQ07Q
# YiMCAwEAAaOCARswggEXMB0GA1UdDgQWBBSGmokmTguJN7uqSTQ1UhLwt1RObDAf
# BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH
# hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU
# aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF
# BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0
# YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG
# AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQCN4ARqpzCuutNqY2nWJDDXj35iaidl
# gtJ/bspYsAX8atJl19IfUKIzTuuSVU3caXZ6/YvMMYMcbsNa/4J28us23K6PWZAl
# jIj0G8QtwDMlQHjrKnrcr4FBAz6ZqvB6SrN3/Wbb0QSK/OlxsU0mfD7z87R2JM4g
# wKJvH6EILuAEtjwUGSB1NKm3Twrm51fCD0jxvWxzaUS2etvMPrh8DNrrHLJBR3UH
# vg/NXS2IzdQn20xjjsW0BUAiTf+NCRpxUvu/j80Nb1++vnejibfpQJ2IlXiJdIi+
# Hb+OL3XOr8MaDDSYOaRFAIfcoq3VPi4BkvSC8QGrvhjAZafkE7R6L5FJMIIGcTCC
# BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv
# b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcN
# MjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv
# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0
# aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIw
# DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0
# VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEw
# RA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQe
# dGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKx
# Xf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4G
# kbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEA
# AaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7
# fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC
# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX
# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v
# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI
# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0g
# AQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93
# d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYB
# BQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUA
# bQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOh
# IW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS
# +7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlK
# kVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon
# /VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOi
# PPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/
# fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCII
# YdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0
# cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7a
# KLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQ
# cdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+
# NR4Iuto229Nfj950iEkSoYIC0jCCAjsCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYT
# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD
# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP
# cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjoz
# MkJELUUzRDUtM0IxRDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy
# dmljZaIjCgEBMAcGBSsOAwIaAxUA+1/CN6BILeU1yDGo+b6WkpLoQpuggYMwgYCk
# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF
# AOMnpJIwIhgPMjAyMDEwMDcwNjIxMDZaGA8yMDIwMTAwODA2MjEwNlowdzA9Bgor
# BgEEAYRZCgQBMS8wLTAKAgUA4yekkgIBADAKAgEAAgIkkgIB/zAHAgEAAgISMzAK
# AgUA4yj2EgIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB
# AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAIX9ZwHX3ivmGlLe
# yNpJKoewAEP+8cul5jRw7RM0qHUTtODrpuzWfAd9PzZ2jnkeKscfIGvJAlLsQHLf
# ///Ea62Ew/bvzhah/5Z8K9STc2HjmkwqBH8fmmJpgU2HI1e4NkYLQRVwMa/zFiM6
# NANRuzzBGmiHXIVKRpxbP2/3i8/zMYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
# bWUtU3RhbXAgUENBIDIwMTACEzMAAAEuqNIZB5P0a+gAAAAAAS4wDQYJYIZIAWUD
# BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B
# CQQxIgQgGgKQ1zDWxbDKC8G6fgdLA/Sx1RERaVBYrMCLShzg7bcwgfoGCyqGSIb3
# DQEJEAIvMYHqMIHnMIHkMIG9BCDa/s3O8YhWiqpVN0kTeK+x2m0RAh17JpR6DiFo
# TILJKTCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u
# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp
# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB
# LqjSGQeT9GvoAAAAAAEuMCIEINv3yxa4Bk/VU8x75VzgyUZKpUud3rpTOpQLOrjo
# szxcMA0GCSqGSIb3DQEBCwUABIIBAAWD5ZkKxS4ijQj6lStJiAGf4ht6ckyKHoD7
# +bTeOeT9W8kTTHOyXCaKy+7W854g8cwWx7Vb84zH21kr+b0CyUkIOPz0L4NLQHe1
# YA8LariG8edWI49WpaxVx6hvnIg8iOp08N3OoXqMIcYN3uOSoM7AVpB2DTWyWZCb
# mJ7vuosaKDwWg1OLTYvFF7/2uPsyHTon5XfecKdPdbapVC1kO+Q9jS2+Gsf3uteY
# 8aFD5cW2IkcPWnWr3kIcMyrAGg0G3lxtwRm6/2MxiKFbJ7hUUegCynHX+bnDaVls
# tp7Hm656IbGzX1y3l59K9mX6lMrwTpTyQqURrzLqS6wWxrJxo4U=
# SIG # End signature block