NetworkValidation/NetworkChecker/NetworkValidationModules/CommonFunctions.psm1

$ErrorActionPreference = 'stop'



function Get-ScriptInfo { 
    <#
        .SYNOPSIS
            Proivdes the file name and line number for the log file
 
        .EXAMPLE
            $(Get-ScriptInfo)
 
    #>

    [CmdletBinding()]

    # Only providing the script file name and not the full file path
    $scriptFileName = (split-path -leaf $MyInvocation.ScriptName )

    # Provides the line number in the file
    $scriptLineNumber = $MyInvocation.ScriptLineNumber

    $returnData = [string]($scriptFileName + ":" + $scriptLineNumber)
    
    return $returnData
}


function Trace-ToDestination {
    <#
        .SYNOPSIS
            Performs a traceroute to a destination, results will print to the console and logfile
 
        .EXAMPLE
            Trace-ToDestination -DestIP 1.2.3.4 -ConfigJson $ConfigJson
 
    #>

    param(
        # The IP address to perform traceroute to
        [Parameter(mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $DestIP,
        
        # All the configuration data required for AzureStack
        [Parameter(mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [PSCustomObject]
        $ConfigJson,

        # The gateway of the SVI of the BMC switch as this is not calucalated in the ConfigJson
        [Parameter(mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $BmcGatewayIP


    )
    
    Write-AzsReadinessLog -Message "Running command: Test-Netconnection -TraceRoute $DestIP -Hops 15"

    try {
        $traceRT = Test-NetConnection -TraceRoute $DestIP -WarningAction SilentlyContinue -Hops 15
    } 
    catch {
        Write-AzsReadinessLog -Message $($error[0]) -Type 'Error' -ToScreen
    }
    
    if (![bool]$traceRT.TraceRoute) {
        Write-AzsReadinessLog -Message "TraceRoute failed to execute" -Type 'Error' -ToScreen
    }

    # Perform a grid search by using nested loops to map a traceroute hop to an AzureStack network device
    $array = [system.collections.arraylist]@()
    $count = 0
    foreach ($hop in $traceRT.TraceRoute) {
        $count ++
        $digitCount = "{0:D2}" -f $count
        $array += @("HOP_$($digitCount)=$hop")
    }

    Write-AzsReadinessLog -Message "TRACEROUTE RESULTS"
    $array | Where-Object { Write-AzsReadinessLog -Message $_ }

    return $traceRT

}


function Invoke-CheckForVnic {
    <#
        .SYNOPSIS
            Check if the specified vNic exists and remove the adapter if it does
 
        .EXAMPLE
            Invoke-CheckForVnic -Name "AzSPreValNic-DVM"
 
    #>

    [CmdletBinding()]
    param(

        # The name of the virutal NIC, this is hard coded in the logic of the script
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Name

    )

    try {

        $adapters = Get-VMNetworkAdapter -ManagementOS | Where-Object { $_.Name -eq $Name } -ErrorAction SilentlyContinue

        if ($adapters.length -gt 0) {
            Remove-VMNetworkAdapter -ManagementOS -Name $Name
        }

        $returnData = @{
            'success' = $True
            'message' = 'cleanup complete'
        }

    }
    catch {
        $returnData = @{
            'success' = $False
            'message' = $_.Excpetion.Message 
        } 
    }

    return $returnData

}


function Invoke-CreateNet {
    <#
        .SYNOPSIS
            Create a new network adapter to be used for testing connectivity
 
        .EXAMPLE
            Invoke-CreateNet -Name $nicName -IPAddr $IpInfo.testIPv4Address -SubnetMaskPrefix $IpInfo.IPv4MaskBits -VirtualSwitchName $VirtualSwitchName
 
    #>

    [CmdletBinding()]
    param(
        # The name of the virutal NIC, this is hard coded in the logic of the script
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Name,

        # The IP Address used for sourcing traffic from for network tests
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [string]
        $IPAddr,

        # Subnet Mask of the IP Address used for sourcing traffic from for network tests
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [string]
        $SubnetMaskPrefix,

        #Virtual Switch Name that will have the new test NIC
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [string]
        $VirtualSwitchName
    )

    try {
        # Information required for adding adapters to the virtual switch
        $virtualSW = Get-VMSwitch -Name $VirtualSwitchName

        # Adds the new adapter to the vSwitch
        Add-VMNetworkAdapter -Name "$Name" -SwitchName $virtualSW.name -ManagementOS
 
        if ($name -eq 'AzSPreValNic-Public') {
            Set-VMNetworkAdapterVlan -ManagementOS -VMNetworkAdapterName $name -Untagged
            Set-VMNetworkAdapterVlan -ManagementOS -VMNetworkAdapterName $name -Access -VlanId 100
        }

        #interfaceInfo required for adding a new IP address to an interface
        $adapterInfo = Get-NetAdapter | Where-Object { $_.Name -match $Name }
        
        Write-AzsReadinessLog -Message $adapterInfo

        #Apply the IP configurations to the adapter

        $null = New-NetIPAddress -InterfaceIndex $adapterInfo.ifIndex -IPAddress $IPAddr -PrefixLength $subnetMaskPrefix -ErrorAction Stop

        Write-AzsReadinessLog -Message "Created Nic with IP $IPAddr mask $subnetMaskPrefix" 

        $returnData = @{
            'success'     = $True
            'adapterInfo' = $adapterInfo 
        }
    }    
    catch {
        $returnData = @{
            'success' = $False
            'message' = $_.Exception.Message 
        }   
    }

    return $returnData
}

function Invoke-TestNicReady {
    [CmdletBinding()]
    param(

        # IP Address of of test nic
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [string]
        $IPAddr,

        # Name of the test nic
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [string]
        $NicName

    )

    Write-AzsReadinessLog -Message  "Testing vNIC $NicName to ensure its ready to pass traffic..."
    
    <#
     
        The while loop here also serves as a test if the vNIC is ready to pass
        traffic. Once it moves from tenative to preffered its ready to pass traffic.
 
        Check for duplicate IP address: The scenario could be that deployment
        was started and a failure occured and the OEM now decides to run network
        validation, however the DVM's IP address will be a duplicate and
        sourcing traffic from the DVM IP address will fail.
 
    #>


    $addressState = "Tentative"

    while ($addressState -eq "Tentative") { 
        $addressState = (Get-NetIPAddress -IPAddress $IPAddr).AddressState
        
        Write-AzsReadinessLog -Message "Address State($IPAddr)= $addressState"
        Start-Sleep 2
    }

    if ($addressState -eq "Duplicate") {
        $returnData = @{
            "success" = $false
            "message" = "Duplicate IP address detected for $IPAddr.`n`tPossible causes: DVM is online. See logs for details."
        }
    }
    else {
        $returnData = @{ 
            'success' = $true 
            'message' = "vNic is ready."
        }
    }

    return $returnData
}

function Get-PublicVIpInfo {
    [CmdletBinding()]
    param(

        # All the config data required to delploy AzureStack
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [PSCustomObject]
        $ConfigJson

    )

    $vips = $configJson.IPConfigData | Where-Object { $_.Name -match "-VIPS" -and $_.Name -match $cloudId }
    $broadcastAddr = ($vips.IPv4BroadcastAddress.split("."))
    $gateway = $broadcastAddr.clone()
    $gateway[3] = ([int]$gateway[3] - 2)
    $gateway = $gateway -join "."
    $networkAddr = $broadcastAddr.clone()
    $networkAddr[3] = ([int]$networkAddr[3] - 3)
    $networkAddr = $networkAddr -join "."
    $loopback = $broadcastAddr.clone()
    $loopback[3] = ([int]$loopback[3] - 4)
    $loopback = $loopback -join "."
    $loopback = $broadcastAddr.clone()
    $loopback[3] = ([int]$loopback[3] - 4)
    $loopback = $loopback -join "."

    $returnData = @{
        "testIPv4Address"      = $vips.IPv4LastAddress
        "IPv4BroadcastAddress" = $vips.IPv4BroadcastAddress
        "IPv4Mask"             = "255.255.255.252"
        "IPv4MaskBits"         = "30"
        "IPV4Gateway"          = $gateway
        "IPv4NetworkAddress"   = $networkAddr
        "networkName"          = "publicVip"
    }    

    return $returnData
}


function Get-DvmIpInfo {
    [CmdletBinding()]
    param(

        # Scale Unit Data
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [PSCustomObject]
        $ScaleUnits,

        # All the config data required to delploy AzureStack
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [PSCustomObject]
        $ConfigJson

    )

    $dvmIp = $ScaleUnits.DeploymentData.DeploymentVM.IpAddress
    $bmcNet = $ConfigJson.IPConfigData | Where-Object { $_.Name -eq "Rack01-BMCMgmt" }

    $returnData = @{
        "testIPv4Address"      = $dvmIp
        "IPv4BroadcastAddress" = $bmcNet.IPv4BroadcastAddress
        "IPv4Mask"             = $bmcNet.IPv4Mask
        "IPv4MaskBits"         = $bmcNet.IPv4MaskBits
        "IPV4Gateway"          = $bmcNet.IPv4Gateway
        "IPv4NetworkAddress"   = $bmcNet.IPv4NetworkAddress
        "networkName"          = "dvm" 
    }
    
    return $returnData
}


function Invoke-TestNicGateway {
    [CmdletBinding()]
    param(

        # The IP address of the gatway to check if it pings
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Gateway,

        # The name of the network we are currently testing
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [string]
        $NetworkName

    )

    $orginalProgPref = $Global:ProgressPreference
    $Global:ProgressPreference = 'SilentlyContinue'

    $ping = $False
    $count = 0
    #while (!$ping -and $count -le 30) {
    while ($ping -eq $False -and $count -le 4) {
        $ping = Test-Connection $gateway -count 1 -Quiet
        if (!$ping) {
            $message = "Ping to $($networkName) gateway $($gateway) failed, ensure the TOR's are configured and the cabling is correct!"
            Write-AzsReadinessLog -Message $message
        }
        else {
            $message = "the $($networkName) gateway is reachable."
        }
        $count ++
    }

    $returnData = @{
        'success' = $ping
        'message' = $message
    }

    $Global:ProgressPreference = $orginalProgPref

    return $returnData
}


function Invoke-DnsTesting {
    [CmdletBinding()]
    param(

        # Information regarding the vNic used to ping the P2P IP addresses
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [ciminstance]
        $AdapterInfo,

        # The customers DNS servers
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [System.Array]
        $DnsServers,

        # IP information for the network being tested
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [System.Collections.Hashtable]
        $IpInfo,

        #The name to be resolved by DNS
        [Parameter(Mandatory = $true)]
        [String]
        $HostnameToResolve

    )

    #All dns servers need to be fucntional. Both servers will be tested here
    $dnsResults = [System.Collections.ArrayList]@()
    $logMsg = [System.Collections.ArrayList]@()
    foreach ($resolver in $dnsServers) {
        #set route to customers DNS server on AzSPreValNic NIC

        $splat = @{
            AdapterInfo = $AdapterInfo
            IPAddress   = $resolver
            IpInfo      = $IpInfo
            Verbose     = $VerbosePreference
        }
    
        $route = Invoke-Routing @splat
        if (!$route.success) {
            return $route
        }
        
        #Test if DNS works
        $dnsResolution = Resolve-DnsName $HostnameToResolve -QuickTimeout -Server $resolver -ErrorAction SilentlyContinue

        $dnsResults += @{
            'server'  = $resolver
            'success' = [bool]$dnsResolution
            'dns'     = $dnsResolution 
        
        }
        if ([bool]$dnsResolution -eq $true) {
            $msg = "PASS: (dvm) DNS Server $resolver using Source IP $($IpInfo.testIPv4Address) resolved domain $HostnameToResolve" 
            Write-AzsReadinessLog -Message $msg
        }
        else {
            $msg = "FAIL: (dvm) DNS Server $resolver using Source IP $($IpInfo.testIPv4Address) FAILED to resolve $HostnameToResolve."
            $logMsg += $msg
            Write-AzsReadinessLog -Message $msg -Type 'Error'
        }
        #remove the var for the next iteration of the loop
        Remove-Variable dnsResolution
    }
  
    #check for failures, collect the failed servers and generate test commands
    $failed = [System.Collections.ArrayList]@()
    $manualTestCommand = [System.Collections.ArrayList]@()
    foreach ($nameServer in $dnsResults) {
        if (!$nameServer.success) {
            $failed += $nameServer.server
            $manualTestCommand += "Resolve-DnsName $HostnameToResolve -QuickTimeout -Server $($nameServer.server)"
        }
    }

    # Check the $failed array for failures. If any resolver failed stop the script and return the failed dns server(s)
    if ($failed) {
        $returnData = @{
            'success'           = $False
            'message'           = $logMsg
            'dnsServers'        = $failed
            'manualTestCommand' = $manualTestCommand
            'sourceIP'          = $IpInfo.testIPv4Address
            'destinationIP'     = $failed
            'port'              = 'tcp_53/udp_53'
            'networkName'       = $IpInfo.networkName 
        }
    }
    # All DNS servers are working. The results from the first DNS server will be returned back to main for further testing
    else {
        $ips = [System.Collections.ArrayList]@()
        foreach ($result in $dnsResults[0].dns ) {
            if ($result.ip4address) {
                
                $splat = @{
                    AdapterInfo = $AdapterInfo
                    IPAddress   = $result.IP4Address
                    IpInfo      = $IpInfo
                    Verbose     = $VerbosePreference
                }

                $route = Invoke-Routing @splat
                $ips += $result.IP4Address
            }
        }
        $returnData = @{
            'success' = $True
            'ips'     = $ips 
        }
    }
    
    return $returnData
}

function Invoke-NtpTesting {
    [CmdletBinding()]
    param(

        # Information regarding the vNic used to ping the P2P IP addresses
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [ciminstance]
        $AdapterInfo,

        # The customers DNS servers
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [String]
        $TimeServer,

        # IP information for the network being tested
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [System.Collections.Hashtable]
        $IpInfo

    )
    
    $splat = @{
        AdapterInfo = $AdapterInfo
        IPAddress   = $TimeServer
        IpInfo      = $IpInfo
        Verbose     = $VerbosePreference
    }
    $route = Invoke-Routing @splat
    if (!$route.success) {
        return $route
    }

    $count = 0
    $result = $false
    while (!$result) {
        $timeTest = & w32tm /stripchart /computer:$TimeServer /dataonly /samples:1

        $tab = "`t" * 2
        $ntpResults = "NTP Query Results`n$tab$($timeTest -join "`n$tab")"
    
        if ( ($timeTest -match "error") -Or ($timeTest.length -le 1) ) {
            $result = $false
            $count ++
            if ($count -gt 4){
                break
            }
            Start-Sleep 5
        }
        else {
            $result = $true
            break
        }
    }

    if ($result) { 
        $timeTest | Where-Object { 
            if ($_ -match "The current time is") {
                Write-AzsReadinessLog -Message "PASS: (dvm) NTP Server $($TimeServer): $_" 
            }
        }
        $returnData = @{ 
            "success" = $result
            "message" = "PASS: (dvm) NTP Server $TimeServer using Source IP $($IpInfo.testIPv4Address) is working: $($ntpResults)"
        } 
    }
    else {
        $msg = "FAIL: (dvm) NTP Server $TimeServer using Source IP $($IpInfo.testIPv4Address) is not working: $($ntpResults)"
        Write-AzsReadinessLog -Message $msg -Type 'Error'

        $manualTestCommand = "& w32tm /stripchart /computer:$($TimeServer) /dataonly /samples:1"
        
        $returnData = @{
            "success"           = $result
            "networkName"       = $($IpInfo.networkName)
            "message"           = $msg
            "port"              = "udp_123"
            "sourceIP"          = $IpInfo.testIPv4Address
            "destinationIP"     = $timeServer
            "manualTestCommand" = $manualTestCommand  
        }
    }

    return $returnData
}

function Invoke-Routing {
    [CmdletBinding()]
    param(

        # Information regarding the vNic used to ping the P2P IP addresses
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [ciminstance]
        $AdapterInfo,

        # The customers DNS servers
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [String]
        $IPAddress,

        # IP information for the network being tested
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [System.Collections.Hashtable]
        $IpInfo

    )
    # check if the route already exists, if it does do not set it
    $checkRoute = Get-NetRoute -DestinationPrefix "$($IPAddress)/32" -ErrorAction SilentlyContinue

    if (![bool]$checkRoute) {
        Try {
            
            #set the route, if failure occurs catch the error
            $null = New-NetRoute -DestinationPrefix "$($IPAddress)/32" -InterfaceIndex $AdapterInfo.ifIndex -NextHop $IpInfo.IPV4Gateway -routemetric 1 -ErrorAction Stop
            
            Write-AzsReadinessLog -Message "Route is now set for IP: $IPAddress"
            
            $returnData = @{ "success" = $true }
        }
        Catch {
            Write-AzsReadinessLog -Message $($_.Exception.Message) -Type 'Error' -ToScreen            
            $returnData = @{ 
                "success" = $false 
                "message" = $_.Exception.Message 
            }
        }
    }
    else {
        Write-AzsReadinessLog -Message "Route is already set for IP: $IPAddress"
        $returnData = @{ "success" = $true }
    }
    return $returnData
}


function Invoke-WebCheck {
    [CmdletBinding()]
    param(

        # IP's from nslookup of $Domain
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [Array]
        $Ips,

        # IP information for the network being tested
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [System.Collections.Hashtable]
        $IpInfo,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [String]
        $Domain

    )

    #make web request until successful, or for about 5 minutes.
    $uri = "https://$($Ips[0])"
    $ip = $Ips[0]
    
    <# #uncomment to spoof failed webcall
        if ($Domain -match "login"){
            $uri = "https://1.2.3.4"
        }
    #>

    
    

    Try {
        $routeInfo = Get-NetRoute -DestinationPrefix $ip"/32" -ErrorAction stop
    }
    Catch {
        $returnData = @{
            'success' = $False
            'message' = "routes to $Domain did not get set properly"
        } 
        return $returnData
    }

    $manualTestCommand = ("Invoke-webrequest -Uri $uri -TimeoutSec 5 -MaximumRedirection 0 -Headers @{'HOST'= '$Domain'} -UseBasicParsing ")
    
    $count = 0
    $webCheck = $False
    while ($webCheck -eq $False) {
        #login.microsoftonline.com is fronted by a CDN and the IPs change frequently. I take the first IP fron a nslookup and set a HOST header in order to make the call to a single IP.
        Try {
            $check = Invoke-webrequest -Uri $uri -TimeoutSec 5  -MaximumRedirection 0 -Headers @{"HOST" = "$Domain" } -ErrorAction SilentlyContinue -UseBasicParsing -Verbose:$VerbosePreference
            $statusCode = $check.StatusCode

        }
        Catch {
            $statusCode = $_.Exception.Response.StatusCode.value__
        }
        
        $count += 1
        if (($statusCode -lt 500) -And ($statusCode -ge 300)) {
            $msg = "PASS: (dvm) Successfull webrequest to: https://$Domain($($ips[0]))"
            Write-AzsReadinessLog -Message $msg 
            $webCheck = $True
            $returnData = @{
                "success"     = $True; 
                "message"     = $msg; 
                "networkName" = $IpInfo.networkName
            }
            return $returnData
        }
        else {
            
            $message = "Attempt $($count) of 3
    Check connectivity to the internet (https://$Domain $($ips[0])) from the $($IpInfo.networkName) network
    sourceIP = $($IpInfo.testIPv4Address)
    destinationIP = $ip
    port = https/tcp_443
    manualTestCommand = $manualTestCommand
     
    "

    
        }
        if ($count -eq 3) {
            $msg = "FAIL: (dvm) Unsuccessfull webrequest using Source IP $($IpInfo.testIPv4Address) to: https://$Domain($($ips[0]))" 
            Write-AzsReadinessLog -Message $msg -Type 'Error'
            $returnData = @{
                "success"           = $False
                "message"           = $msg
                'manualTestCommand' = $manualTestCommand
                "sourceIP"          = $IpInfo.testIPv4Address
                "destinationIP"     = $ips[0] 
                "port"              = "https/tcp_443"
                "networkName"       = $IpInfo.networkName
                "TraceRoute"        = $traceRT  
            }
            return $returnData
        }
    }
}

function Get-Endpoints { 
    [CmdletBinding()]
    param(

        #ARM Endpoing
        #[Parameter(Mandatory = $True)]
        #[ValidateNotNullOrEmpty()]
        #[string] $CloudARMEndpoint,

        # IP's from nslookup of $Domain
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [Array]
        $Ips,

        # IP information for the network being tested
       # [Parameter(Mandatory = $True)]
       # [ValidateNotNullOrEmpty()]
       # [System.Collections.Hashtable]
       # $IpInfo,

        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [String]
        $Domain
    
    )

    try {
        Write-AzsReadinessLog -Message "Start: Get-Endpoints from ARM for $Domain"
        $fullUri = "https://" + $Ips[0].TrimEnd('/') + "/metadata/endpoints?api-version=2015-01-01"
        Write-AzsReadinessLog -Message $fullUri
        $response = Invoke-RestMethod -Uri $fullUri -UseBasicParsing -TimeoutSec 30 -Headers @{ "HOST" = "$Domain" } -Verbose:$VerbosePreference

        $checkResponse = @(
            [bool]$response.graphEndpoint
            [bool]$response.authentication.loginEndpoint
            [bool]$response.authentication.audiences[0]
        )

        if ($false -in $checkResponse) {
            $returnData = @{
                success = $false
                message = "The response from $Domain did not contain the needed URL's for AAD."
            }
            $hash = @{
                Test = "dvm_webRequests"
                Result = 'Fail'
                FailureDetail = $returnData.message
                outputObject = $null
            }
            Write-AzsResult -in $hash
            $global:ValidationResults += $hash
        }
        else {
            $returnData = @{
                success = $true
                message = "The response from $Domain contains the needed URL's for AAD."
                EndpointProperties = @{
                    GraphUri             = $response.graphEndpoint
                    LoginUri             = $response.authentication.loginEndpoint
                    ManagementServiceUri = $response.authentication.audiences[0]
                }
            }
            
        }
        

        
    }
    catch {
        $exceptionMessage = $($PSItem.Exception.Message)
        Write-AzsReadinessLog -Message "Could not retrive the AAD Endpoints for $Domain"
        Write-AzsReadinessLog -Message "Exception Message: $exceptionMessage"
        $returnData = @{
            success = $false
            message = "Could not retrive the AAD Endpoints from $Domain. Exception: $exceptionMessage "
        }
        $hash = @{
            Test = "dvm_webRequests"
            Result = 'Fail'
            FailureDetail = $returnData.message
            outputObject = $null
        }
        Write-AzsResult -in $hash
        $global:ValidationResults += $hash
        return $returnData
    }

    return $returnData
}

function Get-AzureURIs {
    <#
    .SYNOPSIS
        Resolve Azure URIs for a given Azure Service
    .DESCRIPTION
        Resolve Azure URIs for a given Azure Service
    .EXAMPLE
        Get-AzureURIs -AzureEnvironment AzureCloud
    .INPUTS
        AzureEnvironment - string - should be AzureCloud, AzureChinaCloud, AzureGermanCloud
    .OUTPUTS
        Hashtable of URIs
    .NOTES
        General notes
    #>

    [OutputType([Hashtable])]
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateSet("AzureCloud", "AzureChinaCloud", "AzureGermanCloud", "AzureUSGovernment", "CustomCloud")]
        [string] $AzureEnvironment,

        [Parameter(Mandatory = $false)]
        [string] $CustomCloudARMEndpoint,

        # IP's from nslookup of $Domain
        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [Array]
        $Ips
    )

    $data = switch ($AzureEnvironment) {
        'AzureCloud' {
            @{
                GraphUri             = "https://graph.windows.net/"
                LoginUri             = "https://login.microsoftonline.com/"
                ManagementServiceUri = "https://management.core.windows.net/"
                ARMUri               = "https://management.azure.com/"
            }
        }

        'AzureChinaCloud' {
            @{
                GraphUri             = "https://graph.chinacloudapi.cn/"
                LoginUri             = "https://login.chinacloudapi.cn/"
                ManagementServiceUri = "https://management.core.chinacloudapi.cn/"
                ARMUri               = "https://management.chinacloudapi.cn/"
            }
        }

        'AzureUSGovernment' {
            @{
                GraphUri             = "https://graph.windows.net/"
                LoginUri             = "https://login.microsoftonline.us/"
                ManagementServiceUri = "https://management.core.usgovcloudapi.net/"
                ARMUri               = "https://management.usgovcloudapi.net/"
            }
        }

        'AzureGermanCloud' {
            @{
                GraphUri             = "https://graph.cloudapi.de/"
                LoginUri             = "https://login.microsoftonline.de/"
                ManagementServiceUri = "https://management.core.cloudapi.de/"
                ARMUri               = "https://management.microsoftazure.de/"
            }
        }

        'CustomCloud' {
            $endPoints = Get-Endpoints -Ips $Ips -Domain (([System.Uri]$CustomCloudARMEndpoint).Host)
            $CustomCloudARMEndpointProperties = $endPoints.EndpointProperties
            @{
                GraphUri             = $CustomCloudARMEndpointProperties.GraphUri
                LoginUri             = $CustomCloudARMEndpointProperties.LoginUri
                ManagementServiceUri = $CustomCloudARMEndpointProperties.ManagementServiceUri
                ARMUri               = $CustomCloudARMEndpoint
            }
        }

        default {
            throw New-Object NotImplementedException("Unknown environment type '$AzureEnvironment'")
        }
    }

    return $data
}

function Invoke-DnsCheck {
    [CmdletBinding()]
    param(

        # IP's from nslookup of login.microsoftonline.com
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [Array]
        $Routes,

        # All the configuration data required for AzureStack
        [Parameter(mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [PSCustomObject]
        $ConfigJson,

        # IP information for the network being tested
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [System.Collections.Hashtable]
        $IpInfo

    )
    
    if ($Routes.success -eq $False) {
        Write-AzsReadinessLog -Message $($Routes.message) -Type 'Error'

        $traceRT = [System.Collections.ArrayList]@()
        $Routes.destinationIP | where-object {
            $traceRT += @{
                "destinationIP" = $_
                "TraceRoute"    = (Trace-ToDestination -configJson $ConfigJson -DestIP $_ -BmcGatewayIP $ipInfo.IPV4Gateway).TraceRoute 
            }
        }

        $manualTest = "Commands to manually test DNS resolution:`n`t"
        $manualTest += $Routes.manualTestCommand -join "`n`t"

        $returnData = @{ 
            success = $false
            'message' = $Routes.message
        }
    } 
    else {
        $returnData = @{ "success" = $true }
    }

    return $returnData
}
    
function Invoke-WebResults {
    [CmdletBinding()]
    param(

        # IP's from nslookup of login.microsoftonline.com
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [Hashtable]
        $WebCheck,

        # IP's from nslookup of login.microsoftonline.com
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [Array]
        $Routes,

        # All the configuration data required for AzureStack
        [Parameter(mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [PSCustomObject]
        $ConfigJson,

        
        # IP information for the network being tested
        [Parameter(Mandatory = $True)]
        [ValidateNotNullOrEmpty()]
        [System.Collections.Hashtable]
        $IpInfo

    )
    if (!$webCheck.success) {

        Write-AzsReadinessLog -Message "Performing traceroute to $($routes.ips[0]) using SRC IP $($ipInfo.testIPv4Address)" -Type 'Error'

        $traceRT = (Trace-ToDestination -configJson $ConfigJson -DestIP $($routes.ips[0]) -BmcGatewayIP $ipInfo.IPV4Gateway).TraceRoute
        
        Write-AzsReadinessLog -Message $($webCheck.message) -Type 'Error'

    }
}
# SIG # Begin signature block
# MIIjhQYJKoZIhvcNAQcCoIIjdjCCI3ICAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBFvuWbKIEFV6PI
# g+7Y6Gyji/NvAs4KRMTjSEm2zKTGfKCCDYEwggX/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/BvW1taslScxMNelDNMYIVWjCCFVYCAQEwgZUwfjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAlKLM6r4lfM52wAAAAACUjAN
# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgBECAdZeB
# f2Gc4uwCtZYIWNmJx/k7iJwr5RlgRmRdngIwQgYKKwYBBAGCNwIBDDE0MDKgFIAS
# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN
# BgkqhkiG9w0BAQEFAASCAQBQA9yO8hXLwo9CFyVR3wBayc1VJTGEJo633FESWci6
# MDVTnaHKkY4cJ4PNblItpi7GUTS228P959WwVCP7Ayp1CCkg9xW2/9Hr1TYxHC5G
# 3fK1i/RIFZS5oWtOtNHfVEn4rf0hAyk/qojjsC8/vITQ1HkVHZNrMVxIqBOQmldA
# hcPdYYeCMVjsChJ2h0aBCbNtAK8LckFOyiEgdGovKhS23L60kUctxryBW+vITnIH
# 7AWsaM9pO4pVqXEj46eUuF1oBKuyO7F8h7pEpdCFrFkBG9FCjVe5mpCmpEJNf9Me
# bbsy4mARlRJfb7dfylT8Hgmr9lbNwW7P2lmPoRq7QaEioYIS5DCCEuAGCisGAQQB
# gjcDAwExghLQMIISzAYJKoZIhvcNAQcCoIISvTCCErkCAQMxDzANBglghkgBZQME
# AgEFADCCAVEGCyqGSIb3DQEJEAEEoIIBQASCATwwggE4AgEBBgorBgEEAYRZCgMB
# MDEwDQYJYIZIAWUDBAIBBQAEIDr2uY+m2ZVHcb2Com+Tr0lh1MLqLy/UhVaIs+06
# SUUfAgZhkuEfJhgYEzIwMjExMTE2MDg0OTQzLjkzOVowBIACAfSggdCkgc0wgcox
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1p
# Y3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMxJjAkBgNVBAsTHVRoYWxlcyBUU1Mg
# RVNOOjEyQkMtRTNBRS03NEVCMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFt
# cCBTZXJ2aWNloIIOOzCCBPEwggPZoAMCAQICEzMAAAFT0oJyRWxX44sAAAAAAVMw
# 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/edIhJEqGCAs0wggI2AgEBMIH4oYHQpIHNMIHKMQswCQYDVQQGEwJVUzETMBEG
# A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj
# cm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBP
# cGVyYXRpb25zMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjoxMkJDLUUzQUUtNzRF
# QjElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcG
# BSsOAwIaAxUAikpN7ccO1k9PKCGCvaQWKyJ50ZuggYMwgYCkfjB8MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQg
# VGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIFAOU9X54wIhgPMjAy
# MTExMTYwNjM3MThaGA8yMDIxMTExNzA2MzcxOFowdjA8BgorBgEEAYRZCgQBMS4w
# LDAKAgUA5T1fngIBADAJAgEAAgEFAgH/MAcCAQACAhFEMAoCBQDlPrEeAgEAMDYG
# CisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEA
# AgMBhqAwDQYJKoZIhvcNAQEFBQADgYEATmvyESXbHLXzKcvhWUgOObExLX3IafeK
# KeAQawX6fIoDRNkzENMAoyfwP6USSMHE/orwf7VIrCCEloW587yfk4pWAIRGN9LO
# b+m5HOF2xcnpo0jTE6kZWPc57ViSyiJK5EVBAuN0bQKoNSGOvFfX1TIgwTQqJQun
# ItWzfp69aaExggMNMIIDCQIBATCBkzB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0Eg
# MjAxMAITMwAAAVPSgnJFbFfjiwAAAAABUzANBglghkgBZQMEAgEFAKCCAUowGgYJ
# KoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEiBCALF7tCiz2r
# lgX7Xz1a5at2wSaPpnkusOW4ZqjDds1xXTCB+gYLKoZIhvcNAQkQAi8xgeowgecw
# geQwgb0EIFDBCo85tCAICfyXoZBDppodLIMcb2wOH2rEBWiNtY8AMIGYMIGApH4w
# fDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl
# ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMd
# TWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAFT0oJyRWxX44sAAAAA
# AVMwIgQgl3uKqMQCykGgGYoUA5hTgL739FJU/MI10jnQkcUn6KswDQYJKoZIhvcN
# AQELBQAEggEABa155G0SAeUOs1DB3OKccs4I8cDwrQQqekWQ+omla8M6CGrViSy8
# LTMkYadAHZ6tGDN5RldiK4OtZi7KEV5qYdtJsMVToVvPIlT8X2Rb8GDR9Sn4UQ7X
# 9ovotLZF/RCLZ5Z2GJoD73xX2UuCTr1Tweo6jAB5fKkLFO+ERLT6lzgsPMVBOB2Z
# kaeW73QptwFJrU3kNnPi26kmC+KXO0hqGs63tkES4x1vJEHSPSpM+nSlnnvv79FX
# KxFW2z71SmFixzPPGU55eNQrcSBpKlkG3mivP9wtJmnRw/V6bJzj5lCPSOMBABvy
# B6RcpwMCso82mVYBkzas5Kj05mq1vPE99A==
# SIG # End signature block