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
# MIIjnwYJKoZIhvcNAQcCoIIjkDCCI4wCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# 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/BvW1taslScxMNelDNMYIVdDCCFXACAQEwgZUwfjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAlKLM6r4lfM52wAAAAACUjAN
# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgBECAdZeB
# f2Gc4uwCtZYIWNmJx/k7iJwr5RlgRmRdngIwQgYKKwYBBAGCNwIBDDE0MDKgFIAS
# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN
# BgkqhkiG9w0BAQEFAASCAQBQA9yO8hXLwo9CFyVR3wBayc1VJTGEJo633FESWci6
# MDVTnaHKkY4cJ4PNblItpi7GUTS228P959WwVCP7Ayp1CCkg9xW2/9Hr1TYxHC5G
# 3fK1i/RIFZS5oWtOtNHfVEn4rf0hAyk/qojjsC8/vITQ1HkVHZNrMVxIqBOQmldA
# hcPdYYeCMVjsChJ2h0aBCbNtAK8LckFOyiEgdGovKhS23L60kUctxryBW+vITnIH
# 7AWsaM9pO4pVqXEj46eUuF1oBKuyO7F8h7pEpdCFrFkBG9FCjVe5mpCmpEJNf9Me
# bbsy4mARlRJfb7dfylT8Hgmr9lbNwW7P2lmPoRq7QaEioYIS/jCCEvoGCisGAQQB
# gjcDAwExghLqMIIS5gYJKoZIhvcNAQcCoIIS1zCCEtMCAQMxDzANBglghkgBZQME
# AgEFADCCAVkGCyqGSIb3DQEJEAEEoIIBSASCAUQwggFAAgEBBgorBgEEAYRZCgMB
# MDEwDQYJYIZIAWUDBAIBBQAEIDr2uY+m2ZVHcb2Com+Tr0lh1MLqLy/UhVaIs+06
# SUUfAgZhgwk+c+kYEzIwMjExMTE1MTQ1NjI0LjY4MVowBIACAfSggdikgdUwgdIx
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1p
# Y3Jvc29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEmMCQGA1UECxMdVGhh
# bGVzIFRTUyBFU046RkM0MS00QkQ0LUQyMjAxJTAjBgNVBAMTHE1pY3Jvc29mdCBU
# aW1lLVN0YW1wIFNlcnZpY2Wggg5NMIIE+TCCA+GgAwIBAgITMwAAAUAjGdZe3pUk
# MQAAAAABQDANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0Eg
# MjAxMDAeFw0yMDEwMTUxNzI4MjZaFw0yMjAxMTIxNzI4MjZaMIHSMQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQg
# SXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1Mg
# RVNOOkZDNDEtNEJENC1EMjIwMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFt
# cCBTZXJ2aWNlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArn1rM3Hq
# 1S9N0z8R+YKqZu25ykk5OlT8TsuwtdBWyDCRFoASk9fB6siColFhXBhyej4c3yIw
# N0UyJWBOTAjHteOIYjfCpx539rbgBI5/BTHtC+qcBT7ftPknTtQn89lNOcpP70fu
# YVZLoQsDnLjGxxtW/eVewR5Q0I1mWQfJy5xOfelk5OWjz3YV4HKtqyIRzJZd/Rzc
# Y8w6qmzoSNsYIdvliT2eeQZbyYTdJQsRozIKTMLCJUBfVjow2fJMDtzDB9XEOdfh
# PWzvUOadYgqqh0lslAR7NV90FFmZgZWARrG8j7ZnVnC5MOXOS/NI58S48ycsug0p
# N2NGLLk2YWjxCwIDAQABo4IBGzCCARcwHQYDVR0OBBYEFDVDHC4md0YgjozSqnVs
# +OeELQ5nMB8GA1UdIwQYMBaAFNVjOlyKMZDzQ3t8RhvFM2hahW1VMFYGA1UdHwRP
# ME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1
# Y3RzL01pY1RpbVN0YVBDQV8yMDEwLTA3LTAxLmNybDBaBggrBgEFBQcBAQROMEww
# SgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMv
# TWljVGltU3RhUENBXzIwMTAtMDctMDEuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0l
# BAwwCgYIKwYBBQUHAwgwDQYJKoZIhvcNAQELBQADggEBAGMMUq2gQuCC9wr4YhIS
# fPyobaNYV3Ov4YwWsSfIz/j1xaN9TvLAB2BxPi2CtRbgbBUf48n07yReZInwu2r8
# vwLoNG2TtYzey01DRyjjsNoiHF9UuRLFyKZChkKC3o9r0Oy2x0YYjUpDxVChZ5q5
# cAfw884wP0iUcYnKKGn8eJ0nwpr7zr/Tlu+HOjXDT9C754aS4KUFNm8D7iwuvWWz
# SOVl7XMWdu82BnnTmB7s2Ocf3I4adGzdixQ5Zxxa3zOAvKzrV+0HcVQIY3SQJ9Pz
# jDRlzCviMThxA8FUIRL3FnYqvchWkEoZ4w8S7FsGWNlXLWQ7fHMb3l4gjueHyO4p
# 6tUwggZxMIIEWaADAgECAgphCYEqAAAAAAACMA0GCSqGSIb3DQEBCwUAMIGIMQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNy
# b3NvZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0xMDA3MDEy
# MTM2NTVaFw0yNTA3MDEyMTQ2NTVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX
# YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg
# Q29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAy
# MDEwMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqR0NvHcRijog7PwT
# l/X6f2mUa3RUENWlCgCChfvtfGhLLF/Fw+Vhwna3PmYrW/AVUycEMR9BGxqVHc4J
# E458YTBZsTBED/FgiIRUQwzXTbg4CLNC3ZOs1nMwVyaCo0UN0Or1R4HNvyRgMlhg
# RvJYR4YyhB50YWeRX4FUsc+TTJLBxKZd0WETbijGGvmGgLvfYfxGwScdJGcSchoh
# iq9LZIlQYrFd/XcfPfBXday9ikJNQFHRD5wGPmd/9WbAA5ZEfu/QS/1u5ZrKsajy
# eioKMfDaTgaRtogINeh4HLDpmc085y9Euqf03GS9pAHBIAmTeM38vMDJRF1eFpwB
# BU8iTQIDAQABo4IB5jCCAeIwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFNVj
# OlyKMZDzQ3t8RhvFM2hahW1VMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsG
# A1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJc
# YmjRPZSQW9fOmhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9z
# b2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIz
# LmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWlj
# cm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0
# MIGgBgNVHSABAf8EgZUwgZIwgY8GCSsGAQQBgjcuAzCBgTA9BggrBgEFBQcCARYx
# aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL1BLSS9kb2NzL0NQUy9kZWZhdWx0Lmh0
# bTBABggrBgEFBQcCAjA0HjIgHQBMAGUAZwBhAGwAXwBQAG8AbABpAGMAeQBfAFMA
# dABhAHQAZQBtAGUAbgB0AC4gHTANBgkqhkiG9w0BAQsFAAOCAgEAB+aIUQ3ixuCY
# P4FxAz2do6Ehb7Prpsz1Mb7PBeKp/vpXbRkws8LFZslq3/Xn8Hi9x6ieJeP5vO1r
# VFcIK1GCRBL7uVOMzPRgEop2zEBAQZvcXBf/XPleFzWYJFZLdO9CEMivv3/Gf/I3
# fVo/HPKZeUqRUgCvOA8X9S95gWXZqbVr5MfO9sp6AG9LMEQkIjzP7QOllo9ZKby2
# /QThcJ8ySif9Va8v/rbljjO7Yl+a21dA6fHOmWaQjP9qYn/dxUoLkSbiOewZSnFj
# nXshbcOco6I8+n99lmqQeKZt0uGc+R38ONiU9MalCpaGpL2eGq4EQoO4tYCbIjgg
# tSXlZOz39L9+Y1klD3ouOVd2onGqBooPiRa6YacRy5rYDkeagMXQzafQ732D8OE7
# cQnfXXSYIghh2rBQHm+98eEA3+cxB6STOvdlR3jo+KhIq/fecn5ha293qYHLpwms
# ObvsxsvYgrRyzR30uIUBHoD7G4kqVDmyW9rIDVWZeodzOwjmmC3qjeAzLhIp9cAv
# VCch98isTtoouLGp25ayp0Kiyc8ZQU3ghvkqmqMRZjDTu3QyS99je/WZii8bxyGv
# WbWu3EQ8l1Bx16HSxVXjad5XwdHeMMD9zOZN+w2/XU/pnR4ZOC+8z1gFLu8NoFA1
# 2u8JJxzVs341Hgi62jbb01+P3nSISRKhggLXMIICQAIBATCCAQChgdikgdUwgdIx
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1p
# Y3Jvc29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEmMCQGA1UECxMdVGhh
# bGVzIFRTUyBFU046RkM0MS00QkQ0LUQyMjAxJTAjBgNVBAMTHE1pY3Jvc29mdCBU
# aW1lLVN0YW1wIFNlcnZpY2WiIwoBATAHBgUrDgMCGgMVAEKl5h7yE6Y7MpfmMpEb
# QzkJclFToIGDMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0
# b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh
# dGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwDQYJ
# KoZIhvcNAQEFBQACBQDlPLAkMCIYDzIwMjExMTE1MTgwODM2WhgPMjAyMTExMTYx
# ODA4MzZaMHcwPQYKKwYBBAGEWQoEATEvMC0wCgIFAOU8sCQCAQAwCgIBAAICDf8C
# Af8wBwIBAAICEiwwCgIFAOU+AaQCAQAwNgYKKwYBBAGEWQoEAjEoMCYwDAYKKwYB
# BAGEWQoDAqAKMAgCAQACAwehIKEKMAgCAQACAwGGoDANBgkqhkiG9w0BAQUFAAOB
# gQDSOA0V7Ts1gRQeBcsjZ2S0usJfbBdlFs7Zf32eEnBd1bmLHmUtH3NEm0y5ogUQ
# Mxo9q7ssgWrweJTX5RQFKUHt59SE6U0fdT8JDqm7egAj+2g9r53/bppcgzrOtl3d
# wj/kqR2m0nAVopOSmxzeKXBt4DbPvfUg/X8KKAPalzdrDTGCAw0wggMJAgEBMIGT
# MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT
# HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABQCMZ1l7elSQxAAAA
# AAFAMA0GCWCGSAFlAwQCAQUAoIIBSjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQ
# AQQwLwYJKoZIhvcNAQkEMSIEIOAYXsvIHsIL8LgHT/o6GuKm1IUKkB8Gf1CpQq1D
# kyKuMIH6BgsqhkiG9w0BCRACLzGB6jCB5zCB5DCBvQQgLzawterM0qRcJO/zcvJT
# 7do/ycp8RZsRSTqqtgIIl4MwgZgwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UE
# CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z
# b2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQ
# Q0EgMjAxMAITMwAAAUAjGdZe3pUkMQAAAAABQDAiBCBEYfIJH9Jp8I2/cYBohNYH
# 4gKhGQz5BDoqsYJ71PFXlzANBgkqhkiG9w0BAQsFAASCAQAQbIeRZYgOgSSXVQvH
# ImiuCjcmVAtlUsseDocvyH8ck6U46m4hlFgJ/KHqVSaSp5UWtXnnMJKKDifRomHr
# 55Y0tcuQEgv0t6VfM9Q6tJvroXdzZdFgRbD/x7WHhi7osBU/QB7S6umv5FT+zTZf
# IOdoOX2x3PcFx6G0GvgbVmJw1eAcS/z+zDQTi+eVjWkktuXZtx6BFKXPZ2IZVCSw
# Tzln3lzACo4sL4hY+hvSvoj2cnrPtBo23NXGj+4wxZbzO3B4b4qN/wmFkpKHag2P
# uUNYZbv1ilFgiDq2mJgMdnYLO4DcxIbOFC2MfY5hP6fur4nBkV1AVTYIBkybQRoY
# r7eM
# SIG # End signature block