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
}

#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
    #>

    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  = 'Missing DNS Server'
                Status    = 'Failed'
                TimeStamp = [datetime]::UtcNow
                Source    = $ENV:COMPUTERNAME
                Detail = 'DNS not configured on this node.'
            }
        }
        else
        {
            foreach ($dnsServer in $dnsServers)
            {
                $dnsResult = $false
                $dnsResult = Resolve-DnsName -Name microsoft.com -Server $dnsServer -DnsOnly -ErrorAction SilentlyContinue -QuickTimeout -Type A
                $detail = "Queried dns server {0} for {1} on {2}. Result returned {3} A records: {4}, expected at least 1." -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  = ((Get-PSCallStack)[-1].Command)
    }
    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

    )
    try
    {
        Import-AzStackHciConnectivityTarget
        $executionTargets = @()
        # Additive allows the user to "-OR" their parameter values
        if ($Additive)
        {
            Log-Info -Message "Getting targets additively"
            if (-not [string]::IsNullOrEmpty($Service))
            {
                Log-Info -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))
            {
                Log-Info -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))
            {
                Log-Info -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))
            {
                Log-Info -Message ("Getting targets by Operation Type: {0}" -f ($OperationType -join ','))
                foreach ($Op in $OperationType)
                {
                    $executionTargets += $Script:AzStackHciConnectivityTargets | Where-Object { $Op -in $_.OperationType }
                }
            }
            else
            {
                Log-Info -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 ()
    try
    {
        $Script:AzStackHciConnectivityTargets = @()
        $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 ((Get-PSCallStack).Command -join '\')
                    $target.TargetResourceID = $target.EndPoint -join '_'
                    $target.TargetResourceName = $target.EndPoint -join '_'
                    $target.TargetResourceType = 'External Endpoint'
                    $Script:AzStackHciConnectivityTargets += [AzStackHciConnectivityTarget]$target
                }
            }
            catch
            {
                Log-Info -Message ("Unable to read {0}. Error: {1}" -f (Split-Path -Path $targetFile -Leaf), $_.Exception.Message) -Type Warning
            }
        }
    }
    catch
    {
        throw "Import failed: $($_.exception)"
    }
}

function Get-CloudEndpointFromManifest
{
    <#
    .SYNOPSIS
        Retrieve Endpoints to test from Cloud Manifest
    .DESCRIPTION
        Retrieve Endpoints to test from Cloud Manifest
    .EXAMPLE
        PS C:\> Get-CloudEndpointFromManifest -Uri
        Explanation of what the example does
    .INPUTS
        URI
    .OUTPUTS
        Output (if any)
    .NOTES
        URL: https://docs.microsoft.com/en-us/javascript/api/@azure/arm-azurestack/cloudmanifestfile?view=azure-node-preview
    #>

    [CmdletBinding()]
    param (
        [Parameter()]
        [System.Uri]
        $Uri
    )
    throw "Not implemented"
}

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]
            function Get-SslCertificateChain
            {
                <#
                .SYNOPSIS
                    Retrieve remote ssl certificate & chain from https endpoint for Desktop and Core
                .NOTES
                    Credit: https://github.com/markekraus
                #>

                [CmdletBinding()]
                param (
                    [system.uri]
                    $url,

                    [Parameter()]
                    [string]
                    $Proxy,

                    [Parameter()]
                    [pscredential]
                    $ProxyCredential
                )
                try
                {

                    $cs = @'
                using System;
                using System.Collections.Generic;
                using System.Net.Http;
                using System.Net.Security;
                using System.Security.Cryptography.X509Certificates;
 
                namespace CertificateCapture
                {
                    public class Utility
                    {
                        public static Func<HttpRequestMessage,X509Certificate2,X509Chain,SslPolicyErrors,Boolean> ValidationCallback =
                            (message, cert, chain, errors) => {
                                CapturedCertificates.Clear();
                                var newCert = new X509Certificate2(cert);
                                var newChain = new X509Chain();
                                newChain.Build(newCert);
                                CapturedCertificates.Add(new CapturedCertificate(){
                                    Certificate = newCert,
                                    CertificateChain = newChain,
                                    PolicyErrors = errors,
                                    URI = message.RequestUri
                                });
                                return true;
                            };
                        public static List<CapturedCertificate> CapturedCertificates = new List<CapturedCertificate>();
                    }
 
                    public class CapturedCertificate
                    {
                        public X509Certificate2 Certificate { get; set; }
                        public X509Chain CertificateChain { get; set; }
                        public SslPolicyErrors PolicyErrors { get; set; }
                        public Uri URI { get; set; }
                    }
                }
'@


                    if ($PSEdition -ne 'Core')
                    {
                        Add-Type -AssemblyName System.Net.Http
                        Add-Type $cs -ReferencedAssemblies System.Net.Http
                    }
                    else
                    {
                        Add-Type $cs
                    }

                    $Certs = [CertificateCapture.Utility]::CapturedCertificates
                    $Handler = [System.Net.Http.HttpClientHandler]::new()
                    if ($Proxy)
                    {
                        $Handler.Proxy = New-Object System.Net.WebProxy($proxy)
                        if ($proxyCredential)
                        {
                            $Handler.DefaultProxyCredentials = $ProxyCredential
                        }
                    }
                    $Handler.ServerCertificateCustomValidationCallback = [CertificateCapture.Utility]::ValidationCallback
                    $Client = [System.Net.Http.HttpClient]::new($Handler)
                    $null = $Client.GetAsync($url).Result
                    return $Certs.CertificateChain
                }
                catch
                {
                    throw $_
                }
            }

            $chain = Get-SslCertificateChain -Url $Uri -Proxy $Proxy -ProxyCredential $ProxyCredential
            if ($chain.ChainElements.Certificate.Count -le 1)
            {
                throw ("Unexpected certificate chain in response. Expected 2 or more certificates in chain, found {0}. {1}" -f $chain.ChainElements.Certificate.Count, `
                    ($chain.ChainElements.Certificate | ForEach-Object { "Thumbprint: {0}, Subject: {1}, Issuer: {2}" -f $_.Thumbprint, $_.Subject, $_.Issuer }))
            }
            return $chain.ChainElements.Certificate
        }
        $ChainElements = if ($PsSession)
        {
            Invoke-Command -Session $PsSession -ScriptBlock $sb -ArgumentList $Uri, $Proxy, $ProxyCredential
        }
        else
        {
            Invoke-Command -ScriptBlock $sb -ArgumentList $Uri, $Proxy, $ProxyCredential
        }
        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
    #>

    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)"
            }

            # 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
                # 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)) { [System.Net.Dns]::GetHostName() } 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'}
            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
    .EXAMPLE
        PS C:\> Invoke-WebRequestEx -Target $Target
        Explanation of what the example does
    .INPUTS
        URI
    .OUTPUTS
        Output (if any)
    .NOTES
    #>

    [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
        $AdditionalData = @()
        $timeoutSecondsDefault = 10
        if ([string]::IsNullOrEmpty($TimeoutSecs))
        {
            $timeout = $timeoutSecondsDefault
        }
        else
        {
            $timeout = $TimeoutSecs
        }

        foreach ($uri in $EndPoints)
        {
            foreach ($p in $Protocol)
            {
                # TO DO handle wildcards
                $invokeParams = @{
                    Uri             = "{0}://{1}" -f $p, $Uri.Replace('*', 'www')
                    UseBasicParsing = $true
                    Timeout         = $timeout
                    ErrorAction     = 'SilentlyContinue'
                }

                if (-not [string]::IsNullOrEmpty($Proxy))
                {
                    $invokeParams += @{
                        Proxy = $Proxy
                    }
                }

                if (-not [string]::IsNullOrEmpty($ProxyCredential))
                {
                    $invokeParams += @{
                        ProxyCredential = $ProxyCredential
                    }
                }

                try
                {
                    $ProgressPreference = 'SilentlyContinue'
                    $stopwatch = [System.Diagnostics.Stopwatch]::new()
                    $Stopwatch.Start()
                    $result = Invoke-WebRequest @invokeParams
                    $Stopwatch.Stop()
                    $StatusCode = $result.StatusCode
                }
                catch
                {
                    $webResponse = $_.Exception.Response
                    if ($webResponse)
                    {
                        try
                        {
                            $StatusCode = $webResponse.StatusCode.value__
                            $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 -WarningAction SilentlyContinue
                            }
                        }
                        catch {}
                    }
                    else
                    {
                        $statusCode = $_.Exception.Message
                    }

                    # if proxy is not null
                    # check the responseuri matches a proxy set the status code to the exception
                    # so ps5 behaves similar to ps7
                    $ProxyLookup = [Regex]::Escape($Proxy)
                    if (-not [string]::IsNullOrEmpty($Proxy) -and $webResponse.ResponseUri.OriginalString -match $ProxyLookup)
                    {
                        $statusCode = $_.Exception.Message
                    }
                }
                finally
                {
                    $ProgressPreference = 'Continue'
                }

                $source = if ([string]::IsNullOrEmpty($PsSession.ComputerName))
                {
                    [System.Net.Dns]::GetHostName()
                }
                else
                {
                    $PsSession.ComputerName
                }
                if (-not [string]::IsNullOrEmpty($Proxy))
                {
                    $source = $source + "($Proxy)"
                }

                $AdditionalData += New-Object -TypeName PSObject -Property @{
                    Source       = $source
                    Resource     = $invokeParams.uri
                    Protocol     = $p
                    Status       = if ($StatusCode -is [int]) { "Succeeded" } else { "Failed" }
                    TimeStamp    = [datetime]::UtcNow
                    StatusCode   = $StatusCode
                    Detail       = $StatusCode
                    MilliSeconds = $Stopwatch.Elapsed.Milliseconds
                }
            }
        }
        return $AdditionalData
    }
    # 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'
    }
    $result.AdditionalData | ForEach-Object {
        Log-Info ("{0}: {1}" -f $_.Status, $_.Resource) -Type $(if ( $_.Status -eq 'Failed' ){ "Warning" } else { "Info" } )
    }
    $result.HealthCheckSource = ((Get-PSCallStack)[-1].Command)
    return $result
}

function Invoke-TestNetConnection
{
    <#
    .SYNOPSIS
        Get endpoint via Test-NetConnection
    .DESCRIPTION
        Get endpoint via Test-NetConnection, quicker simplier proxy-less check.
    .EXAMPLE
        PS C:\> Invoke-TestNetConnection -Target $Target
        Explanation of what the example does
    .INPUTS
        URI
    .OUTPUTS
        Output (if any)
    .NOTES
    #>

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

        [Parameter()]
        [System.Management.Automation.Runspaces.PSSession[]]
        $PsSession
    )
    try
    {
        $ProgressPreference = 'SilentlyContinue'
        # Create a copy of the Target object
        $result = $Target | Select-Object -Property *
        $result.TimeStamp = [datetime]::UtcNow
        $result.HealthCheckSource = ((Get-PSCallStack)[-1].Command)

        # Create ScriptBlock
        $scriptBlock = {
            $EndPoints = $args[0]
            $Protocols = $args[1]
            $AdditionalData = @()
            foreach ($endPoint in $EndPoints)
            {
                foreach ($p in $Protocols)
                {
                    # Run check
                    # TO DO remove wildcard
                    $uri = [system.uri]("{0}://{1}" -f $p, $endPoint.Replace('*', 'wildcardsdontwork'))
                    $tncParams = @{
                        ComputerName    = $uri.Host
                        Port            = $Uri.Port
                        WarningAction   = 'SilentlyContinue'
                        WarningVariable = 'warnVar'
                        ErrorAction     = 'SilentlyContinue'
                        ErrorVariable   = 'ErrorVar'
                    }
                    $tncResult = Test-NetConnection @tncParams

                    # Write/Clean errors
                    $tncResult | Add-Member -NotePropertyName Warnings -NotePropertyValue $warnVar -Force -ErrorAction SilentlyContinue
                    $tncResult | Add-Member -NotePropertyName Errors -NotePropertyValue $errorVar -Force -ErrorAction SilentlyContinue
                    Clear-Variable warnVar, errorVar -Force -ErrorAction SilentlyContinue

                    # Write result
                    $AdditionalData += New-Object -TypeName PSObject -Property @{
                        Source    = [System.Net.Dns]::GetHostName()
                        Resource  = $uri.OriginalString
                        Protocol  = $p
                        Status    = if ($tncResult.TcpTestSucceeded) { "Succeeded" } else { "Failed" }
                        TimeStamp = [datetime]::UtcNow
                    }
                }
            }
            return $AdditionalData
        }

        # Run Invoke-Command
        $icmParam = @{
            ScriptBlock  = $scriptBlock
            ArgumentList = @($result.EndPoint, $result.Protocol)
        }
        if ($PsSession)
        {
            $icmParam += @{
                Session = $PsSession
            }
        }
        $result.AdditionalData = Invoke-Command @icmParam
        if ($result.AdditionalData.Status -contains 'Failed')
        {
            $result.Status = 'Failed'
        }
        else
        {
            $result.Status = 'Succeeded'
        }
        $result.AdditionalData | ForEach-Object {
            Log-Info ("{0}: {1}" -f $_.Status, $_.Resource) -Type $(if ( $_.Status -eq 'Failed' ){ "Warning" } else { "Info" } )
        }
        return $result
    }
    catch
    {
        throw $_
    }
    finally
    {
        $ProgressPreference = 'Continue'
    }
}

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

        [Parameter()]
        [string]
        $Proxy
    )
    Log-Info "Gathering proxy diagnostics"
    $proxyConfigs = @()
    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
{
    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
{
    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  = ((Get-PSCallStack)[-1].Command)
    }
    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
    #>

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

    $envProxySb = {
        $AdditionalData = @()
        Foreach ($num in 0..2)
        {
            Foreach ($varName in 'https_proxy', 'http_proxy')
            {
                $environmentValue = [System.Environment]::GetEnvironmentVariable("$varName", $num)
                $scope = switch ($num)
                {
                    2 { 'machine' }
                    1 { 'user' }
                    0 { 'process' }
                }
                $AdditionalData += New-Object PsObject -Property @{
                    Source   = "{0}_{1}_{2}" -f $ENV:COMPUTERNAME, $varName, $scope
                    Resource = if ($environmentValue) { $environmentValue } 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  = ((Get-PSCallStack)[-1].Command)
    }
    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 $IeProxySettings.ProxyEnable -eq 0) { '<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  = ((Get-PSCallStack)[-1].Command)
        }
        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
    }
}
# SIG # Begin signature block
# MIInkwYJKoZIhvcNAQcCoIInhDCCJ4ACAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDGaToKi4xYyHCe
# GNCtjyTVznzSsKysTBj/vqiblFjBoqCCDXYwggX0MIID3KADAgECAhMzAAACy7d1
# OfsCcUI2AAAAAALLMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjIwNTEyMjA0NTU5WhcNMjMwNTExMjA0NTU5WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQC3sN0WcdGpGXPZIb5iNfFB0xZ8rnJvYnxD6Uf2BHXglpbTEfoe+mO//oLWkRxA
# wppditsSVOD0oglKbtnh9Wp2DARLcxbGaW4YanOWSB1LyLRpHnnQ5POlh2U5trg4
# 3gQjvlNZlQB3lL+zrPtbNvMA7E0Wkmo+Z6YFnsf7aek+KGzaGboAeFO4uKZjQXY5
# RmMzE70Bwaz7hvA05jDURdRKH0i/1yK96TDuP7JyRFLOvA3UXNWz00R9w7ppMDcN
# lXtrmbPigv3xE9FfpfmJRtiOZQKd73K72Wujmj6/Su3+DBTpOq7NgdntW2lJfX3X
# a6oe4F9Pk9xRhkwHsk7Ju9E/AgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUrg/nt/gj+BBLd1jZWYhok7v5/w4w
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzQ3MDUyODAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAJL5t6pVjIRlQ8j4dAFJ
# ZnMke3rRHeQDOPFxswM47HRvgQa2E1jea2aYiMk1WmdqWnYw1bal4IzRlSVf4czf
# zx2vjOIOiaGllW2ByHkfKApngOzJmAQ8F15xSHPRvNMmvpC3PFLvKMf3y5SyPJxh
# 922TTq0q5epJv1SgZDWlUlHL/Ex1nX8kzBRhHvc6D6F5la+oAO4A3o/ZC05OOgm4
# EJxZP9MqUi5iid2dw4Jg/HvtDpCcLj1GLIhCDaebKegajCJlMhhxnDXrGFLJfX8j
# 7k7LUvrZDsQniJZ3D66K+3SZTLhvwK7dMGVFuUUJUfDifrlCTjKG9mxsPDllfyck
# 4zGnRZv8Jw9RgE1zAghnU14L0vVUNOzi/4bE7wIsiRyIcCcVoXRneBA3n/frLXvd
# jDsbb2lpGu78+s1zbO5N0bhHWq4j5WMutrspBxEhqG2PSBjC5Ypi+jhtfu3+x76N
# mBvsyKuxx9+Hm/ALnlzKxr4KyMR3/z4IRMzA1QyppNk65Ui+jB14g+w4vole33M1
# pVqVckrmSebUkmjnCshCiH12IFgHZF7gRwE4YZrJ7QjxZeoZqHaKsQLRMp653beB
# fHfeva9zJPhBSdVcCW7x9q0c2HVPLJHX9YCUU714I+qtLpDGrdbZxD9mikPqL/To
# /1lDZ0ch8FtePhME7houuoPcMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# 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
# /Xmfwb1tbWrJUnMTDXpQzTGCGXMwghlvAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAALLt3U5+wJxQjYAAAAAAsswDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIPAM4dUDiY9kXEvRNTgf8OvU
# 6JcFJVsvaATaCdIZ8u8dMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEANyHsa7a6RQ9w5CLAcmKUDy67LQ6Er5ccjCe9LaUA+qq7IDTgKGlmpw2r
# +mFTxOG3gFfxk/dfE7Lv+1ZcYNU8h/swkNjwdj6zYbjNEwAZpU9oVHJWfX3ncE/P
# Hh32CUEvuh9ehtE1T9jUfAah80J1swdHlseqf9WNFgxMNA3LUwwMnaS0DJ7lBhfo
# tvn/ttAMF6MU3IsPlxb87tWFf02a3SsLq3K5dZ9e0EpwFcwTgb03tc2IJZW8oRs0
# X0nxyqIegpLSTW/VQi4rfLB/2O4g/U/lbNEHyw59p8WKMTruEsyrkrBFNLo6GDUW
# xbsCzNJtF2hirlAL3TSgF1qPGjdFIKGCFv0wghb5BgorBgEEAYI3AwMBMYIW6TCC
# FuUGCSqGSIb3DQEHAqCCFtYwghbSAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFRBgsq
# hkiG9w0BCRABBKCCAUAEggE8MIIBOAIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCCdj3eYJrt70IV9HBVdi9xScqQh/igWpY6Yxk+rIOl+RQIGZBMyt98q
# GBMyMDIzMDMyMTEwMDU0My43NjZaMASAAgH0oIHQpIHNMIHKMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l
# cmljYSBPcGVyYXRpb25zMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjoxMkJDLUUz
# QUUtNzRFQjElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaCC
# EVQwggcMMIIE9KADAgECAhMzAAAByk/Cs+0DDRhsAAEAAAHKMA0GCSqGSIb3DQEB
# CwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH
# EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNV
# BAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTIyMTEwNDE5MDE0
# MFoXDTI0MDIwMjE5MDE0MFowgcoxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo
# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y
# cG9yYXRpb24xJTAjBgNVBAsTHE1pY3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMx
# JjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOjEyQkMtRTNBRS03NEVCMSUwIwYDVQQD
# ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIICIjANBgkqhkiG9w0BAQEF
# AAOCAg8AMIICCgKCAgEAwwGcq9j50rWEkcLSlGZLweUVfxXRaUjiPsyaNVxPdMRs
# 3CVe58siu/EkaVt7t7PNTPko/s8lNtusAeLEnzki44yxk2c9ekm8E1SQ2YV9b8/L
# OxfKapZ8tVlPyxw6DmFzNFQjifVm8EiZ7lFRoY448vpcbBD18qjYNF/2Z3SQchcs
# dV1N9Y6V2WGl55VmLqFRX5+dptdjreBXzi3WW9TsoCEWcYCBK5wYgS9tT2SSSTza
# e3jmdw40g+LOIyrVPF2DozkStv6JBDPvwahXWpKGpO7rHrKF+o7ECN/ViQFMZyp/
# vxePiUABDNqzEUI8s7klYmeHXvjeQOq/CM3C/Y8bj3fJObnZH7eAXvRDnxT8R6W/
# uD1mGUJvv9M9BMu3nhKpKmSxzzO5LtcMEh2tMXxhMGGNMUP3DOEK3X+2/LD1Z03u
# sJTk5pHNoH/gDIvbp787Cw40tsApiAvtrHYwub0TqIv8Zy62l8n8s/Mv/P764CTq
# rxcXzalBHh+Xy4XPjmadnPkZJycp3Kczbkg9QbvJp0H/0FswHS+efFofpDNJwLh1
# hs/aMi1K/ozEv7/WLIPsDgK16fU/axybqMKk0NOxgelUjAYKl4wU0Y6Q4q9N/9Pw
# AS0csifQhY1ooQfAI0iDCCSEATslD8bTO0tRtqdcIdavOReqzoPdvAv3Dr1XXQ8C
# AwEAAaOCATYwggEyMB0GA1UdDgQWBBT6x/6lS4ESQ8KZhd0RgU7RYXM8fzAfBgNV
# HSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBfBgNVHR8EWDBWMFSgUqBQhk5o
# dHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBU
# aW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmwwbAYIKwYBBQUHAQEEYDBeMFwG
# CCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRz
# L01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNydDAMBgNV
# HRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IC
# AQDY0HkqCS3KuKefFX8/rm/dtD9066dKEleNqriwZqsM4Ym8Ew4QiqOqO7mWoYYY
# 4K5y8eXSOHKNXOfpO6RbaYj8jCOcJAB5tqLl5hiMgaMbAVLrl1hlix9sloO45LON
# 0JphKva3D6AVKA7P78mA9iRHZYUVrRiyfvQjWxmUnxhis8fom92+/RHcEZ1Dh5+p
# 4gzeeL84Yl00Wyq9EcgBKKfgq0lCjWNSq1AUG1sELlgXOSvKZ4/lXXH+MfhcHe91
# WLIaZkS/Hu9wdTT6I14BC97yhDsZWXAl0IJ801I6UtEFpCsTeOyZBJ7CF0rf5lxJ
# 8tE9ojNsyqXJKuwVn0ewCMkZqz/cEwv9FEx8QmsZ0ZNodTtsl+V9dZm+eUrMKZk6
# PKsKArtQ+jHkfVsHgKODloelpOmHqgX7UbO0NVnIlpP55gQTqV76vU7wRXpUfz7K
# hE3BZXNgwG05dRnCXDwrhhYz+Itbzs1K1R8I4YMDJjW90ASCg9Jf+xygRKZGKHjo
# 2Bs2XyaKuN1P6FFCIVXN7KgHl/bZiakGq7k5TQ4OXK5xkhCHhjdgHuxj3hK5AaOy
# +GXxO/jbyqGRqeSxf+TTPuWhDWurIo33RMDGe5DbImjcbcj6dVhQevqHClR1OHSf
# r+8m1hWRJGlC1atcOWKajArwOURqJSVlThwVgIyzGNmjzjCCB3EwggVZoAMCAQIC
# EzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYT
# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD
# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBS
# b290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMB4XDTIxMDkzMDE4MjIyNVoX
# DTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0
# b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh
# dGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggIi
# MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDk4aZM57RyIQt5osvXJHm9DtWC
# 0/3unAcH0qlsTnXIyjVX9gF/bErg4r25PhdgM/9cT8dm95VTcVrifkpa/rg2Z4VG
# Iwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPFdvWGUNzBRMhxXFExN6AKOG6N7dcP
# 2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6GnszrYBbfowQHJ1S/rboYiXcag/P
# XfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBpDco2LXCOMcg1KL3jtIckw+DJj361
# VI/c+gVVmG1oO5pGve2krnopN6zL64NF50ZuyjLVwIYwXE8s4mKyzbnijYjklqwB
# Sru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3EXzTdEonW/aUgfX782Z5F37ZyL9t9
# X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0lBw0gg/wEPK3Rxjtp+iZfD9M269e
# wvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1qGFphAXPKZ6Je1yh2AuIzGHLXpyDw
# wvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ+QuJYfM2BjUYhEfb3BvR/bLUHMVr
# 9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PAPBXbGjfHCBUYP3irRbb1Hode2o+e
# FnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkwEgYJKwYBBAGCNxUBBAUCAwEAATAj
# BgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxGNSnPEP8vBO4wHQYDVR0OBBYEFJ+n
# FV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARVMFMwUQYMKwYBBAGCN0yDfQEBMEEw
# PwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvRG9j
# cy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAKBggrBgEFBQcDCDAZBgkrBgEEAYI3
# FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAf
# BgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvXzpoYxDBWBgNVHR8ETzBNMEugSaBH
# hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNS
# b29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF
# BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0Nl
# ckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG9w0BAQsFAAOCAgEAnVV9/Cqt4Swf
# ZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0xM7U518JxNj/aZGx80HU5bbsPMeTC
# j/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmCVgADsAW+iehp4LoJ7nvfam++Kctu
# 2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449xvNo32X2pFaq95W2KFUn0CS9QKC/
# GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wMnosZiefwC2qBwoEZQhlSdYo2wh3D
# YXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDSPeZKPmY7T7uG+jIa2Zb0j/aRAfbO
# xnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2dY3RILLFORy3BFARxv2T5JL5zbcqO
# Cb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxnGSgkujhLmm77IVRrakURR6nxt67I
# 6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+CrvsQWY9af3LwUFJfn6Tvsv4O+S3Fb+0
# zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokLjzbaukz5m/8K6TT4JDVnK+ANuOaM
# mdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNT
# TY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggLLMIICNAIBATCB+KGB0KSBzTCByjEL
# MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v
# bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWlj
# cm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEmMCQGA1UECxMdVGhhbGVzIFRTUyBF
# U046MTJCQy1FM0FFLTc0RUIxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1w
# IFNlcnZpY2WiIwoBATAHBgUrDgMCGgMVAKOO55cMT4syPP6nClg2IWfajMqkoIGD
# MIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV
# BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQG
# A1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwDQYJKoZIhvcNAQEF
# BQACBQDnw5/DMCIYDzIwMjMwMzIxMTExNTE1WhgPMjAyMzAzMjIxMTE1MTVaMHQw
# OgYKKwYBBAGEWQoEATEsMCowCgIFAOfDn8MCAQAwBwIBAAICIZ4wBwIBAAICEbAw
# CgIFAOfE8UMCAQAwNgYKKwYBBAGEWQoEAjEoMCYwDAYKKwYBBAGEWQoDAqAKMAgC
# AQACAwehIKEKMAgCAQACAwGGoDANBgkqhkiG9w0BAQUFAAOBgQBdEGBksEgaHgVb
# IPUqLHCzRzYV90CemPWWZCXg/4SORJqbe/bjPdkQO2mKceV3M9JoqunPPhWoFUyO
# xATXIThLUwAsXiheS9IkB//Jg9LKw4Xop9iiG7ii3pzXutwv7fvHc1/D/xzZsJkl
# p/SFKkTEL6hUfvpCqFysaA0LEBcVlDGCBA0wggQJAgEBMIGTMHwxCzAJBgNVBAYT
# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD
# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBU
# aW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAByk/Cs+0DDRhsAAEAAAHKMA0GCWCGSAFl
# AwQCAQUAoIIBSjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwLwYJKoZIhvcN
# AQkEMSIEII1JDoBlCFO1HIpvGPHHshvQJ5JiMhbATI310yGlXng5MIH6BgsqhkiG
# 9w0BCRACLzGB6jCB5zCB5DCBvQQgEz0b85vrVU2slZAk4jt1SDEk6IzZAwVCoWwF
# 3KzcGuAwgZgwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv
# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0
# aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAA
# AcpPwrPtAw0YbAABAAAByjAiBCBvFZsyXSigLYqG345yGjPrP2SwyI88X9idY/Up
# 3N+ITDANBgkqhkiG9w0BAQsFAASCAgAS2x+lj+5m2vVDWi9md8X/C6y4lVzEGWi5
# rQaj4FdKCzBgUosW1Wf75naLzE8pAeMNeetG8RqijmEG/dOhNSq9HPOqhKSwJX4g
# o3flL9HayxofepONlwuvFt0NEd4w4xgsAFQ4wPv9NDEDJjcRfWt5IOOJjAcIrAkg
# PNvctG9LDUoPfq7jEBqq2sVvItScYOaJydhYlNr/+C6VDUc3Q+BwK52Qu0LaQCdg
# 1rLRbpWHV8nbK7oqXCa1C3JfVoFyZzeHEf0grZH6n7Q62PnbInuntz2KqPc5wk5h
# GjdcZLnY1g9JGrppcsEqp7ydUsmy/fKLiy+c9IsIWKt/TtMaof1TUokQr5fUiaFU
# 4PG0J10Bz9fBU6lIAwxOg7P4GfCXX3bu+EhrY1BbZDB5YSF9TffzDJEWi1DGHGcK
# M7xAJ7r5cKxIbnfLUR//+BdPne2f6H6SiJEkTu05/+T8HN5IpBKCKHDaERFUyD4f
# tRQ/Y6CjIQVYgi331sD1LZ9NNgqYgJ/d424nE2DNPRLzMIsS8J/bRZ38H5LzpIWN
# xm94gVxS5l+hbdBLqew8c+vypVEmdI8NYXMYT3UQGHZYvbu3c+cYXxC/qr5PSWa2
# DNFA1CbvMKJa8HKgiKe8ifVqP7+MdhvQeLtRnTZcO6ToHLNgC2ISyuSzPmnYBMWS
# 5lry0NZwTw==
# SIG # End signature block