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]$System # targets for system checks such as proxy traversal
}

#Create additional classes to help with writing/report results
class Diagnostics : HealthModel {}
class DnsResult : HealthModel {}
class ProxyDiagnostics : HealthModel {}
function Get-DnsServer
{
    <#
    .SYNOPSIS
        Get DNS Servers
    .DESCRIPTION
        Get DNS Servers of network adapter that are up
    #>

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

    $getDnsServersb = {
        $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
        $dnsServers
    }
    $dnsServer = if ($PsSession)
    {
        Invoke-Command -Session $PsSession -ScriptBlock $getDnsServersb
    }
    else
    {
        Invoke-Command -ScriptBlock $getDnsServersb
    }
    return $dnsServer
}

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

    param (
        [System.Management.Automation.Runspaces.PSSession]
        $PsSession
    )
    $dnsServers = Get-DnsServer -PsSession $PsSession
    Log-Info ("DNS Servers discovered: {0}" -f ($dnsServers -join ','))

    # scriptblock to test dns resolution for each dns server
    $testDnsSb = {
        $AdditionalData = @()
        if (-not $dnsServers)
        {
            $AdditionalData += @{
                Resource  = 'Missing DNS Server'
                Status    = 'Failed'
                TimeStamp = Get-Date
                Source    = $ENV:COMPUTERNAME
            }
        }
        else
        {
            foreach ($dnsServer in $dnsServers)
            {
                $dnsResult = $false
                try
                {
                    $dnsResult = Resolve-DnsName -Name microsoft.com -Server $dnsServer -DnsOnly -ErrorAction SilentlyContinue -QuickTimeout -Type A
                }
                catch
                {
                    $dnsResult = $_.Exception.Message
                }
                if ($dnsResult)
                {
                    if ($dnsResult[0] -is [Microsoft.DnsClient.Commands.DnsRecord])
                    {
                        $status = 'Succeeded'
                    }
                    else
                    {
                        $status = 'Failed'
                    }
                }
                else
                {
                    $status = 'Failed'
                }
                $AdditionalData += @{
                    Resource  = $dnsServer
                    Status    = $status
                    TimeStamp = Get-Date
                    Source    = $ENV:COMPUTERNAME
                    Detail    = $dnsResult
                }
            }
        }
        $AdditionalData
    }

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

    # build result
    $now = Get-Date
    $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
        Remediation        = 'https://aka.ms/hci-connect-chk'
        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 }
                }
            }
        }
        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 Name -Value ("AzStackHci_Connectivity_{0}" -f $Target.TargetResourceName)
                    $target | Add-Member -MemberType NoteProperty -Name HealthCheckSource -Value ((Get-PSCallStack).Command -join '\')
                    $target | Add-Member -MemberType NoteProperty -Name Service -Value $Target.Tags.Service
                    $target | Add-Member -MemberType NoteProperty -Name OperationType -Value $Target.Tags.OperationType
                    $target | Add-Member -MemberType NoteProperty -Name Group -Value $Target.Tags.Group

                    # TO DO: Determine the proper use of TargetResourceID
                    $target.TargetResourceID = (New-Guid).Guid.ToString()
                    $Script:AzStackHciConnectivityTargets += [AzStackHciConnectivityTarget]$target
                }
            }
            catch
            {
                Log-Info -Message -Type Warning -Message ("Unable to read {0}. Error: {1}" -f (Split-Path -Path $targetFile -Leaf), $_.Exception.Message)
            }
        }
    }
    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 TargetResourceName -EQ System_RootCA
            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)"
    }
}

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 = {
        $target = $args[0]
        $Proxy = $args[1]
        $ProxyCredential = $args[2]

        $failedTarget = $false
        $target.TimeStamp = [datetime]::UtcNow
        $AdditionalData = @()
        $timeoutSecondsDefault = 10
        if ([string]::IsNullOrEmpty($Target.Tags.TimeoutSecs))
        {
            $timeout = $timeoutSecondsDefault
        }
        else
        {
            $timeout = $Target.Tags.TimeoutSecs
        }

        foreach ($uri in $Target.EndPoint)
        {
            foreach ($p in $Target.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'
                }

                if ($StatusCode -isnot [int])
                {
                    $failedTarget = $true
                }


                $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
                }
            }
        }
        if ($failedTarget)
        {
            $target.Status = 'Failed'
            Log-Info "$($target.Title) failed:"
        }
        else
        {
            $target.Status = 'Succeeded'
            Log-Info "$($target.Title) succeeded:"
        }
        $AdditionalData | ForEach-Object { Log-Info ("Endpoint detail {0}: {1}, {2}" -f $_.Status, $_.Resource, $_.StatusCode) }
        $Target.AdditionalData = $AdditionalData
        $Target.HealthCheckSource = ((Get-PSCallStack)[-1].Command)
        return $Target
    }

    $sessionArgs = @()
    if ($Target)
    {
        $sessionArgs += $Target
    }
    if ($Proxy)
    {
        $sessionArgs += $Proxy
    }
    if ($ProxyCredential)
    {
        $sessionArgs += $ProxyCredential
    }
    if ($PsSession)
    {
        Invoke-Command -Session $PsSession -ScriptBlock $ScriptBlock -ArgumentList $sessionArgs
    }
    else
    {
        Invoke-Command -ScriptBlock $ScriptBlock -ArgumentList $sessionArgs
    }
}

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'
        $target.TimeStamp = [datetime]::UtcNow
        $Target.HealthCheckSource = ((Get-PSCallStack)[-1].Command)

        # Create ScriptBlock
        $scriptBlock = {
            $Target = $args[0]
            $AdditionalData = @()
            $failedTarget = $false
            foreach ($endPoint in $Target.EndPoint)
            {
                foreach ($p in $Target.Protocol)
                {
                    # 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
                    }
                    if ($tncResult.TcpTestSucceeded -eq $false)
                    {
                        $target.Status = 'Failed'
                        $failedTarget = $true
                    }
                }
            }
            $AdditionalData | ForEach-Object { Log-Info ("{0}: {1}" -f $_.Status, $_.Resource) }
            $target.AdditionalData = $AdditionalData
            if ($failedTarget)
            {
                $target.Status = 'Failed'
            }
            else
            {
                $target.Status = 'Succeeded'
            }
            return $target
        }

        # Run Invoke-Command
        $icmParam = @{
            ScriptBlock  = $scriptBlock
            ArgumentList = $Target
        }
        if ($PsSession)
        {
            $icmParam += @{
                Session = $PsSession
            }
        }
        Invoke-Command @icmParam
    }
    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 TargetResourceName -EQ System_User_Proxy
    $UserProxyUri = [system.uri]$Proxy
    $userProxy.EndPoint = "{0}:{1}" -f $UserProxyUri.Host, $UserProxyUri.Port
    $userProxy.Protocol = $UserProxyUri.Scheme
    $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          = Get-Date
        Status             = 'Succeeded'
        AdditionalData     = @{
            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 = {
        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' }
                }
                @{
                    Source   = "{0}_{1}_{2}" -f $ENV:COMPUTERNAME, $varName, $scope
                    Resource = if ($environmentValue) { $environmentValue } else { '<Not configured>' }
                    Status   = 'Succeeded'
                }
            }
        }
    }
    $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          = Get-Date
        Status             = 'Succeeded'
        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
            @{
                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'
            }
        }
    }
    $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          = Get-Date
            Status             = 'Succeeded'
            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
# MIInygYJKoZIhvcNAQcCoIInuzCCJ7cCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAtppWkA7rWwjbg
# gqOYHxmFLioowNAprBmwiMtowk8f3qCCDYEwggX/MIID56ADAgECAhMzAAACzI61
# lqa90clOAAAAAALMMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjIwNTEyMjA0NjAxWhcNMjMwNTExMjA0NjAxWjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQCiTbHs68bADvNud97NzcdP0zh0mRr4VpDv68KobjQFybVAuVgiINf9aG2zQtWK
# No6+2X2Ix65KGcBXuZyEi0oBUAAGnIe5O5q/Y0Ij0WwDyMWaVad2Te4r1Eic3HWH
# UfiiNjF0ETHKg3qa7DCyUqwsR9q5SaXuHlYCwM+m59Nl3jKnYnKLLfzhl13wImV9
# DF8N76ANkRyK6BYoc9I6hHF2MCTQYWbQ4fXgzKhgzj4zeabWgfu+ZJCiFLkogvc0
# RVb0x3DtyxMbl/3e45Eu+sn/x6EVwbJZVvtQYcmdGF1yAYht+JnNmWwAxL8MgHMz
# xEcoY1Q1JtstiY3+u3ulGMvhAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUiLhHjTKWzIqVIp+sM2rOHH11rfQw
# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1
# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDcwNTI5MB8GA1UdIwQYMBaAFEhu
# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu
# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w
# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx
# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAeA8D
# sOAHS53MTIHYu8bbXrO6yQtRD6JfyMWeXaLu3Nc8PDnFc1efYq/F3MGx/aiwNbcs
# J2MU7BKNWTP5JQVBA2GNIeR3mScXqnOsv1XqXPvZeISDVWLaBQzceItdIwgo6B13
# vxlkkSYMvB0Dr3Yw7/W9U4Wk5K/RDOnIGvmKqKi3AwyxlV1mpefy729FKaWT7edB
# d3I4+hldMY8sdfDPjWRtJzjMjXZs41OUOwtHccPazjjC7KndzvZHx/0VWL8n0NT/
# 404vftnXKifMZkS4p2sB3oK+6kCcsyWsgS/3eYGw1Fe4MOnin1RhgrW1rHPODJTG
# AUOmW4wc3Q6KKr2zve7sMDZe9tfylonPwhk971rX8qGw6LkrGFv31IJeJSe/aUbG
# dUDPkbrABbVvPElgoj5eP3REqx5jdfkQw7tOdWkhn0jDUh2uQen9Atj3RkJyHuR0
# GUsJVMWFJdkIO/gFwzoOGlHNsmxvpANV86/1qgb1oZXdrURpzJp53MsDaBY/pxOc
# J0Cvg6uWs3kQWgKk5aBzvsX95BzdItHTpVMtVPW4q41XEvbFmUP1n6oL5rdNdrTM
# j/HXMRk1KCksax1Vxo3qv+13cCsZAaQNaIAvt5LvkshZkDZIP//0Hnq7NnWeYR3z
# 4oFiw9N2n3bb9baQWuWPswG0Dq9YT9kb+Cs4qIIwggd6MIIFYqADAgECAgphDpDS
# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0
# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla
# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT
# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG
# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S
# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz
# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7
# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u
# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33
# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl
# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP
# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB
# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF
# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM
# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ
# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud
# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO
# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0
# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p
# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB
# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw
# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA
# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY
# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj
# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd
# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ
# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf
# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ
# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j
# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B
# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96
# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7
# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I
# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIZnzCCGZsCAQEwgZUwfjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAsyOtZamvdHJTgAAAAACzDAN
# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQg5caiNYKa
# wDbeeDARRJAaJylB3z1an2aSkKaGRJ0LmLcwQgYKKwYBBAGCNwIBDDE0MDKgFIAS
# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN
# BgkqhkiG9w0BAQEFAASCAQB8R9MApi7MDnLRrgVvixHMC1Ywpg+/5++8HP5sBE5Q
# fPrE3+OUb+7u21fBP7DqRhGluMbhmCv5yY1CIt5PmXNimDfqSgsV6zgPGX98l3eU
# 7AVlF6PKS52IxoYaTtFieqfe3Sd6rDykE29IF8l9WOp7rtn+5SvPuX+Bsk4oOHwm
# uRzUlcS77/8nLP1e7JXk5h7JnEFCmzH2cBby7OEEpdfMo2W1Ke/AtblcGm/DTAW+
# mXdHv+9+/HeqPKjTqZvf5uOPTBXgkNAdZwOc1O2vx1DwfrGg0rkTzIiwa6MTzkWz
# kJ00gNqzyLStBsKjES89j+h1yFzHBQN7QVjoqGInj7NaoYIXKTCCFyUGCisGAQQB
# gjcDAwExghcVMIIXEQYJKoZIhvcNAQcCoIIXAjCCFv4CAQMxDzANBglghkgBZQME
# AgEFADCCAVkGCyqGSIb3DQEJEAEEoIIBSASCAUQwggFAAgEBBgorBgEEAYRZCgMB
# MDEwDQYJYIZIAWUDBAIBBQAEINMeTODlJE2rKvfap7C68L/zZulZxQlWXLuvZ3I2
# NExAAgZjN1ie7IoYEzIwMjIxMDEzMTAyODIwLjQ0NVowBIACAfSggdikgdUwgdIx
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1p
# Y3Jvc29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEmMCQGA1UECxMdVGhh
# bGVzIFRTUyBFU046ODZERi00QkJDLTkzMzUxJTAjBgNVBAMTHE1pY3Jvc29mdCBU
# aW1lLVN0YW1wIFNlcnZpY2WgghF4MIIHJzCCBQ+gAwIBAgITMwAAAbchJxoHoiqG
# RgABAAABtzANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0Eg
# MjAxMDAeFw0yMjA5MjAyMDIyMTRaFw0yMzEyMTQyMDIyMTRaMIHSMQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQg
# SXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1Mg
# RVNOOjg2REYtNEJCQy05MzM1MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFt
# cCBTZXJ2aWNlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAx/3PV1A0
# E2RMGrcclyXKmNBwkiUMCCm+zDi8cl7TqfnfZEoaRyfRXunrk54DXA6g77w2yime
# M+lVnz7iz9GF0wIw09AlehZ2RY6n/em4gSzV/M8GBMmt2bN3JRjOsCWggJjHvbaC
# Z2ls1/c/TLlnaBE7jyxYhjJh40lhyH5l8nNeqq+6+rsbw2bwgxCLxlrK/H7l1IYE
# zlVgcHEISNcX0Q3pDDsDrRJpeMqOiNKlSalozgWZV5z3zk811KHyE54/a0vvzVIF
# qf3YNPX2180e/0fQUCYTYcZHCLMeQn/+YBl1LSzpuL9TA8ySwCJrlumcgME6hph1
# aRXSVUbms3T6W1HP3OOVof26F0ZMn5aI0o6vuZGeXNhADnd+1JIkGqwSlPMK/tzr
# vd92+W3HfLjw96vq9zpRHl7iRJmwfXjUiHGGZFHZKYRpu5go5N07tD++W8B9DG4S
# cn3c0vgthXUhbDw77E07qCWOsGkI5845NCBYkX4zr8g/K5ity/v0u7uVzrtL5T0w
# S7Z2iDQy4kihgIyK5VpdLn4WY4mLJ+IJmbRUmwnnvTL2T+0RB7ppBH0zENbWnYXc
# dZ5uiVeP2y7vOusSU2UcAS+kWFyCgyeLKU1OJNnMvBSPtMfzeCe6DDUUIRVWHlT2
# XA0w8vUga7P/dT5FP3BkIElgisUk93qTxS0CAwEAAaOCAUkwggFFMB0GA1UdDgQW
# BBSz8jk+DDQkBWEN1gHbI8gjyXi07DAfBgNVHSMEGDAWgBSfpxVdAF5iXYP05dJl
# pxtTNRnpcjBfBgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAx
# MCgxKS5jcmwwbAYIKwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUtU3Rh
# bXAlMjBQQ0ElMjAyMDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQM
# MAoGCCsGAQUFBwMIMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAgEA
# DZ1kzJuFXFJdNsnMWC82IwJXn8DKflS7MlTj3lgO1x5+iV3R6FH1AEYGEeYAy3XR
# ZgIfPZZBTIzeZAPgiwd8QvUkTCAb20arvW5yf12ScsEcPfQm9tBGEgpy8VKe9EIK
# lWmKUzwX9hpZcL1WJtJ5/89TYZYD7XOzrhUXEmod67LwqqtDJ8XbRIKd3zNFmXhh
# gFyOH0bQaOEkdQ8BgTHSvyNJaPKLajZYAcPoKmkG2TCdlJ/sDrwMvg7DAGmYUHf6
# +5uOA3BogJL5+QZW9Gc/ZmCaV+doBqzwzHuKAmpyKqrRDoCf7SsePWTGKw10iJPM
# WW7kbDZ6FekuuXn2S+yY41UAkeGd2nHef/SvvpMf7dY1L1RP2NvFCQT63a2GJ/ow
# rmSsUyeQEgIJH8NVxXWrD2yDqwwSjTBZJeVBhe9xBYDrl5XNKSizKWEiUhlksvcY
# dkPUtC0COnYDkXsLcjDg23sLtoMJ+kXGibdm6VGcUmiWU8dve6eP2ED3G9GIqdYw
# AylLgxkCiJXg7b3NYpl1TPKCnuAPhaMorXXkoInSi8Rx/3YCfAwBcyc2zWjQsKzO
# D64OaJBmzl5HuPN0rNV8XXPtt8Ng08Am+bmlJB1vLfAg6w3+y1iMz4SRo+1TOvw7
# IzCsb6OkUxuwHhx83q28h4a2u2SUwkW2cXrwJxlnkgAwggdxMIIFWaADAgECAhMz
# AAAAFcXna54Cm0mZAAAAAAAVMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9v
# dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0yMTA5MzAxODIyMjVaFw0z
# MDA5MzAxODMyMjVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u
# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp
# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIICIjAN
# BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5OGmTOe0ciELeaLL1yR5vQ7VgtP9
# 7pwHB9KpbE51yMo1V/YBf2xK4OK9uT4XYDP/XE/HZveVU3Fa4n5KWv64NmeFRiMM
# tY0Tz3cywBAY6GB9alKDRLemjkZrBxTzxXb1hlDcwUTIcVxRMTegCjhuje3XD9gm
# U3w5YQJ6xKr9cmmvHaus9ja+NSZk2pg7uhp7M62AW36MEBydUv626GIl3GoPz130
# /o5Tz9bshVZN7928jaTjkY+yOSxRnOlwaQ3KNi1wjjHINSi947SHJMPgyY9+tVSP
# 3PoFVZhtaDuaRr3tpK56KTesy+uDRedGbsoy1cCGMFxPLOJiss254o2I5JasAUq7
# vnGpF1tnYN74kpEeHT39IM9zfUGaRnXNxF803RKJ1v2lIH1+/NmeRd+2ci/bfV+A
# utuqfjbsNkz2K26oElHovwUDo9Fzpk03dJQcNIIP8BDyt0cY7afomXw/TNuvXsLz
# 1dhzPUNOwTM5TI4CvEJoLhDqhFFG4tG9ahhaYQFzymeiXtcodgLiMxhy16cg8ML6
# EgrXY28MyTZki1ugpoMhXV8wdJGUlNi5UPkLiWHzNgY1GIRH29wb0f2y1BzFa/Zc
# UlFdEtsluq9QBXpsxREdcu+N+VLEhReTwDwV2xo3xwgVGD94q0W29R6HXtqPnhZy
# acaue7e3PmriLq0CAwEAAaOCAd0wggHZMBIGCSsGAQQBgjcVAQQFAgMBAAEwIwYJ
# KwYBBAGCNxUCBBYEFCqnUv5kxJq+gpE8RjUpzxD/LwTuMB0GA1UdDgQWBBSfpxVd
# AF5iXYP05dJlpxtTNRnpcjBcBgNVHSAEVTBTMFEGDCsGAQQBgjdMg30BATBBMD8G
# CCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL0RvY3Mv
# UmVwb3NpdG9yeS5odG0wEwYDVR0lBAwwCgYIKwYBBQUHAwgwGQYJKwYBBAGCNxQC
# BAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYD
# VR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZF
# aHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9v
# Q2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcw
# AoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJB
# dXRfMjAxMC0wNi0yMy5jcnQwDQYJKoZIhvcNAQELBQADggIBAJ1VffwqreEsH2cB
# MSRb4Z5yS/ypb+pcFLY+TkdkeLEGk5c9MTO1OdfCcTY/2mRsfNB1OW27DzHkwo/7
# bNGhlBgi7ulmZzpTTd2YurYeeNg2LpypglYAA7AFvonoaeC6Ce5732pvvinLbtg/
# SHUB2RjebYIM9W0jVOR4U3UkV7ndn/OOPcbzaN9l9qRWqveVtihVJ9AkvUCgvxm2
# EhIRXT0n4ECWOKz3+SmJw7wXsFSFQrP8DJ6LGYnn8AtqgcKBGUIZUnWKNsIdw2Fz
# Lixre24/LAl4FOmRsqlb30mjdAy87JGA0j3mSj5mO0+7hvoyGtmW9I/2kQH2zsZ0
# /fZMcm8Qq3UwxTSwethQ/gpY3UA8x1RtnWN0SCyxTkctwRQEcb9k+SS+c23Kjgm9
# swFXSVRk2XPXfx5bRAGOWhmRaw2fpCjcZxkoJLo4S5pu+yFUa2pFEUep8beuyOiJ
# Xk+d0tBMdrVXVAmxaQFEfnyhYWxz/gq77EFmPWn9y8FBSX5+k77L+DvktxW/tM4+
# pTFRhLy/AsGConsXHRWJjXD+57XQKBqJC4822rpM+Zv/Cuk0+CQ1ZyvgDbjmjJnW
# 4SLq8CdCPSWU5nR0W2rRnj7tfqAxM328y+l7vzhwRNGQ8cirOoo6CGJ/2XBjU02N
# 7oJtpQUQwXEGahC0HVUzWLOhcGbyoYIC1DCCAj0CAQEwggEAoYHYpIHVMIHSMQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNy
# b3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxl
# cyBUU1MgRVNOOjg2REYtNEJCQy05MzM1MSUwIwYDVQQDExxNaWNyb3NvZnQgVGlt
# ZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQDIZ0EYw5s4OWwYFmZJRfah
# aB+pxqCBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u
# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp
# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqG
# SIb3DQEBBQUAAgUA5vJRMzAiGA8yMDIyMTAxMzE2NTYxOVoYDzIwMjIxMDE0MTY1
# NjE5WjB0MDoGCisGAQQBhFkKBAExLDAqMAoCBQDm8lEzAgEAMAcCAQACAgN8MAcC
# AQACAhGMMAoCBQDm86KzAgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkK
# AwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJKoZIhvcNAQEFBQADgYEAgf43
# v1ceeebbb+E+fDgm6yT9WVaJ4CGJ5mBfXT8zfTrG/2w2M5VBJA1o84d+DSK3/Yix
# b5lNVQ6nfrninqWNfU7zIqVQFxbPIAObDQTviqWFl4wdxiXVUDhy/mNBJ6q2+MbF
# hLzM9A+Jt1UdxgVE14bQ654Jg3zLt9agpHB5/10xggQNMIIECQIBATCBkzB8MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNy
# b3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAbchJxoHoiqGRgABAAABtzAN
# BglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8G
# CSqGSIb3DQEJBDEiBCDIQ2jDINv4Jrhmxkl/6+3Fwa4bSBQcuPnonq1BywWyrTCB
# +gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIGwneNOyHtovyE16JgYvn/wgGfzN
# R+vv9DuuQzyJzXBBMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh
# c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD
# b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw
# MTACEzMAAAG3IScaB6IqhkYAAQAAAbcwIgQgORFO3nmYsSi+Kao5iUprwu9X2mZy
# C5SHFV5I41iHhgUwDQYJKoZIhvcNAQELBQAEggIAMi9TWrLEwEwkd4bVd+5DUrAM
# 06h4vtCCs2mpk2/GVpaMkB92rdEuCREYG01CEEmBHzVeYojPC3yYp1RrwlnyL2XS
# BG1WroisftK3DGRmR9Nvg0DhrGvXnTGykUZM6A3HkkOspYwb3mSxCbh3wiJ4PAUx
# VCPz8lxvy9Z+Apij+boDkoq3WOAEbCaADAJir8KRX29aOQRdoiYOrWoTO/fUn7PP
# c9+NGZ2SC3SnMsfm9ffLdekVGSHCPogXDLyzsRnS92orA8o8e84o1Vq+DHyYXObn
# vL9EcwWM5g/X1GJS1bElZcF1KFylWPB25SnrXzCUHLOCZMsgZemIEg3penWdf2o+
# mhm9GZ75PEpS5hl1x+6mjU2RwbNAWE5Wwzg6VdPei47Gj24otCJmNkRQq8ifrRDe
# AX8U0Qb7Uazar2I9tV6gz34UBd6Pq67OqYWInNkfSIevDD3Zh52DNQg9EKQjvHZo
# 5oAIjtOoU+r2wT8Rv7su7ENsj1GSMhKYvgq1eG1cbCYeb/5IbPx8XETKE6REZqmo
# Nms4dSo748F9r9/enyYUro7V0C98FNYE54v4k5hSM5dXGhPiOeDOyWwiSirQdj5y
# Xc1oZJism6OuqsNOkVwcomEsZdyNaSPwTOs+UPChzpcjIsT3ldLlvKVHHAVPzhv9
# KeVCQgjHL7H0WheOaVA=
# SIG # End signature block