AzStackHciConnectivity/AzStackHci.Connectivity.Helpers.psm1

class HealthModel
{
    # Attributes for Azure Monitor schema
    [string]$Name #Name of the individual test/rule/alert that was executed. Unique, not exposed to the customer.
    [string]$Title #User-facing name; one or more sentences indicating the direct issue.
    [string]$Severity #Severity of the result (Critical, Warning, Informational, Hidden) – this answers how important the result is. Critical is the only update-blocking severity.
    [string]$Description #Detailed overview of the issue and what impact the issue has on the stamp.
    [psobject]$Tags #Key-value pairs that allow grouping/filtering individual tests. For example, "Group": "ReadinessChecks", "UpdateType": "ClusterAware"
    [string]$Status #The status of the check running (i.e. Failed, Succeeded, In Progress) – this answers whether the check ran, and passed or failed.
    [string]$Remediation #Set of steps that can be taken to resolve the issue found.
    [string]$TargetResourceID #The unique identifier for the affected resource (such as a node or drive).
    [string]$TargetResourceName #The name of the affected resource.
    [string]$TargetResourceType #The type of resource being referred to (well-known set of nouns in infrastructure, aligning with Monitoring).
    [datetime]$Timestamp #The Time in which the HealthCheck was called.
    [psobject[]]$AdditionalData #Property bag of key value pairs for additional information.
    [string]$HealthCheckSource #The name of the services called for the HealthCheck (I.E. Test-AzureStack, Test-Cluster).
}

class AzStackHciConnectivityTarget : HealthModel
{
    # Attribute for performing check
    [string[]]$EndPoint
    [string[]]$Protocol

    # Additional Attributes for end user interaction
    [string[]]$Service # short cut property to Service from tags
    [string[]]$OperationType # short cut property to Operation Type from tags
    [string[]]$Group # short cut property to group from tags
    [bool]$Mandatory # short cut property to mandatory from tags
    [bool]$System # targets for system checks such as proxy traversal
}

class AzStackHciConnectivityManifest
{
    [string]$Title
    [System.Version]$Version
    [AzStackHciConnectivityTarget[]]$Targets
}

Import-LocalizedData -BindingVariable lcTxt -FileName AzStackHci.Connectivity.Strings.psd1

#Create additional classes to help with writing/report results
class Diagnostics : AzStackHciConnectivityTarget {}
class DnsResult : AzStackHciConnectivityTarget {}
class ProxyDiagnostics : AzStackHciConnectivityTarget {}

function Test-Dns
{
    <#
    .SYNOPSIS
        Test DNS Resolution
    #>

    [CmdletBinding()]
    param (
        [System.Management.Automation.Runspaces.PSSession]
        $PsSession
    )

    # scriptblock to test dns resolution for each dns server
    $testDnsSb = {
        $AdditionalData = @()

        # Get local DNS servers
        $dnsServers = @()
        $netAdapter = Get-NetAdapter | Where-Object Status -EQ Up
        $dnsServer = Get-DnsClientServerAddress -InterfaceIndex $netAdapter.ifIndex -AddressFamily IPv4
        $dnsServers += $dnsServer | ForEach-Object { $PSITEM.Address } | Sort-Object | Get-Unique

        if (-not $dnsServers)
        {
            $AdditionalData += New-Object PsObject -Property @{
                Resource  = $lcTxt.NoDnsConfigured
                Status    = 'Failed'
                TimeStamp = [datetime]::UtcNow
                Source    = $ENV:COMPUTERNAME
                Detail    = $lcTxt.NoDnsConfigured
            }
        }
        else
        {
            foreach ($dnsServer in $dnsServers)
            {
                $dnsResult = $false
                $dnsResult = Resolve-DnsName -Name microsoft.com -Server $dnsServer -DnsOnly -ErrorAction SilentlyContinue -QuickTimeout -Type A
                if ([int]($dnsResult.count) -eq 0)
                {
                    $detail = $lcTxt.QueryDnsFail -f $dnsServer, 'microsoft.com', $ENV:COMPUTERNAME, [int]($dnsResult.count)
                }
                else
                {
                    $detail = $lcTxt.QueryDnsPass -f $dnsServer, 'microsoft.com', $ENV:COMPUTERNAME, [int]($dnsResult.count), ($dnsResult.IpAddress -join ',')
                }

                if ($dnsResult)
                {
                    if ($dnsResult[0] -is [Microsoft.DnsClient.Commands.DnsRecord])
                    {
                        $status = 'Succeeded'
                    }
                    else
                    {
                        $status = 'Failed'
                    }
                }
                else
                {
                    $status = 'Failed'
                }
                $AdditionalData += New-Object PsObject -Property @{
                    Resource  = $dnsServer
                    Status    = $status
                    TimeStamp = [datetime]::UtcNow
                    Source    = $ENV:COMPUTERNAME
                    Detail    = $detail
                }
            }
        }
        $AdditionalData
    }

    # run scriptblock
    $testDnsServer = if ($PsSession)
    {
        Invoke-Command -Session $PsSession -ScriptBlock $testDnsSb
    }
    else
    {
        Invoke-Command -ScriptBlock $testDnsSb
    }

    # build result
    $now = [datetime]::UtcNow

    # Write result to verbose log
    $testDnsServer | Foreach-Object {
        Log-Info $_.Detail -Type $(if ( $_.Status -eq 'Failed' ){ "Warning" } else { "Info" } )
    }

    $TargetComputerName = if ($PsSession.PSComputerName) { $PsSession.PSComputerName } else { $ENV:COMPUTERNAME }
    $aggregateStatus = if ($testDnsServer.Status -contains 'Succeeded') { 'Succeeded' } else { 'Failed' }
    $testDnsResult = New-Object -Type DnsResult -Property @{
        Name               = 'AzStackHci_Connectivity_Test_Dns'
        Title              = 'Test DNS'
        Severity           = 'Critical'
        Description        = 'Test DNS Resolution'
        Tags               = $null
        EndPoint           = @("microsoft.com")
        Service            = 'System'
        Remediation        = 'https://learn.microsoft.com/en-us/azure-stack/hci/deploy/deployment-tool-checklist'
        TargetResourceID   = 'c644bad4-044d-4066-861d-ceb93b64f046'
        TargetResourceName = "Test_DNS_$TargetComputerName"
        TargetResourceType = 'DNS'
        Timestamp          = $now
        Status             = $aggregateStatus
        AdditionalData     = $testDnsServer
        HealthCheckSource  = $ENV:EnvChkrId
    }
    return $testDnsResult
}

function Get-AzStackHciConnectivityServiceName
{
    <#
    .SYNOPSIS
        Retrieve Services from built target packs
    .DESCRIPTION
        Retrieve Services from built target packs
    .EXAMPLE
        PS C:\> Get-AzStackHciServices
        Explanation of what the example does
    .INPUTS
        Service
    .OUTPUTS
        PSObject
    .NOTES
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [string[]]
        $Service,

        [Parameter(Mandatory = $false)]
        [switch]
        $IncludeSystem
    )
    try
    {
        Get-AzStackHciConnectivityTarget -IncludeSystem:$IncludeSystem | Select-Object -ExpandProperty Service | Sort-Object | Get-Unique
    }
    catch
    {
        throw "Failed to get services names. Error: $($_.Exception.Message)"
    }
}

function Get-AzStackHciConnectivityOperationName
{
    <#
    .SYNOPSIS
        Retrieve Operation Types from built target packs
    .DESCRIPTION
        Retrieve Operation Types from built target packs e.g. Deployment, Update, Secret Rotation.
    .EXAMPLE
        PS C:\> Get-AzStackHciConnectivityOperationName
        Explanation of what the example does
    .INPUTS
        Service
    .OUTPUTS
        PSObject
    .NOTES
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [string]
        $OperationType
    )
    try
    {
        Get-AzStackHciConnectivityTarget | Select-Object -ExpandProperty OperationType | Sort-Object | Get-Unique
    }
    catch
    {
        throw "Failed to get services names. Error: $($_.Exception.Message)"
    }
}

function Get-AzStackHciConnectivityTarget
{
    <#
        .SYNOPSIS
            Retrieve Endpoints from built target packs
        .DESCRIPTION
            Retrieve Endpoints from built target packs
        .EXAMPLE
            PS> Get-AzStackHciConnectivityTarget
            Get all connectivity targets
        .EXAMPLE
            Get-AzStackHciConnectivityTarget -Service ARC | ft Name, Title, Service, OperationType -AutoSize
            Get all ARC connectivity targets
        .EXAMPLE
            PS> Get-AzStackHciConnectivityTarget -Service ARC -OperationType Workload | ft Name, Title, Service, OperationType -AutoSize
            Get all ARC targets for workloads
        .EXAMPLE
            PS> Get-AzStackHciConnectivityTarget -OperationType Workload | ft Name, Title, Service, OperationType -AutoSize
            Get all targets for workloads
        .EXAMPLE
            PS> Get-AzStackHciConnectivityTarget -OperationType ARC -OperationType Update -Additive | ft Name, Title, Service, OperationType -AutoSize
            Get all ARC targets and all targets for Update
        .INPUTS
            Service - String array
            OperationType - String array
            Additive - Switch
        .OUTPUTS
            PSObject
        .NOTES
    #>

    [CmdletBinding()]
    param (

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

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

        [Parameter(Mandatory = $false)]
        [switch]
        $Additive,

        [Parameter(Mandatory = $false)]
        [switch]
        $IncludeSystem,

        [Parameter(Mandatory = $false)]
        [switch]
        $LocalOnly

    )
    try
    {
        Import-AzStackHciConnectivityTarget -LocalOnly:$LocalOnly
        $executionTargets = @()
        # Additive allows the user to "-OR" their parameter values
        if ($Additive)
        {
            Write-Verbose -Message "Getting targets additively"
            if (-not [string]::IsNullOrEmpty($Service))
            {
                Write-Verbose -Message ("Getting targets by Service: {0}" -f ($Service -join ','))
                foreach ($svc in $Service)
                {
                    $executionTargets += $Script:AzStackHciConnectivityTargets | Where-Object { $svc -in $_.Service }
                }
            }
            if (-not [string]::IsNullOrEmpty($OperationType))
            {
                Write-Verbose -Message ("Getting targets by Operation Type: {0}" -f ($OperationType -join ','))
                foreach ($Op in $OperationType)
                {
                    $executionTargets += $Script:AzStackHciConnectivityTargets | Where-Object { $Op -in $_.OperationType }
                }
            }
            if ([string]::IsNullOrEmpty($OperationType) -and [string]::IsNullOrEmpty($Service))
            {
                $executionTargets += $Script:AzStackHciConnectivityTargets
            }
        }
        else
        {
            if ([string]::IsNullOrEmpty($OperationType) -and [string]::IsNullOrEmpty($Service))
            {
                $executionTargets += $Script:AzStackHciConnectivityTargets
            }
            elseif (-not [string]::IsNullOrEmpty($Service) -and [string]::IsNullOrEmpty($OperationType))
            {
                Write-Verbose -Message ("Getting targets by Service: {0}" -f ($Service -join ','))
                foreach ($svc in $Service)
                {
                    $executionTargets += $Script:AzStackHciConnectivityTargets | Where-Object { $svc -in $_.Service }
                }
            }
            elseif (-not [string]::IsNullOrEmpty($OperationType) -and [string]::IsNullOrEmpty($Service))
            {
                Write-Verbose -Message ("Getting targets by Operation Type: {0}" -f ($OperationType -join ','))
                foreach ($Op in $OperationType)
                {
                    $executionTargets += $Script:AzStackHciConnectivityTargets | Where-Object { $Op -in $_.OperationType }
                }
            }
            else
            {
                Write-Verbose -Message ("Getting targets by Operation Type: {0} and Service: {1}" -f ($OperationType -join ','), ($Service -join ','))
                $executionTargetsByOp = @()
                foreach ($Op in $OperationType)
                {
                    $executionTargetsByOp += $Script:AzStackHciConnectivityTargets | Where-Object { $Op -in $_.OperationType }
                }
                foreach ($svc in $Service)
                {
                    $executionTargets += $executionTargetsByOp | Where-Object { $svc -in $_.Service }
                }
            }
        }

        # Always add Mandatory targets
        $executionTargets += $Script:AzStackHciConnectivityTargets | Where-Object Mandatory | ForEach-Object {
            if ($PSITEM -notin $executionTargets)
            {
                $PSITEM
            }
        }

        if ($IncludeSystem)
        {
            return $executionTargets
        }
        else
        {
            return ($executionTargets | Where-Object Service -NotContains 'System')
        }
    }
    catch
    {
        throw "Get failed: $($_.exception)"
    }
}

function Import-AzStackHciConnectivityTarget
{
    <#
    .SYNOPSIS
        Retrieve Endpoints from built target packs
    .DESCRIPTION
        Retrieve Endpoints from built target packs
    .EXAMPLE
        PS C:\> Import-AzStackHciConnectivityTarget
        Explanation of what the example does
    .INPUTS
        URI
    .OUTPUTS
        PSObject
    .NOTES
    #>

    [CmdletBinding()]
    param (
        [switch]
        $LocalOnly
    )
    try
    {
        $Script:AzStackHciConnectivityTargets = @()
        if (-not $LocalOnly)
        {
            $Script:AzStackHciConnectivityTargets += Get-CloudEndpointFromManifest
        }

        if ($Script:AzStackHciConnectivityTargets) {
            return
        }
        else
        {
            $targetFiles = Get-ChildItem -Path "$PSScriptRoot\Targets\*.json" | Select-Object -ExpandProperty FullName
            Write-Verbose ("Importing {0}" -f ($targetFiles -join ','))
            ForEach ($targetFile in $targetFiles)
            {
                try
                {
                    # TO DO - Add validations:
                    # - protocol should not contain ://
                    $targetPackContent = Get-Content -Path $targetFile | ConvertFrom-Json -WarningAction SilentlyContinue
                    foreach ($target in $targetPackContent)
                    {
                        #Set Name of the individual test/rule/alert that was executed. Unique, not exposed to the customer.
                        $target | Add-Member -MemberType NoteProperty -Name HealthCheckSource -Value $ENV:EnvChkrId
                        $target.TargetResourceID = $target.EndPoint -join '_'
                        $target.TargetResourceName = $target.EndPoint -join '_'
                        $target.TargetResourceType = 'External Endpoint'
                        $Script:AzStackHciConnectivityTargets += [AzStackHciConnectivityTarget]$target
                    }
                }
                catch
                {
                    throw ("Unable to read {0}. Error: {1}" -f (Split-Path -Path $targetFile -Leaf), $_.Exception.Message)
                }
            }
        }
    }
    catch
    {
        throw "Import failed: $($_.exception)"
    }
}


function Get-SystemProxy
{
    <#
    .SYNOPSIS
        Get Proxy set on system
    .DESCRIPTION
        Get Proxy set on system
    .EXAMPLE
        PS C:\> Get-SystemProxy
        Explanation of what the example does
    .OUTPUTS
        Output (if any)
    .NOTES
    #>

    [CmdletBinding()]
    param ()
    throw "Not implemented"
}

function Get-SigningRootChain
{
    <#
    .SYNOPSIS
        Get signing root for https endpoint
    .DESCRIPTION
        Get signing root for https endpoint
    .EXAMPLE
        PS C:\> Get-SigningRoot -uri MicrosoftOnline.com
        Explanation of what the example does
    .INPUTS
        URI
    .OUTPUTS
        Output (if any)
    .NOTES
    #>

    [CmdletBinding()]
    param (
        [Parameter()]
        [System.Uri]
        $Uri,

        [Parameter()]
        [System.Management.Automation.Runspaces.PSSession]
        $PsSession,

        [Parameter()]
        [string]
        $Proxy,

        [Parameter()]
        [pscredential]
        $proxyCredential
    )
    try
    {
        $sb = {
            $uri = $args[0]
            $proxy = $args[1]
            $proxyCredential = $args[2]
            $GetSslCertChainFunction = $args[3]

            # Check if helper function is locally available
            Import-Module -Name AzStackHci.EnvironmentChecker -Force -ErrorAction SilentlyContinue -Scope Local
            if (-not (Get-Command -Name Get-SslCertificateChain -ErrorAction SilentlyContinue))
            {
                throw "Cannot find Get-SslCertificateChain in AzStackHci.EnvironmentChecker.Utilities module"
            }
            else
            {
                Write-Verbose "Found Get-SslCertificateChain in AzStackHci.EnvironmentChecker.Utilities module"
                $chain = Get-SslCertificateChain -Url $Uri -Proxy $Proxy -ProxyCredential $ProxyCredential
            }
            return $chain.ChainElements.Certificate
        }
        $ChainElements = if ($PsSession)
        {
            Invoke-Command -Session $PsSession -ScriptBlock $sb -ArgumentList $Uri, $Proxy, $ProxyCredential,${function:Get-SslCertificateChain}
        }
        else
        {
            Invoke-Command -ScriptBlock $sb -ArgumentList $Uri, $Proxy, $ProxyCredential,${function:Get-SslCertificateChain}
        }
        return $ChainElements
    }
    catch
    {
        throw $_
    }
}

function Test-RootCA
{
    <#
    .SYNOPSIS
        Short description
    .DESCRIPTION
        Long description
    .EXAMPLE
        PS C:\> <example usage>
        Explanation of what the example does
    .INPUTS
        Inputs (if any)
    .OUTPUTS
        Output (if any)
    .NOTES
        General notes
    #>

    [CmdletBinding()]
    param(
        [Parameter()]
        [System.Management.Automation.Runspaces.PSSession]
        $PsSession,

        [Parameter()]
        [string]
        $Proxy,

        [Parameter()]
        [pscredential]
        $ProxyCredential
    )
    try
    {
        if ($Script:AzStackHciConnectivityTargets)
        {
            $rootCATarget = $Script:AzStackHciConnectivityTargets | Where-Object Name -EQ System_Check_SSL_Inspection_Detection
            if ($rootCATarget.count -ne 1)
            {
                throw "Expected 1 System_RootCA, found $($rootCATarget.count)"
            }
            Install-UtilityModule -PsSession $PsSession -CmdletName Get-SslCertificateChain
            # We have two endpoints to check, they expire 6 months apart
            # meaning we should get a warning if criteria needs to change
            # 1 only require 1 endpoint to not be re-encrypted to succeed.
            $rootCATargetUrls = @()
            $rootCATarget.EndPoint | Foreach-Object {
                foreach ($p in $rootCATarget.Protocol) {
                    $rootCATargetUrls += "{0}://{1}" -f $p,$PSITEM
                }
            }

            $AdditionalData = @()

            foreach ($rootCATargetUrl in $rootCATargetUrls) {
                Log-Info "Testing SSL chain for $rootCATargetUrl"
                [array]$ChainElements = Get-SigningRootChain -Uri $rootCATargetUrl -PsSession $PsSession -Proxy $Proxy -ProxyCredential $ProxyCredential
                # This is our canary internet endpoint, if we can't get the chain we probably don't have internet access.
                if ($null -eq $ChainElements)
                {
                    $Status = 'Failed'
                    $detail = "Failed to get certificate chain for $rootCATargetUrl. Ensure the endpoint is accessible and proxy configuration is correct."
                    Log-Info $detail -Type Warning
                }
                else
                {
                    # Remove the leaf as this will always contain O=Microsoft in its subject
                    $ChainElements = $ChainElements[1..($ChainElements.Length-1)]
                    $subjectMatchCount = 0
                    # We check for 2 expected subjects and only require 1 to succeed
                    $rootCATarget.Tags.ExpectedSubject | Foreach-Object {
                        if ($ChainElements.Subject -match $PSITEM)
                        {
                            $subjectMatchCount++
                        }
                    }
                    if ($subjectMatchCount -ge 1)
                    {
                        $Status = 'Succeeded'
                        $detail = "Expected at least 1 chain certificate subject to match $($rootCATarget.Tags.ExpectedSubject -join ' or '). $subjectMatchCount matched."
                        Log-Info $detail
                    }
                    else
                    {
                        $Status = 'Failed'
                        $detail = "Expected at least 1 chain certificate subjects to match $($rootCATarget.Tags.ExpectedSubject -join ' or '). $subjectMatchCount matched. Actual subjects $($ChainElements.Subject -join ','). SSL decryption and re-encryption detected."
                        Log-Info $detail -Type Error
                    }
                }
                $AdditionalData += New-Object -TypeName PSObject -Property @{
                    Source    = if ([string]::IsNullOrEmpty($PsSession.ComputerName)) { $ENV:COMPUTERNAME } else { $PsSession.ComputerName }
                    Resource  = $rootCATargetUrl
                    Status    = $Status
                    Detail    = $detail
                    TimeStamp = [datetime]::UtcNow
                }
            }
            $rootCATarget.AdditionalData = $AdditionalData
            $rootCATarget.TimeStamp = [datetime]::UtcNow
            $rootCATarget.Status = if ('Succeeded' -in $rootCATarget.AdditionalData.Status) { 'Succeeded' } else { 'Failed'}
            Remove-UtilityModule -PsSession $PsSession
            return $rootCATarget
        }
        else
        {
            throw "No AzStackHciConnectivityTargets"
        }
    }
    catch
    {
        Log-Info "Test-RootCA failed with error: $($_.exception.message)" -Type Warning
    }
}

function Invoke-WebRequestEx
{
    <#
    .SYNOPSIS
        Get Connectivity via Invoke-WebRequest
    .DESCRIPTION
        Get Connectivity via Invoke-WebRequest, supporting proxy.
        This function takes a connectivity target definition, creates a PS(5) Job for each endpoint and protocol and returns the results.
        If PsSession is provided, the jobs are run on the remote machine.
        Success is defined as a 200 status code or a valid status code (not service available),
        with a GET method and the response Uri is the same as the request Uri.
    .EXAMPLE
        PS C:\> Invoke-WebRequestEx -Target $Target
        Explanation of what the example does
    .INPUTS
        URI
    .OUTPUTS
        Output (if any)
    .NOTES
        In the case of a proxy being provided, the proxy is used for all endpoints.
        In the case of a proxy being configured on the box, invoke-webrequest will use the wininet proxy for all calls.
        Certificate Validation is disabled for the calls.
    #>

    [CmdletBinding()]
    param (
        [Parameter()]
        [psobject]
        $Target,

        [Parameter()]
        [System.Management.Automation.Runspaces.PSSession[]]
        $PsSession,

        [Parameter()]
        [string]
        $Proxy,

        [Parameter()]
        [pscredential]
        $ProxyCredential

    )
    $ScriptBlock = {
        $EndPoints = $args[0]
        $Protocol = $args[1]
        $TimeoutSecs = $args[2]
        $Proxy = $args[3]
        $ProxyCredential = $args[4]

        $target.TimeStamp = [datetime]::UtcNow
        $timeoutSecondsDefault = 10
        if ([string]::IsNullOrEmpty($TimeoutSecs))
        {
            $timeout = $timeoutSecondsDefault
        }
        else
        {
            $timeout = $TimeoutSecs
        }
        # Create an array of jobs for all uris and protocols
        $iwrJobs = @()
        foreach ($uri in $EndPoints)
        {
            foreach ($p in $Protocol)
            {
                # ScriptBlock to test connectivity
                $iwrScriptBlock = {
                    try
                    {
                        $uri = $args[0]
                        $proxy = $args[1]
                        $proxyCred = $args[2]
                        $Timeout = $args[3]
                        $iwrParams =@{
                            Uri = $Uri
                            UseBasicParsing = $true
                            TimeoutSec = 30
                        }

                        # Ignore certificate validation and use TLS 1.2
                        if (-not ([System.Management.Automation.PSTypeName]'ServerCertificateValidationCallback').Type)
                        {
                            $certCallback = @"
                            using System;
                            using System.Net;
                            using System.Net.Security;
                            using System.Security.Cryptography.X509Certificates;
                            public class ServerCertificateValidationCallback
                            {
                                public static void Ignore()
                                {
                                    if(ServicePointManager.ServerCertificateValidationCallback == null)
                                    {
                                        ServicePointManager.ServerCertificateValidationCallback +=
                                            delegate
                                            (
                                                Object obj,
                                                X509Certificate certificate,
                                                X509Chain chain,
                                                SslPolicyErrors errors
                                            )
                                            {
                                                return true;
                                            };
                                    }
                                }
                            }
"@

                            $null = Add-Type $certCallback
                            $null = [ServerCertificateValidationCallback]::Ignore()
                            $null = [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
                        }

                        if ($proxy) {
                            $iwrParams.Add('Proxy', $proxy)
                            $iwrParams.Add('ProxyUseDefaultCredentials', $true)
                        }

                        if ($proxyCred) {
                            $iwrParams.Remove('ProxyUseDefaultCredentials')
                            $iwrParams.Add('ProxyCredential', $proxyCred)
                        }
                        $measure = Measure-Command -Expression { $webOut = Invoke-WebRequest @iwrParams }
                        $statusCode = $webout.StatusCode
                        $webResponse = $webOut.BaseResponse
                        $headers = $webOut.Headers
                        $content = $webOut.Content
                    }
                    catch {
                        $webResponse = $_.Exception.Response

                        if ($webResponse) {
                            try {
                                $statusCode = [int]$webResponse.StatusCode
                                $headers = @{}
                                $content = [System.Text.Encoding]::UTF8.GetString($webResponse.GetResponseStream().ToArray())
                                foreach ($header in $webResponse.Headers) {
                                    $headers.$header = $webResponse.GetResponseHeader($header)
                                }
                                if ($webResponse.ContentType -eq 'application/json') {
                                    $content = ConvertFrom-Json -InputObject $content
                                }
                            }
                            catch {}
                        }
                        $exception = @{ExceptionMessage = $_.Exception.Message; ErrorDetails = $_.ErrorDetails.Message; NonHTTPFailure = [System.String]::IsNullOrEmpty($webResponse) }
                    }

                    # Response Analysis
                    # if status code is 200 return true
                    # otherwise if the status code is a HTTP status code and the response Uri is the same as the request Uri and the method is GET, return true
                    $isHttpStatusCode = $webResponse.StatusCode -is [System.Net.HttpStatusCode]
                    $responseUriMatch = ([system.uri]$webResponse.ResponseUri).Host -eq ([system.uri]$uri).Host
                    $responseMethodIsGet = $webResponse.Method -eq 'GET'
                    $serviceUnavailable = $webResponse.StatusCode -eq [System.Net.HttpStatusCode]::ServiceUnavailable
                    $test = $webResponse.StatusCode -eq [System.Net.HttpStatusCode]::OK -or ($isHttpStatusCode -and $responseUriMatch -and $responseMethodIsGet -and !$serviceUnavailable)
                    # Gather TCP net connection to test layer 2 connectivity if it failed.
                    if (-not $test)
                    {
                        $tnc = Test-NetConnection -ComputerName ([system.uri]$uri).Host -Port ([system.uri]$uri).Port -InformationLevel Quiet -WarningAction SilentlyContinue
                    }

                    $Detail = "TestIsSuccess: $test`r`nStatusCode: $statusCode`r`nResponseUri: $($webResponse.ResponseUri)`r`nResponseUriMatch: $responseUriMatch`r`nResponseMethodIsGet: $responseMethodIsGet`r`nTCPNetConnection: $tnc`r`nserviceUnavailable: $serviceUnavailable"
                    return (New-Object -TypeName PSObject -Property @{
                        Source       = $env:ComputerName
                        Resource     = $Uri
                        Protocol     = $p
                        Status       = if ($test) { "Succeeded" } else { "Failed" }
                        TimeStamp    = [datetime]::UtcNow
                        StatusCode   = $StatusCode
                        Detail       = $Detail
                        DebugDtls   = (New-Object -TypeName 'PSObject' -Property @{
                            'Test' = $test
                            'Uri' = $Uri
                            'StatusCode'= $statusCode
                            'TCPNetConnection' = $tnc
                            'WebResponse' = $webResponse
                            'Headers' = $headers
                            'Exception' = $exception
                            'serviceUnavailable' = $serviceUnavailable
                            'ResponseUriMatch' = $responseUriMatch
                            'ResponseMethodIsGet' = $responseMethodIsGet
                            'ResponseUri' = $webResponse.ResponseUri
                            'ExceptionMessage' = $Exception.ExceptionMessage
                            'ErrorDetails' = $Exception.ErrorDetails
                            'NonHTTPFailure' = $Exception.NonHTTPFailure
                            'Server' = $webResponse.Server
                            'PowerShellVersion' = $PSVersionTable.PSVersion.Major
                            'Measure' = $measure.TotalMilliseconds
                        })
                    })
                }

                $Uri = "{0}://{1}" -f $p, ($Uri -Replace '\*', 'www')
                # Run scriptblock in PowerShell 5 to support responseuri
                $iwrJobs += Start-Job -PSVersion 5.1 -ArgumentList $Uri, $proxy, $proxyCred, $timeout -ScriptBlock $iwrScriptBlock
            }
        }
        $null = $iwrJobs | Wait-Job
        $results = @()
        $results += $iwrJobs | Receive-Job
        $iwrJobs | Remove-Job
        return $results
    }
    # Create a copy of the Target object
    $result = $Target | Select-Object -Property *
    $sessionArgs = @()
    if ($result)
    {
        $sessionArgs += @($result.EndPoint, $result.Protocol,$result.Tags.TimeoutSecs)
    }
    if ($Proxy)
    {
        $sessionArgs += $Proxy
    }
    if ($ProxyCredential)
    {
        $sessionArgs += $ProxyCredential
    }
    $result.AdditionalData += if ($PsSession)
    {
        Invoke-Command -Session $PsSession -ScriptBlock $ScriptBlock -ArgumentList $sessionArgs
    }
    else
    {
        Invoke-Command -ScriptBlock $ScriptBlock -ArgumentList $sessionArgs
    }
    if ($result.AdditionalData.Status -contains 'Failed')
    {
        $result.Status = 'Failed'
    }
    else
    {
        $result.Status = 'Succeeded'
    }
    # In the case of failures, log the debug info to aid troubleshooting
    $result.AdditionalData | ForEach-Object {
        if ( $_.Status -eq 'Failed' ){
            Log-Info ("{0}: {1}" -f $_.Status, $_.Resource) -Type Warning
            Log-Info ("Debug {0}: {1}" -f $_.Resource, ($_.DebugDtls | ConvertTo-Json)) -Type Warning
        }
        else {
            Log-Info ("{0}: {1}" -f $_.Status, $_.Resource)
        }
        $_.DebugDtls = 'redacted'
        Log-Info ("{0}: {1}" -f $_.Status, $_.Resource) -Type $(if ( $_.Status -eq 'Failed' ){ "Warning" } else { "Info" } )
    }
    $result.HealthCheckSource = $ENV:EnvChkrId
    return $result
}

function Get-ProxyDiagnostics
{
    [CmdletBinding()]
    param(
        [Parameter()]
        [System.Management.Automation.Runspaces.PSSession]
        $PsSession,

        [Parameter()]
        [string]
        $Proxy
    )
    Log-Info "Gathering proxy diagnostics"
    $proxyConfigs = @()
    # This might have to move to support proxy detection
    if (-not [string]::IsNullOrEmpty($Proxy))
    {
        $proxyConfigs += Test-ProxyServer -PsSession $PsSession -Proxy $Proxy
    }
    $proxyConfigs += Get-WinHttp -PsSession $PsSession
    $proxyConfigs += Get-ProxyEnvironmentVariable -PsSession $PsSession
    $proxyConfigs += Get-IEProxy -PsSession $PsSession
    Log-Info ("Proxy details: {0}" -f $(($proxyConfigs | ConvertTo-Json -Depth 20) -replace "`r`n", ''))
    return $proxyConfigs
}

function Test-ProxyServer
{
    [CmdletBinding()]
    param(
        [Parameter()]
        [System.Management.Automation.Runspaces.PSSession]
        $PsSession,

        [Parameter()]
        [string]
        $Proxy
    )

    Log-Info "Testing User specified Proxy"
    $userProxy = $Script:AzStackHciConnectivityTargets | Where-Object Name -EQ System_Check_User_Proxy
    $UserProxyUri = [system.uri]$Proxy
    $userProxy.EndPoint = "{0}:{1}" -f $UserProxyUri.Host, $UserProxyUri.Port
    $userProxy.Protocol = $UserProxyUri.Scheme
    $userProxy.Service = @('System')
    $UserProxyResult = Invoke-WebRequestEx -Target $userProxy -PsSession $PsSession
    return $UserProxyResult
}

function Get-WinHttp
{
    [CmdletBinding()]
    param(
        [Parameter()]
        [System.Management.Automation.Runspaces.PSSession]
        $PsSession
    )
    Log-Info "Gathering WinHttp Proxy settings"
    $netshSb = {
        #$netsh = netsh winhttp show proxy
        @{
            Source   = $ENV:COMPUTERNAME
            Resource = netsh winhttp show proxy
            Status   = 'Succeeded'
        }
    }

    $netsh = if ($PsSession)
    {
        Invoke-Command -Session $PsSession -ScriptBlock $netshSb
        $TargetResourceName = "WinHttp_Proxy_$($PsSession.ComputerName)"
    }
    else
    {
        Invoke-Command -ScriptBlock $netshSb
        $TargetResourceName = "WinHttp_Proxy_$($ENV:COMPUTERNAME)"
    }

    $winHttpProxy = New-Object -Type ProxyDiagnostics -Property @{
        Name               = 'AzStackHci_Connectivity_Collect_Proxy_Diagnostics_WinHttp'
        Title              = 'WinHttp Proxy Settings'
        Severity           = 'Informational'
        Description        = 'Collects proxy configuration for WinHttp'
        Tags               = $null
        Remediation        = "https://docs.microsoft.com/en-us/azure-stack/hci/concepts/firewall-requirements?tabs=allow-table#set-up-a-proxy-server"
        TargetResourceID   = '767c0b95-a3c9-43dd-b112-76dff50f2c75'
        TargetResourceName = $TargetResourceName
        TargetResourceType = 'Proxy_Setting'
        Timestamp          = [datetime]::UtcNow
        Status             = 'Succeeded'
        Service            = 'System'
        AdditionalData     = New-object PsObject -Property @{
            source   = $netsh.Source
            resource = if ($netsh.resource -like '*Direct access (no proxy server)*') { '<Not configured>' } else { [string]$netsh.resource -replace "`r`n", "" -replace 'Current WinHTTP proxy settings:', '' -replace ' ', '' }
            status   = if ([string]::IsNullOrEmpty($netsh.status)) { 'Failed' } else { 'Succeeded' }
            detail   = $netsh.resource
        }
        HealthCheckSource = $ENV:EnvChkrId
    }
    return $winHttpProxy
}

function Get-ProxyEnvironmentVariable
{
    <#
    .SYNOPSIS
        Get Proxy configuration from environment variables
    .DESCRIPTION
        Get Proxy configuration from environment variables
    .EXAMPLE
        PS C:\> Get-ProxyEnvironmentVariable
        Explanation of what the example does
    .INPUTS
        URI
    .OUTPUTS
        Output (if any)
    .NOTES
    #>

    [CmdletBinding()]
    param (
        [Parameter()]
        [System.Management.Automation.Runspaces.PSSession]
        $PsSession
    )
    Log-Info "Gathering Proxy settings from environment variables"

    $envProxySb = {
        $AdditionalData = @()

        $AdditionalData += New-Object PsObject -Property @{
            Source   = "{0}_{1}" -f $ENV:COMPUTERNAME, "HTTPS_PROXY"
            Resource = if ($env:HTTPS_PROXY) { $env:HTTPS_PROXY } else { '<Not configured>' }
            Status   = 'Succeeded'
        }

        $AdditionalData += New-Object PsObject -Property @{
            Source   = "{0}_{1}" -f $ENV:COMPUTERNAME, "HTTP_PROXY"
            Resource = if ($env:HTTP_PROXY) { $env:HTTP_PROXY } else { '<Not configured>' }
            Status   = 'Succeeded'
        }
        return $AdditionalData
    }
    [array]$EnvironmentProxyOutput = if ($PsSession)
    {
        Invoke-Command -Session $PsSession -ScriptBlock $envProxySb
        $TargetResourceName = "Environment_Proxy_$($PsSession.ComputerName)"
        $Source = $PsSession.ComputerName
    }
    else
    {
        Invoke-Command -ScriptBlock $envProxySb
        $TargetResourceName = "Environment_Proxy_$($ENV:COMPUTERNAME)"
        $Source = $ENV:COMPUTERNAME
    }

    $EnvProxy = New-Object -Type ProxyDiagnostics -Property @{
        Name               = 'AzStackHci_Connectivity_Collect_Proxy_Diagnostics_Environment'
        Title              = 'Environment Proxy Settings'
        Severity           = 'Informational'
        Description        = 'Collects proxy configuration from environment variables'
        Tags               = $null
        Remediation        = "https://docs.microsoft.com/en-us/azure-stack/aks-hci/set-proxy-settings"
        TargetResourceID   = 'cb019485-676c-4c7d-98a8-fde6e5f35dfb'
        TargetResourceName = $TargetResourceName
        TargetResourceType = 'Proxy_Setting'
        Timestamp          = [datetime]::UtcNow
        Status             = 'Succeeded'
        Service            = 'System'
        AdditionalData     = $EnvironmentProxyOutput
        HealthCheckSource  = $ENV:EnvChkrId
    }
    return $EnvProxy
}

function Get-IEProxy
{
    <#
    .SYNOPSIS
        Get Proxy configuration from IE
    .DESCRIPTION
        Get Proxy configuration from IE
    .EXAMPLE
        PS C:\> Get-IEProxy
        Explanation of what the example does
    .INPUTS
        URI
    .OUTPUTS
        Output (if any)
    .NOTES
    [System.Net.WebProxy]::GetDefaultProxy()
        Address :
        BypassProxyOnLocal : False
        BypassList : {}
        Credentials :
        UseDefaultCredentials : False
        BypassArrayList : {}
    #>

    [CmdletBinding()]
    param (
        [Parameter()]
        [System.Management.Automation.Runspaces.PSSession]
        $PsSession
    )
    Log-Info "Gathering IE Proxy settings"
    $ieProxySb = {
        $ErrorActionPreference = 'SilentlyContinue'
        if ($PSVersionTable['Platform'] -eq 'Win32NT' -or $PSVersionTable['PSEdition'] -eq 'Desktop' )
        {
            $IeProxySettings = Get-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings' | Select-Object ProxyServer, ProxyEnable
            New-Object PsObject -Property @{
                Source   = "$($ENV:COMPUTERNAME)"
                Resource = if ([string]::IsNullOrEmpty($IeProxySettings.ProxyServer) -and [string]::IsNullOrEmpty($IeProxySettings.ProxyEnable)) {
                    '<Not configured>'
                } else {
                    "{0} (Enabled:{1})" -f $IeProxySettings.ProxyServer, $IeProxySettings.ProxyEnable
                }
                Detail   = $IeProxySettings
                Status   = 'Succeeded'
            }
        }
    }
    [array]$AdditionalData = if ($PsSession)
    {
        Invoke-Command -Session $PsSession -ScriptBlock $ieProxySb
        $TargetResourceName = "IE_Proxy_$($PsSession.ComputerName)"
    }
    else
    {
        Invoke-Command -ScriptBlock $ieProxySb
        $TargetResourceName = "IE_Proxy_$($ENV:COMPUTERNAME)"
    }

    if (-not $AdditionalData)
    {
        Log-Info "No IE Proxy settings available"
        return $null
    }
    else
    {
        $ieProxy = New-Object -Type ProxyDiagnostics -Property @{
            Name               = 'AzStackHci_Connectivity_Collect_Proxy_Diagnostics_IEProxy'
            Title              = 'IE Proxy Settings'
            Severity           = 'Informational'
            Description        = 'Collects Proxy configuration from IE'
            Tags               = $null
            Remediation        = "https://docs.microsoft.com/en-us/azure-stack/hci/concepts/firewall-requirements?tabs=allow-table#set-up-a-proxy-server"
            TargetResourceID   = 'fe961ba6-295d-4880-82aa-2dd7322658d5'
            TargetResourceName = $TargetResourceName
            TargetResourceType = 'Proxy_Setting'
            Timestamp          = [datetime]::UtcNow
            Status             = 'Succeeded'
            Service            = 'System'
            AdditionalData     = $AdditionalData
            HealthCheckSource  = $ENV:EnvChkrId
        }
        return $ieProxy
    }
}

function Write-FailedUrls
{
    [CmdletBinding()]
    param (
        $result
    )
    if (-not [string]::IsNullOrEmpty($Global:AzStackHciEnvironmentLogFile))
    {
        $file = Join-Path -Path (Split-Path $Global:AzStackHciEnvironmentLogFile -Parent) -ChildPath FailedUrls.txt
    }
    $failedUrls = $result.AdditionalData | Where-Object Status -NE Succeeded | Select-Object -ExpandProperty Resource
    if ($failedUrls.count -gt 0)
    {
        Log-Info ("[Over]Writing {0} to {1}" -f ($failedUrls -split ','), $file)
        $failedUrls | Out-File $file -Force
        Log-Info "`nFailed Urls log: $file" -ConsoleOut
    }
}

function Select-AzStackHciConnectivityTarget
{
    <#
    .SYNOPSIS
        Apply user exclusions to Connectivity Targets
    #>


    [CmdletBinding()]
    param (
        [Parameter()]
        [psobject]
        $Targets,

        [Parameter()]
        [string[]]
        $Exclude,

        [Parameter()]
        [string]
        $FilePath = "$PSScriptRoot\..\ExcludeTests.txt"
    )

    try
    {
        $returnList = @($Targets)
        if ($exclude)
        {
            Log-Info "Removing tests $($exclude -join ',')"
            $returnList = $returnList | Where-Object { $_.Service | Select-String -Pattern $exclude -NotMatch }
        }
        if ($returnList.count -eq 0)
        {
            throw "No tests to perform after filtering"
        }
        if (Test-Path -Path $FilePath)
        {
            $fileExclusion = Get-Content -Path $FilePath
            Log-Info "Reading exclusion file $FilePath" -ConsoleOut
            Log-Info "Applying file exclusions: $($fileExclusion -join ',')" -ConsoleOut
            $returnList = $returnList | Where-Object {( $_.Service | Select-String -Pattern $fileExclusion -NotMatch ) -and ( $_.endpoint | Select-String -Pattern $fileExclusion -NotMatch )}
        }

        Log-Info "Test list: $($returnList -join ',')"
        if ($returnList.Count -eq 0)
        {
            Log-Info -Message "No tests to run." -ConsoleOut -Type Warning
            break noTestsBreak
        }
        return $returnList
    }
    catch
    {
        Log-Info "Failed to filter test list. Error: $($_.exception)" -Type Warning
    }
}

function Get-CloudEndpointFromManifest
{
    [CmdletBinding()]
    param (
        [Parameter()]
        [system.uri]
        $Uri = 'https://aka.ms/hciconnectivitytargets'
    )

    try
    {
        $tempXmlFile = Join-Path -Path $env:temp -ChildPath 'AzStackHciConnectivityTarget.xml'
        Write-Verbose "Retrieving connectivity targets from $Uri to temp location: $tempXmlFile..."
        $iwrParams = @{
            Uri = $Uri
            UseBasicParsing = $true
            OutFile = $tempXmlFile
        }
        $response = Invoke-WebRequest @iwrParams

        $testSigningScript = Join-Path -Path $PSScriptRoot -ChildPath 'TestXmlSigning.ps1'
        Write-Verbose "Validating signature of $tempXmlFile..."
        [bool]$checkSigningResult = Start-Job -PSVersion 5.1 -ScriptBlock { & $USING:testSigningScript $USING:tempXmlFile } | Wait-Job | Receive-Job -ErrorAction SilentlyContinue
        Write-Verbose "Signature validation result: $checkSigningResult"
        if (-not $checkSigningResult)
        {
            throw "Failed to validate signature of $tempXmlFile from $url"
        }
        [xml]$manifest = Get-Content -Path  $tempXmlFile
        $version = $manifest.Objects.Object.Property | Where-Object Name -eq Version | Select -ExpandProperty '#text'
        $title = $manifest.Objects.Object.Property | Where-Object Name -eq Title | Select -ExpandProperty '#text'
        $targets = $manifest.Objects.Object.Property | Where-Object Name -eq Targets | Select -ExpandProperty 'Property'
        [AzStackHciConnectivityTarget[]]$targets = New-AzStackHciConnectivityTargetFromXml -TargetXml $targets
        $msg = "Retrieved $($targets.count) connectivity targets from $($title) version $($version)"
        Write-Verbose $msg
        return $targets
    }
    catch
    {
        $msg = "Failed to get connectivity targets from $Uri. Error: $($_.exception.message)"
        Write-Verbose $msg
    }
    finally
    {
        if (Test-Path -Path $tempXmlFile)
        {
            Remove-Item -Path $tempXmlFile -ErrorAction SilentlyContinue
        }
    }
}


function Export-AzStackHciConnectivityTargetToXml
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [string]
        $TargetDirectory,

        [Parameter(Mandatory)]
        [string]
        $TargetFileName,

        [Parameter(Mandatory)]
        [string]
        $Version
    )
    $AzStackHciConnectivityTargets = Get-AzStackHciConnectivityTarget -LocalOnly -IncludeSystem
    Write-Host ("Found {0} connectivity targets for manifest" -f [int]($Script:AzStackHciConnectivityTargets.Count))
    Write-Host "Creating manifest with version $version"
    $manifest = New-Object AzStackHciConnectivityManifest -Property @{
        Title = 'AzStackHci Connectivity Endpoint Definitions'
        Version = $Version
        Targets = $Script:AzStackHciConnectivityTargets
    }
    $manifest | ConvertTo-Xml -Depth 5 -As Stream | Out-File $TargetDirectory\$TargetFileName -Encoding utf8
}

function New-AzStackHciConnectivityTargetFromXml
{
    param ($TargetXml)
    foreach ($target in $TargetXml)
    {
        $ErrorActionPreference = 'Stop'
        $AzStackHciConnectivityTargetObject = New-Object AzStackHciConnectivityTarget
        # Arrays
        'EndPoint', 'Protocol', 'Service', 'OperationType' | Foreach-Object {
            $AzStackHciConnectivityTargetObject.$PSITEM = ($target.Property | Where-Object Name -eq $PSITEM).ChildNodes.'#text'
        }

        # Booleans
        'Mandatory', 'System' | ForEach-Object {
            $AzStackHciConnectivityTargetObject.$PSITEM = if (($target.Property | Where-Object Name -eq $PSITEM).'#text' -eq 'True') { $true } else { $false }
        }

        # Strings
        'Name', 'Title', 'Severity', 'Description', 'Remediation', 'TargetResourceID', 'TargetResourceName', 'TargetResourceType', 'Group' | Foreach-Object {
            $AzStackHciConnectivityTargetObject.$PSITEM = ($target.Property | Where-Object Name -eq $PSITEM).'#text'
        }

        # Tags
        $ErrorActionPreference = 'SilentlyContinue'
        $tagsProperties = $target.Property | Where-Object Name -eq Tags | Select-Object -ExpandProperty Property

        $Groups = $tagsProperties | Where-Object Name -eq Group
        $Mandatory = $tagsProperties | Where-Object Name -eq Mandatory
        $Service = $tagsProperties | Where-Object Name -eq Service
        $OperationType = $tagsProperties | Where-Object Name -eq OperationType
        $ExpectedSubject = $tagsProperties | Where-Object Name -eq ExpectedSubject

        # Tags are optional, we iterate through them to
        $tagHash = @{}
        if ($Groups) { $tagHash += @{Group = $Groups.'#text'}}
        if ($Service) { $tagHash += @{Service = $Service.ChildNodes.'#text'}}
        if ($Mandatory) { $tagHash += @{Mandatory = if ($Mandatory.'#text' -eq 'True') { $true } else { $false }}}
        if ($OperationType) { $tagHash += @{OperationType = $OperationType.ChildNodes.'#text'}}
        if ($ExpectedSubject) { $tagHash += @{ExpectedSubject = $ExpectedSubject.ChildNodes.'#text'}}

        $AzStackHciConnectivityTargetObject.Tags = New-Object PsObject -Property $tagHash
        Write-Verbose $AzStackHciConnectivityTargetObject.Name
        $AzStackHciConnectivityTargetObject
    }
}
# SIG # Begin signature block
# MIIoLQYJKoZIhvcNAQcCoIIoHjCCKBoCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAmhuQN0Ig48wl/
# A4PhnkPGXlMV0qTSXzdjDmbwtsJ78aCCDXYwggX0MIID3KADAgECAhMzAAADTrU8
# esGEb+srAAAAAANOMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjMwMzE2MTg0MzI5WhcNMjQwMzE0MTg0MzI5WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDdCKiNI6IBFWuvJUmf6WdOJqZmIwYs5G7AJD5UbcL6tsC+EBPDbr36pFGo1bsU
# p53nRyFYnncoMg8FK0d8jLlw0lgexDDr7gicf2zOBFWqfv/nSLwzJFNP5W03DF/1
# 1oZ12rSFqGlm+O46cRjTDFBpMRCZZGddZlRBjivby0eI1VgTD1TvAdfBYQe82fhm
# WQkYR/lWmAK+vW/1+bO7jHaxXTNCxLIBW07F8PBjUcwFxxyfbe2mHB4h1L4U0Ofa
# +HX/aREQ7SqYZz59sXM2ySOfvYyIjnqSO80NGBaz5DvzIG88J0+BNhOu2jl6Dfcq
# jYQs1H/PMSQIK6E7lXDXSpXzAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUnMc7Zn/ukKBsBiWkwdNfsN5pdwAw
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzUwMDUxNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAD21v9pHoLdBSNlFAjmk
# mx4XxOZAPsVxxXbDyQv1+kGDe9XpgBnT1lXnx7JDpFMKBwAyIwdInmvhK9pGBa31
# TyeL3p7R2s0L8SABPPRJHAEk4NHpBXxHjm4TKjezAbSqqbgsy10Y7KApy+9UrKa2
# kGmsuASsk95PVm5vem7OmTs42vm0BJUU+JPQLg8Y/sdj3TtSfLYYZAaJwTAIgi7d
# hzn5hatLo7Dhz+4T+MrFd+6LUa2U3zr97QwzDthx+RP9/RZnur4inzSQsG5DCVIM
# pA1l2NWEA3KAca0tI2l6hQNYsaKL1kefdfHCrPxEry8onJjyGGv9YKoLv6AOO7Oh
# JEmbQlz/xksYG2N/JSOJ+QqYpGTEuYFYVWain7He6jgb41JbpOGKDdE/b+V2q/gX
# UgFe2gdwTpCDsvh8SMRoq1/BNXcr7iTAU38Vgr83iVtPYmFhZOVM0ULp/kKTVoir
# IpP2KCxT4OekOctt8grYnhJ16QMjmMv5o53hjNFXOxigkQWYzUO+6w50g0FAeFa8
# 5ugCCB6lXEk21FFB1FdIHpjSQf+LP/W2OV/HfhC3uTPgKbRtXo83TZYEudooyZ/A
# Vu08sibZ3MkGOJORLERNwKm2G7oqdOv4Qj8Z0JrGgMzj46NFKAxkLSpE5oHQYP1H
# tPx1lPfD7iNSbJsP6LiUHXH1MIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg
# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03
# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr
# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg
# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy
# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9
# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh
# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k
# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB
# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn
# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90
# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w
# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o
# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD
# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa
# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny
# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG
# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t
# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV
# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG
# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl
# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb
# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l
# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6
# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0
# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560
# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam
# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa
# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah
# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA
# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt
# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr
# /Xmfwb1tbWrJUnMTDXpQzTGCGg0wghoJAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAANOtTx6wYRv6ysAAAAAA04wDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIGmRnKFNKhHmNOnBV8UTDHHB
# gBOmb9B2qv3wJm37haxEMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEASHPNbebeQWamGc3a8I6P22sB4/Mo2XvuSWFhwUPXNSr4cWjVDRphmSkQ
# rhDjBssFYWz7FbqyobKv8Icn/PAsfm+kM/hSOL8y8o0bLaSrXG6E/RBFUqIZkYA8
# sjlOzthX6OdMXQcX8J6D7FVZWpmReBCLZT+XNPunUfL7RwHbDeVbT7aB04bait3L
# ePzI/ms7DsReR5YE+l62sLeVJyIPcIkYb6KvdV/Uzyu3gRcptgogjgHO1HYwVOh4
# lTKO+WfGxSlqp42FFxbVnlt9wTMHXoRhv9R33CyoJd7n6WV2C9YTmd5ffWFdNlA4
# zXe8EM3dm/uvRL64d4KND7ea9VAodKGCF5cwgheTBgorBgEEAYI3AwMBMYIXgzCC
# F38GCSqGSIb3DQEHAqCCF3AwghdsAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq
# hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCCnAfCkurB6x7DHZQ7vb2hYEHJDR7/iTXS/MbI9xRs6ogIGZQPePKSv
# GBMyMDIzMDkyMjA4MzEwNS4zMzFaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l
# cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046QTkzNS0w
# M0UwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg
# ghHtMIIHIDCCBQigAwIBAgITMwAAAdGyW0AobC7SRQABAAAB0TANBgkqhkiG9w0B
# AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD
# VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMzA1MjUxOTEy
# MThaFw0yNDAyMDExOTEyMThaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz
# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
# cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z
# MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046QTkzNS0wM0UwLUQ5NDcxJTAjBgNV
# BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQCZTNo0OeGz2XFd2gLg5nTlBm8XOpuwJIiXsMU61rwq
# 1ZKDpa443RrSG/pH8Gz6XNnFQKGnCqNCtmvoKULApwrT/s7/e1X0lNFKmj7U7X4p
# 00S0uQbW6LwSn/zWHaG2c54ZXsGY+BYfhWDgbFpCTxRzTnRCG62bkWPp6ZHbZPg4
# Ht1CRCAMhhOGTR8wI4G7wwWZwdMc6UvUUlq0ql9AxAfzkYRpi2tRvDHMdmZ3vyXp
# qhFwvRG8cgCH/TTCjW5q6aNbdqKL3BFDPzUtuCNsPXL3/E0dR2bDMqa0aNH+iIfh
# GC4/vcwuteOMCPUIDVSqDCNfIaPDEwYci1fd9gu1zVw+HEhDZM7Ea3nxIUrzt+Rf
# p5ToMMj4QAmJ6Uadm+TPbDbo8kFIK70ShmW8wn8fJk9ReQQEpTtIN43eRv9QmXy3
# Ued80osOBE+WkdMvSCFh+qgCsKdzQxQJG62cTeoU2eqNhH3oppXmyfVUwbsefQzM
# PtbinCZd0FUlmlM/dH+4OniqQyaHvrtYy3wqIafY3zeFITlVAoP9q9vF4W7KHR/u
# F0mvTpAL5NaTDN1plQS0MdjMkgzZK5gtwqOe/3rTlqBzxwa7YYp3urP5yWkTzISG
# nhNWIZOxOyQIOxZfbiIbAHbm3M8hj73KQWcCR5JavgkwUmncFHESaQf4Drqs+/1L
# 1QIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFAuO8UzF7DcH0mmsF4XQxxHQvS2jMB8G
# A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG
# Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy
# MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w
# XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy
# dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG
# A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD
# AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQCbu9rTAHV24mY0qoG5eEnImz5akGXTviBw
# Kp2Y51s26w8oDrWor+m00R4/3BcDmYlUK8Nrx/auYFYidZddcUjw42QxSStmv/qW
# nCQi/2OnH32KVHQ+kMOZPABQTG1XkcnYPUOOEEor6f/3Js1uj4wjHzE4V4aumYXB
# Asr4L5KR8vKes5tFxhMkWND/O7W/RaHYwJMjMkxVosBok7V21sJAlxScEXxfJa+/
# qkqUr7CZgw3R4jCHRkPqQhMWibXPMYar/iF0ZuLB9O89DMJNhjK9BSf6iqgZoMuz
# IVt+EBoTzpv/9p4wQ6xoBCs29mkj/EIWFdc+5a30kuCQOSEOj07+WI29A4k6QIRB
# 5w+eMmZ0Jec0sSyeQB5KjxE51iYMhtlMrUKcr06nBqCsSKPYsSAITAzgssJD+Z/c
# TS7Cu35fJrWhM9NYX24uAxYLAW0ipNtWptIeV6akuZEeEV6BNtM3VTk+mAlV5/eC
# /0Y17aVSjK5/gyDoLNmrgVwv5TAaBmq/wgRRFHmW9UJ3zv8Lmk6mIoAyTpqBbuUj
# MLyrtajuSsA/m2DnKMO0Qiz1v+FSVbqM38J/PTlhCTUbFOx0kLT7Y/7+ZyrilVCz
# yAYfFIinDIjWlM85tDeU8ZfJCjFKwq3DsRxV4JY18xww8TTmod3lkr9NqGQ54Lmy
# PVc+5ibNrjCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI
# hvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw
# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
# MjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAy
# MDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
# bWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
# AQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25Phdg
# M/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPF
# dvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6
# GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBp
# Dco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50Zu
# yjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3E
# XzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0
# lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1q
# GFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ
# +QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PA
# PBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkw
# EgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxG
# NSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARV
# MFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWlj
# cm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAK
# BggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC
# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX
# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v
# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI
# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG
# 9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0x
# M7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmC
# VgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449
# xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wM
# nosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDS
# PeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2d
# Y3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxn
# GSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+Crvs
# QWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokL
# jzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL
# 6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNQ
# MIICOAIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn
# MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOkE5MzUtMDNFMC1EOTQ3MSUwIwYDVQQD
# ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQBH
# JY2Fv+GhLQtRDR2vIzBaSv/7LKCBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w
# IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6LeWnzAiGA8yMDIzMDkyMjA0Mjkx
# OVoYDzIwMjMwOTIzMDQyOTE5WjB3MD0GCisGAQQBhFkKBAExLzAtMAoCBQDot5af
# AgEAMAoCAQACAhb0AgH/MAcCAQACAhKDMAoCBQDouOgfAgEAMDYGCisGAQQBhFkK
# BAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJ
# KoZIhvcNAQELBQADggEBAMMZorCacoCGWONUA0UprCJrLvLeTJ4b44I+sLVpt09M
# liZFEw+lydZaB5kgmnaYEqCJQTlzvUEwmhjJPkbJQpMwlHCO56rbfNUy6NeL+ry/
# 0k+6/jVsktnFGB/bThmMKLqURQwRSjL9B/EAZK1weD7uaWj+WfAK70+UHnykQaTb
# qKDjRh8/PmFyzQ+v7STwmuB1DGuZySfYfvL9TSUrf9HkzF7zW/Nsr22psW7w9FfI
# ueH7ZDfksQsYTJuUWooeJf+V52ZFUo/l6hi82XIFVnj0H35uMoroyH2K52hRA72m
# yhrZs64BZ33zNaxyAkj/TR0oLZORGQvAac7WrnibMxUxggQNMIIECQIBATCBkzB8
# MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk
# bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1N
# aWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAdGyW0AobC7SRQABAAAB
# 0TANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEE
# MC8GCSqGSIb3DQEJBDEiBCDFRpPZJyCttbO4m8jT18gZY+6Ndre74Aj9KqIKoYOV
# wzCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIMy8YXkCALv57c5sRhrPTub1
# q4TwJ6oVA36k8IiI/AcMMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
# Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
# dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB
# IDIwMTACEzMAAAHRsltAKGwu0kUAAQAAAdEwIgQgvPNc74y+0hlzLzQQhN//sdFX
# fKbYz0wCrq96EjDDf4AwDQYJKoZIhvcNAQELBQAEggIAYwg14amlq8QvFuJ/Y2qF
# CmubLDPJQX2PYXntx/nP2Uxj+o3a1jSn3Ob9Sgu5Dna5DoacyG6On5K9msNj5IFf
# tKqOaXKJ1Pb5prQlQSv8mdPfTfBERNG/oA4fDWsYX3bjk5KWWFrkL5oXEm+QYpOh
# jrJ/DJfAt+snWUMS6ZjVZQ0eQ3h4oP66KTRslrz3AmqoGGBRHW3CUr3qPhBv4TiV
# vPSAduHZxTGwrh+8rNPSkIsc8knHF9TyJfzAKhokgsIUWVO2tdVcwYa9oLNo1e+t
# DMvj6Udm7zUXK6RdgFrGf2/kzIzyKbcjDcHtZnGbLkIGVvLZVvJtflzo8+rpdADH
# GhGqsHvBpnUvmsiRBpWkvlH1Et9sTz8f2rvvRiZg103Lp2JJn8gCRWEqTAOWUnFm
# CCXxka9mOQsxkyqbjr9o4djBlOnGmGXc7wT9OZ9Fgj5YJPjYYREwWx9vlsdUSupk
# H8dtgG5LeWgpR4f+kW5uBNJ7P4HbupDXsNdvTn5oKr3PgIPMeGRL97w4cmZkq+dy
# xkDTsIC2hQs2nEN8MzdUVe+5g8W6IsKzAf3pjhUptWtlxptICiBDGXHwzjDiNtC9
# I5S2NQhCeGPv4pQQLzCmyBHHfyVyhCwhB+85wBxYGsRV4P3OAN+P8+KnXQ/hVj7h
# FEI5AL+EbT+GDdvWzpa4RSM=
# SIG # End signature block