AzStackHciConnectivity/AzStackHci.Connectivity.Helpers.psm1
class HealthModel { # Attributes for Azure Monitor schema [string]$Name #Name of the individual test/rule/alert that was executed. Unique, not exposed to the customer. [string]$Title #User-facing name; one or more sentences indicating the direct issue. [string]$Severity #Severity of the result (Critical, Warning, Informational, Hidden) – this answers how important the result is. Critical is the only update-blocking severity. [string]$Description #Detailed overview of the issue and what impact the issue has on the stamp. [psobject]$Tags #Key-value pairs that allow grouping/filtering individual tests. For example, "Group": "ReadinessChecks", "UpdateType": "ClusterAware" [string]$Status #The status of the check running (i.e. Failed, Succeeded, In Progress) – this answers whether the check ran, and passed or failed. [string]$Remediation #Set of steps that can be taken to resolve the issue found. [string]$TargetResourceID #The unique identifier for the affected resource (such as a node or drive). [string]$TargetResourceName #The name of the affected resource. [string]$TargetResourceType #The type of resource being referred to (well-known set of nouns in infrastructure, aligning with Monitoring). [datetime]$Timestamp #The Time in which the HealthCheck was called. [psobject[]]$AdditionalData #Property bag of key value pairs for additional information. [string]$HealthCheckSource #The name of the services called for the HealthCheck (I.E. Test-AzureStack, Test-Cluster). } class AzStackHciConnectivityTarget : HealthModel { # Attribute for performing check [string[]]$EndPoint [string[]]$Protocol # Additional Attributes for end user interaction [string[]]$Service # short cut property to Service from tags [string[]]$OperationType # short cut property to Operation Type from tags [string[]]$Group # short cut property to group from tags [bool]$Mandatory # short cut property to mandatory from tags [bool]$System # targets for system checks such as proxy traversal } class AzStackHciConnectivityManifest { [string]$Title [System.Version]$Version [AzStackHciConnectivityTarget[]]$Targets } Import-LocalizedData -BindingVariable lcTxt -FileName AzStackHci.Connectivity.Strings.psd1 #Create additional classes to help with writing/report results class Diagnostics : AzStackHciConnectivityTarget {} class DnsResult : AzStackHciConnectivityTarget {} class ProxyDiagnostics : AzStackHciConnectivityTarget {} function Test-Dns { <# .SYNOPSIS Test DNS Resolution #> [CmdletBinding()] param ( [System.Management.Automation.Runspaces.PSSession] $PsSession ) # scriptblock to test dns resolution for each dns server $testDnsSb = { $AdditionalData = @() # Get local DNS servers $dnsServers = @() $netAdapter = Get-NetAdapter | Where-Object Status -EQ Up $dnsServer = Get-DnsClientServerAddress -InterfaceIndex $netAdapter.ifIndex -AddressFamily IPv4 $dnsServers += $dnsServer | ForEach-Object { $PSITEM.Address } | Sort-Object | Get-Unique if (-not $dnsServers) { $AdditionalData += New-Object PsObject -Property @{ Resource = $lcTxt.NoDnsConfigured Status = 'Failed' TimeStamp = [datetime]::UtcNow Source = $ENV:COMPUTERNAME Detail = $lcTxt.NoDnsConfigured } } else { foreach ($dnsServer in $dnsServers) { $dnsResult = $false $dnsResult = Resolve-DnsName -Name microsoft.com -Server $dnsServer -DnsOnly -ErrorAction SilentlyContinue -QuickTimeout -Type A if ([int]($dnsResult.count) -eq 0) { $detail = $lcTxt.QueryDnsFail -f $dnsServer, 'microsoft.com', $ENV:COMPUTERNAME, [int]($dnsResult.count) } else { $detail = $lcTxt.QueryDnsPass -f $dnsServer, 'microsoft.com', $ENV:COMPUTERNAME, [int]($dnsResult.count), ($dnsResult.IpAddress -join ',') } if ($dnsResult) { if ($dnsResult[0] -is [Microsoft.DnsClient.Commands.DnsRecord]) { $status = 'Succeeded' } else { $status = 'Failed' } } else { $status = 'Failed' } $AdditionalData += New-Object PsObject -Property @{ Resource = $dnsServer Status = $status TimeStamp = [datetime]::UtcNow Source = $ENV:COMPUTERNAME Detail = $detail } } } $AdditionalData } # run scriptblock $testDnsServer = if ($PsSession) { Invoke-Command -Session $PsSession -ScriptBlock $testDnsSb } else { Invoke-Command -ScriptBlock $testDnsSb } # build result $now = [datetime]::UtcNow # Write result to verbose log $testDnsServer | Foreach-Object { Log-Info $_.Detail -Type $(if ( $_.Status -eq 'Failed' ){ "Warning" } else { "Info" } ) } $TargetComputerName = if ($PsSession.PSComputerName) { $PsSession.PSComputerName } else { $ENV:COMPUTERNAME } $aggregateStatus = if ($testDnsServer.Status -contains 'Succeeded') { 'Succeeded' } else { 'Failed' } $testDnsResult = New-Object -Type DnsResult -Property @{ Name = 'AzStackHci_Connectivity_Test_Dns' Title = 'Test DNS' Severity = 'Critical' Description = 'Test DNS Resolution' Tags = $null EndPoint = @("microsoft.com") Service = 'System' Remediation = 'https://learn.microsoft.com/en-us/azure-stack/hci/deploy/deployment-tool-checklist' TargetResourceID = 'c644bad4-044d-4066-861d-ceb93b64f046' TargetResourceName = "Test_DNS_$TargetComputerName" TargetResourceType = 'DNS' Timestamp = $now Status = $aggregateStatus AdditionalData = $testDnsServer HealthCheckSource = $ENV:EnvChkrId } return $testDnsResult } function Get-AzStackHciConnectivityServiceName { <# .SYNOPSIS Retrieve Services from built target packs .DESCRIPTION Retrieve Services from built target packs .EXAMPLE PS C:\> Get-AzStackHciServices Explanation of what the example does .INPUTS Service .OUTPUTS PSObject .NOTES #> [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [string[]] $Service, [Parameter(Mandatory = $false)] [switch] $IncludeSystem ) try { Get-AzStackHciConnectivityTarget -IncludeSystem:$IncludeSystem | Select-Object -ExpandProperty Service | Sort-Object | Get-Unique } catch { throw "Failed to get services names. Error: $($_.Exception.Message)" } } function Get-AzStackHciConnectivityOperationName { <# .SYNOPSIS Retrieve Operation Types from built target packs .DESCRIPTION Retrieve Operation Types from built target packs e.g. Deployment, Update, Secret Rotation. .EXAMPLE PS C:\> Get-AzStackHciConnectivityOperationName Explanation of what the example does .INPUTS Service .OUTPUTS PSObject .NOTES #> [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [string] $OperationType ) try { Get-AzStackHciConnectivityTarget | Select-Object -ExpandProperty OperationType | Sort-Object | Get-Unique } catch { throw "Failed to get services names. Error: $($_.Exception.Message)" } } function Get-AzStackHciConnectivityTarget { <# .SYNOPSIS Retrieve Endpoints from built target packs .DESCRIPTION Retrieve Endpoints from built target packs .EXAMPLE PS> Get-AzStackHciConnectivityTarget Get all connectivity targets .EXAMPLE Get-AzStackHciConnectivityTarget -Service ARC | ft Name, Title, Service, OperationType -AutoSize Get all ARC connectivity targets .EXAMPLE PS> Get-AzStackHciConnectivityTarget -Service ARC -OperationType Workload | ft Name, Title, Service, OperationType -AutoSize Get all ARC targets for workloads .EXAMPLE PS> Get-AzStackHciConnectivityTarget -OperationType Workload | ft Name, Title, Service, OperationType -AutoSize Get all targets for workloads .EXAMPLE PS> Get-AzStackHciConnectivityTarget -OperationType ARC -OperationType Update -Additive | ft Name, Title, Service, OperationType -AutoSize Get all ARC targets and all targets for Update .INPUTS Service - String array OperationType - String array Additive - Switch .OUTPUTS PSObject .NOTES #> [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [string[]] $Service, [Parameter(Mandatory = $false)] [string[]] $OperationType, [Parameter(Mandatory = $false)] [switch] $Additive, [Parameter(Mandatory = $false)] [switch] $IncludeSystem, [Parameter(Mandatory = $false)] [switch] $LocalOnly ) try { Import-AzStackHciConnectivityTarget -LocalOnly:$LocalOnly $executionTargets = @() # Additive allows the user to "-OR" their parameter values if ($Additive) { Write-Verbose -Message "Getting targets additively" if (-not [string]::IsNullOrEmpty($Service)) { Write-Verbose -Message ("Getting targets by Service: {0}" -f ($Service -join ',')) foreach ($svc in $Service) { $executionTargets += $Script:AzStackHciConnectivityTargets | Where-Object { $svc -in $_.Service } } } if (-not [string]::IsNullOrEmpty($OperationType)) { Write-Verbose -Message ("Getting targets by Operation Type: {0}" -f ($OperationType -join ',')) foreach ($Op in $OperationType) { $executionTargets += $Script:AzStackHciConnectivityTargets | Where-Object { $Op -in $_.OperationType } } } if ([string]::IsNullOrEmpty($OperationType) -and [string]::IsNullOrEmpty($Service)) { $executionTargets += $Script:AzStackHciConnectivityTargets } } else { if ([string]::IsNullOrEmpty($OperationType) -and [string]::IsNullOrEmpty($Service)) { $executionTargets += $Script:AzStackHciConnectivityTargets } elseif (-not [string]::IsNullOrEmpty($Service) -and [string]::IsNullOrEmpty($OperationType)) { Write-Verbose -Message ("Getting targets by Service: {0}" -f ($Service -join ',')) foreach ($svc in $Service) { $executionTargets += $Script:AzStackHciConnectivityTargets | Where-Object { $svc -in $_.Service } } } elseif (-not [string]::IsNullOrEmpty($OperationType) -and [string]::IsNullOrEmpty($Service)) { Write-Verbose -Message ("Getting targets by Operation Type: {0}" -f ($OperationType -join ',')) foreach ($Op in $OperationType) { $executionTargets += $Script:AzStackHciConnectivityTargets | Where-Object { $Op -in $_.OperationType } } } else { Write-Verbose -Message ("Getting targets by Operation Type: {0} and Service: {1}" -f ($OperationType -join ','), ($Service -join ',')) $executionTargetsByOp = @() foreach ($Op in $OperationType) { $executionTargetsByOp += $Script:AzStackHciConnectivityTargets | Where-Object { $Op -in $_.OperationType } } foreach ($svc in $Service) { $executionTargets += $executionTargetsByOp | Where-Object { $svc -in $_.Service } } } } # Always add Mandatory targets $executionTargets += $Script:AzStackHciConnectivityTargets | Where-Object Mandatory | ForEach-Object { if ($PSITEM -notin $executionTargets) { $PSITEM } } if ($IncludeSystem) { return $executionTargets } else { return ($executionTargets | Where-Object Service -NotContains 'System') } } catch { throw "Get failed: $($_.exception)" } } function Import-AzStackHciConnectivityTarget { <# .SYNOPSIS Retrieve Endpoints from built target packs .DESCRIPTION Retrieve Endpoints from built target packs .EXAMPLE PS C:\> Import-AzStackHciConnectivityTarget Explanation of what the example does .INPUTS URI .OUTPUTS PSObject .NOTES #> [CmdletBinding()] param ( [switch] $LocalOnly ) try { $Script:AzStackHciConnectivityTargets = @() if (-not $LocalOnly) { $Script:AzStackHciConnectivityTargets += Get-CloudEndpointFromManifest } if ($Script:AzStackHciConnectivityTargets) { return } else { $targetFiles = Get-ChildItem -Path "$PSScriptRoot\Targets\*.json" | Select-Object -ExpandProperty FullName Write-Verbose ("Importing {0}" -f ($targetFiles -join ',')) ForEach ($targetFile in $targetFiles) { try { # TO DO - Add validations: # - protocol should not contain :// $targetPackContent = Get-Content -Path $targetFile | ConvertFrom-Json -WarningAction SilentlyContinue foreach ($target in $targetPackContent) { #Set Name of the individual test/rule/alert that was executed. Unique, not exposed to the customer. $target | Add-Member -MemberType NoteProperty -Name HealthCheckSource -Value $ENV:EnvChkrId $target.TargetResourceID = $target.EndPoint -join '_' $target.TargetResourceName = $target.EndPoint -join '_' $target.TargetResourceType = 'External Endpoint' $Script:AzStackHciConnectivityTargets += [AzStackHciConnectivityTarget]$target } } catch { throw ("Unable to read {0}. Error: {1}" -f (Split-Path -Path $targetFile -Leaf), $_.Exception.Message) } } } } catch { throw "Import failed: $($_.exception)" } } function Get-SystemProxy { <# .SYNOPSIS Get Proxy set on system .DESCRIPTION Get Proxy set on system .EXAMPLE PS C:\> Get-SystemProxy Explanation of what the example does .OUTPUTS Output (if any) .NOTES #> [CmdletBinding()] param () throw "Not implemented" } function Get-SigningRootChain { <# .SYNOPSIS Get signing root for https endpoint .DESCRIPTION Get signing root for https endpoint .EXAMPLE PS C:\> Get-SigningRoot -uri MicrosoftOnline.com Explanation of what the example does .INPUTS URI .OUTPUTS Output (if any) .NOTES #> [CmdletBinding()] param ( [Parameter()] [System.Uri] $Uri, [Parameter()] [System.Management.Automation.Runspaces.PSSession] $PsSession, [Parameter()] [string] $Proxy, [Parameter()] [pscredential] $proxyCredential ) try { $sb = { $uri = $args[0] $proxy = $args[1] $proxyCredential = $args[2] $GetSslCertChainFunction = $args[3] # Check if helper function is locally available Import-Module -Name AzStackHci.EnvironmentChecker -Force -ErrorAction SilentlyContinue -Scope Local if (-not (Get-Command -Name Get-SslCertificateChain -ErrorAction SilentlyContinue)) { throw "Cannot find Get-SslCertificateChain in AzStackHci.EnvironmentChecker.Utilities module" } else { Write-Verbose "Found Get-SslCertificateChain in AzStackHci.EnvironmentChecker.Utilities module" $chain = Get-SslCertificateChain -Url $Uri -Proxy $Proxy -ProxyCredential $ProxyCredential } return $chain.ChainElements.Certificate } $ChainElements = if ($PsSession) { Invoke-Command -Session $PsSession -ScriptBlock $sb -ArgumentList $Uri, $Proxy, $ProxyCredential,${function:Get-SslCertificateChain} } else { Invoke-Command -ScriptBlock $sb -ArgumentList $Uri, $Proxy, $ProxyCredential,${function:Get-SslCertificateChain} } return $ChainElements } catch { throw $_ } } function Test-RootCA { <# .SYNOPSIS Short description .DESCRIPTION Long description .EXAMPLE PS C:\> <example usage> Explanation of what the example does .INPUTS Inputs (if any) .OUTPUTS Output (if any) .NOTES General notes #> [CmdletBinding()] param( [Parameter()] [System.Management.Automation.Runspaces.PSSession] $PsSession, [Parameter()] [string] $Proxy, [Parameter()] [pscredential] $ProxyCredential ) try { if ($Script:AzStackHciConnectivityTargets) { $rootCATarget = $Script:AzStackHciConnectivityTargets | Where-Object Name -EQ System_Check_SSL_Inspection_Detection if ($rootCATarget.count -ne 1) { throw "Expected 1 System_RootCA, found $($rootCATarget.count)" } Install-UtilityModule -PsSession $PsSession -CmdletName Get-SslCertificateChain # We have two endpoints to check, they expire 6 months apart # meaning we should get a warning if criteria needs to change # 1 only require 1 endpoint to not be re-encrypted to succeed. $rootCATargetUrls = @() $rootCATarget.EndPoint | Foreach-Object { foreach ($p in $rootCATarget.Protocol) { $rootCATargetUrls += "{0}://{1}" -f $p,$PSITEM } } $AdditionalData = @() foreach ($rootCATargetUrl in $rootCATargetUrls) { Log-Info "Testing SSL chain for $rootCATargetUrl" [array]$ChainElements = Get-SigningRootChain -Uri $rootCATargetUrl -PsSession $PsSession -Proxy $Proxy -ProxyCredential $ProxyCredential # This is our canary internet endpoint, if we can't get the chain we probably don't have internet access. if ($null -eq $ChainElements) { $Status = 'Failed' $detail = "Failed to get certificate chain for $rootCATargetUrl. Ensure the endpoint is accessible and proxy configuration is correct." Log-Info $detail -Type Warning } else { # Remove the leaf as this will always contain O=Microsoft in its subject $ChainElements = $ChainElements[1..($ChainElements.Length-1)] $subjectMatchCount = 0 # We check for 2 expected subjects and only require 1 to succeed $rootCATarget.Tags.ExpectedSubject | Foreach-Object { if ($ChainElements.Subject -match $PSITEM) { $subjectMatchCount++ } } if ($subjectMatchCount -ge 1) { $Status = 'Succeeded' $detail = "Expected at least 1 chain certificate subject to match $($rootCATarget.Tags.ExpectedSubject -join ' or '). $subjectMatchCount matched." Log-Info $detail } else { $Status = 'Failed' $detail = "Expected at least 1 chain certificate subjects to match $($rootCATarget.Tags.ExpectedSubject -join ' or '). $subjectMatchCount matched. Actual subjects $($ChainElements.Subject -join ','). SSL decryption and re-encryption detected." Log-Info $detail -Type Error } } $AdditionalData += New-Object -TypeName PSObject -Property @{ Source = if ([string]::IsNullOrEmpty($PsSession.ComputerName)) { $ENV:COMPUTERNAME } else { $PsSession.ComputerName } Resource = $rootCATargetUrl Status = $Status Detail = $detail TimeStamp = [datetime]::UtcNow } } $rootCATarget.AdditionalData = $AdditionalData $rootCATarget.TimeStamp = [datetime]::UtcNow $rootCATarget.Status = if ('Succeeded' -in $rootCATarget.AdditionalData.Status) { 'Succeeded' } else { 'Failed'} Remove-UtilityModule -PsSession $PsSession return $rootCATarget } else { throw "No AzStackHciConnectivityTargets" } } catch { Log-Info "Test-RootCA failed with error: $($_.exception.message)" -Type Warning } } function Invoke-WebRequestEx { <# .SYNOPSIS Get Connectivity via Invoke-WebRequest .DESCRIPTION Get Connectivity via Invoke-WebRequest, supporting proxy. This function takes a connectivity target definition, creates a PS(5) Job for each endpoint and protocol and returns the results. If PsSession is provided, the jobs are run on the remote machine. Success is defined as a 200 status code or a valid status code (not service available), with a GET method and the response Uri is the same as the request Uri. .EXAMPLE PS C:\> Invoke-WebRequestEx -Target $Target Explanation of what the example does .INPUTS URI .OUTPUTS Output (if any) .NOTES In the case of a proxy being provided, the proxy is used for all endpoints. In the case of a proxy being configured on the box, invoke-webrequest will use the wininet proxy for all calls. Certificate Validation is disabled for the calls. #> [CmdletBinding()] param ( [Parameter()] [psobject] $Target, [Parameter()] [System.Management.Automation.Runspaces.PSSession[]] $PsSession, [Parameter()] [string] $Proxy, [Parameter()] [pscredential] $ProxyCredential ) $ScriptBlock = { $EndPoints = $args[0] $Protocol = $args[1] $TimeoutSecs = $args[2] $Proxy = $args[3] $ProxyCredential = $args[4] $target.TimeStamp = [datetime]::UtcNow $timeoutSecondsDefault = 10 if ([string]::IsNullOrEmpty($TimeoutSecs)) { $timeout = $timeoutSecondsDefault } else { $timeout = $TimeoutSecs } # Create an array of jobs for all uris and protocols $iwrJobs = @() foreach ($uri in $EndPoints) { foreach ($p in $Protocol) { # ScriptBlock to test connectivity $iwrScriptBlock = { try { $uri = $args[0] $proxy = $args[1] $proxyCred = $args[2] $Timeout = $args[3] $iwrParams =@{ Uri = $Uri UseBasicParsing = $true TimeoutSec = 30 } # Ignore certificate validation and use TLS 1.2 if (-not ([System.Management.Automation.PSTypeName]'ServerCertificateValidationCallback').Type) { $certCallback = @" using System; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; public class ServerCertificateValidationCallback { public static void Ignore() { if(ServicePointManager.ServerCertificateValidationCallback == null) { ServicePointManager.ServerCertificateValidationCallback += delegate ( Object obj, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors ) { return true; }; } } } "@ $null = Add-Type $certCallback $null = [ServerCertificateValidationCallback]::Ignore() $null = [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } if ($proxy) { $iwrParams.Add('Proxy', $proxy) $iwrParams.Add('ProxyUseDefaultCredentials', $true) } if ($proxyCred) { $iwrParams.Remove('ProxyUseDefaultCredentials') $iwrParams.Add('ProxyCredential', $proxyCred) } $measure = Measure-Command -Expression { $webOut = Invoke-WebRequest @iwrParams } $statusCode = $webout.StatusCode $webResponse = $webOut.BaseResponse $headers = $webOut.Headers $content = $webOut.Content } catch { $webResponse = $_.Exception.Response if ($webResponse) { try { $statusCode = [int]$webResponse.StatusCode $headers = @{} $content = [System.Text.Encoding]::UTF8.GetString($webResponse.GetResponseStream().ToArray()) foreach ($header in $webResponse.Headers) { $headers.$header = $webResponse.GetResponseHeader($header) } if ($webResponse.ContentType -eq 'application/json') { $content = ConvertFrom-Json -InputObject $content } } catch {} } $exception = @{ExceptionMessage = $_.Exception.Message; ErrorDetails = $_.ErrorDetails.Message; NonHTTPFailure = [System.String]::IsNullOrEmpty($webResponse) } } # Response Analysis # if status code is 200 return true # otherwise if the status code is a HTTP status code and the response Uri is the same as the request Uri and the method is GET, return true $isHttpStatusCode = $webResponse.StatusCode -is [System.Net.HttpStatusCode] $responseUriMatch = ([system.uri]$webResponse.ResponseUri).Host -eq ([system.uri]$uri).Host $responseMethodIsGet = $webResponse.Method -eq 'GET' $serviceUnavailable = $webResponse.StatusCode -eq [System.Net.HttpStatusCode]::ServiceUnavailable $test = $webResponse.StatusCode -eq [System.Net.HttpStatusCode]::OK -or ($isHttpStatusCode -and $responseUriMatch -and $responseMethodIsGet -and !$serviceUnavailable) # Gather TCP net connection to test layer 2 connectivity if it failed. if (-not $test) { $tnc = Test-NetConnection -ComputerName ([system.uri]$uri).Host -Port ([system.uri]$uri).Port -InformationLevel Quiet -WarningAction SilentlyContinue } $Detail = "TestIsSuccess: $test`r`nStatusCode: $statusCode`r`nResponseUri: $($webResponse.ResponseUri)`r`nResponseUriMatch: $responseUriMatch`r`nResponseMethodIsGet: $responseMethodIsGet`r`nTCPNetConnection: $tnc`r`nserviceUnavailable: $serviceUnavailable" return (New-Object -TypeName PSObject -Property @{ Source = $env:ComputerName Resource = $Uri Protocol = $p Status = if ($test) { "Succeeded" } else { "Failed" } TimeStamp = [datetime]::UtcNow StatusCode = $StatusCode Detail = $Detail DebugDtls = (New-Object -TypeName 'PSObject' -Property @{ 'Test' = $test 'Uri' = $Uri 'StatusCode'= $statusCode 'TCPNetConnection' = $tnc 'WebResponse' = $webResponse 'Headers' = $headers 'Exception' = $exception 'serviceUnavailable' = $serviceUnavailable 'ResponseUriMatch' = $responseUriMatch 'ResponseMethodIsGet' = $responseMethodIsGet 'ResponseUri' = $webResponse.ResponseUri 'ExceptionMessage' = $Exception.ExceptionMessage 'ErrorDetails' = $Exception.ErrorDetails 'NonHTTPFailure' = $Exception.NonHTTPFailure 'Server' = $webResponse.Server 'PowerShellVersion' = $PSVersionTable.PSVersion.Major 'Measure' = $measure.TotalMilliseconds }) }) } $Uri = "{0}://{1}" -f $p, ($Uri -Replace '\*', 'www') # Run scriptblock in PowerShell 5 to support responseuri $iwrJobs += Start-Job -PSVersion 5.1 -ArgumentList $Uri, $proxy, $proxyCred, $timeout -ScriptBlock $iwrScriptBlock } } $null = $iwrJobs | Wait-Job $results = @() $results += $iwrJobs | Receive-Job $iwrJobs | Remove-Job return $results } # Create a copy of the Target object $result = $Target | Select-Object -Property * $sessionArgs = @() if ($result) { $sessionArgs += @($result.EndPoint, $result.Protocol,$result.Tags.TimeoutSecs) } if ($Proxy) { $sessionArgs += $Proxy } if ($ProxyCredential) { $sessionArgs += $ProxyCredential } $result.AdditionalData += if ($PsSession) { Invoke-Command -Session $PsSession -ScriptBlock $ScriptBlock -ArgumentList $sessionArgs } else { Invoke-Command -ScriptBlock $ScriptBlock -ArgumentList $sessionArgs } if ($result.AdditionalData.Status -contains 'Failed') { $result.Status = 'Failed' } else { $result.Status = 'Succeeded' } # In the case of failures, log the debug info to aid troubleshooting $result.AdditionalData | ForEach-Object { if ( $_.Status -eq 'Failed' ){ Log-Info ("{0}: {1}" -f $_.Status, $_.Resource) -Type Warning Log-Info ("Debug {0}: {1}" -f $_.Resource, ($_.DebugDtls | ConvertTo-Json)) -Type Warning } else { Log-Info ("{0}: {1}" -f $_.Status, $_.Resource) } $_.DebugDtls = 'redacted' Log-Info ("{0}: {1}" -f $_.Status, $_.Resource) -Type $(if ( $_.Status -eq 'Failed' ){ "Warning" } else { "Info" } ) } $result.HealthCheckSource = $ENV:EnvChkrId return $result } function Get-ProxyDiagnostics { [CmdletBinding()] param( [Parameter()] [System.Management.Automation.Runspaces.PSSession] $PsSession, [Parameter()] [string] $Proxy ) Log-Info "Gathering proxy diagnostics" $proxyConfigs = @() # This might have to move to support proxy detection if (-not [string]::IsNullOrEmpty($Proxy)) { $proxyConfigs += Test-ProxyServer -PsSession $PsSession -Proxy $Proxy } $proxyConfigs += Get-WinHttp -PsSession $PsSession $proxyConfigs += Get-ProxyEnvironmentVariable -PsSession $PsSession $proxyConfigs += Get-IEProxy -PsSession $PsSession Log-Info ("Proxy details: {0}" -f $(($proxyConfigs | ConvertTo-Json -Depth 20) -replace "`r`n", '')) return $proxyConfigs } function Test-ProxyServer { [CmdletBinding()] param( [Parameter()] [System.Management.Automation.Runspaces.PSSession] $PsSession, [Parameter()] [string] $Proxy ) Log-Info "Testing User specified Proxy" $userProxy = $Script:AzStackHciConnectivityTargets | Where-Object Name -EQ System_Check_User_Proxy $UserProxyUri = [system.uri]$Proxy $userProxy.EndPoint = "{0}:{1}" -f $UserProxyUri.Host, $UserProxyUri.Port $userProxy.Protocol = $UserProxyUri.Scheme $userProxy.Service = @('System') $UserProxyResult = Invoke-WebRequestEx -Target $userProxy -PsSession $PsSession return $UserProxyResult } function Get-WinHttp { [CmdletBinding()] param( [Parameter()] [System.Management.Automation.Runspaces.PSSession] $PsSession ) Log-Info "Gathering WinHttp Proxy settings" $netshSb = { #$netsh = netsh winhttp show proxy @{ Source = $ENV:COMPUTERNAME Resource = netsh winhttp show proxy Status = 'Succeeded' } } $netsh = if ($PsSession) { Invoke-Command -Session $PsSession -ScriptBlock $netshSb $TargetResourceName = "WinHttp_Proxy_$($PsSession.ComputerName)" } else { Invoke-Command -ScriptBlock $netshSb $TargetResourceName = "WinHttp_Proxy_$($ENV:COMPUTERNAME)" } $winHttpProxy = New-Object -Type ProxyDiagnostics -Property @{ Name = 'AzStackHci_Connectivity_Collect_Proxy_Diagnostics_WinHttp' Title = 'WinHttp Proxy Settings' Severity = 'Informational' Description = 'Collects proxy configuration for WinHttp' Tags = $null Remediation = "https://docs.microsoft.com/en-us/azure-stack/hci/concepts/firewall-requirements?tabs=allow-table#set-up-a-proxy-server" TargetResourceID = '767c0b95-a3c9-43dd-b112-76dff50f2c75' TargetResourceName = $TargetResourceName TargetResourceType = 'Proxy_Setting' Timestamp = [datetime]::UtcNow Status = 'Succeeded' Service = 'System' AdditionalData = New-object PsObject -Property @{ source = $netsh.Source resource = if ($netsh.resource -like '*Direct access (no proxy server)*') { '<Not configured>' } else { [string]$netsh.resource -replace "`r`n", "" -replace 'Current WinHTTP proxy settings:', '' -replace ' ', '' } status = if ([string]::IsNullOrEmpty($netsh.status)) { 'Failed' } else { 'Succeeded' } detail = $netsh.resource } HealthCheckSource = $ENV:EnvChkrId } return $winHttpProxy } function Get-ProxyEnvironmentVariable { <# .SYNOPSIS Get Proxy configuration from environment variables .DESCRIPTION Get Proxy configuration from environment variables .EXAMPLE PS C:\> Get-ProxyEnvironmentVariable Explanation of what the example does .INPUTS URI .OUTPUTS Output (if any) .NOTES #> [CmdletBinding()] param ( [Parameter()] [System.Management.Automation.Runspaces.PSSession] $PsSession ) Log-Info "Gathering Proxy settings from environment variables" $envProxySb = { $AdditionalData = @() $AdditionalData += New-Object PsObject -Property @{ Source = "{0}_{1}" -f $ENV:COMPUTERNAME, "HTTPS_PROXY" Resource = if ($env:HTTPS_PROXY) { $env:HTTPS_PROXY } else { '<Not configured>' } Status = 'Succeeded' } $AdditionalData += New-Object PsObject -Property @{ Source = "{0}_{1}" -f $ENV:COMPUTERNAME, "HTTP_PROXY" Resource = if ($env:HTTP_PROXY) { $env:HTTP_PROXY } else { '<Not configured>' } Status = 'Succeeded' } return $AdditionalData } [array]$EnvironmentProxyOutput = if ($PsSession) { Invoke-Command -Session $PsSession -ScriptBlock $envProxySb $TargetResourceName = "Environment_Proxy_$($PsSession.ComputerName)" $Source = $PsSession.ComputerName } else { Invoke-Command -ScriptBlock $envProxySb $TargetResourceName = "Environment_Proxy_$($ENV:COMPUTERNAME)" $Source = $ENV:COMPUTERNAME } $EnvProxy = New-Object -Type ProxyDiagnostics -Property @{ Name = 'AzStackHci_Connectivity_Collect_Proxy_Diagnostics_Environment' Title = 'Environment Proxy Settings' Severity = 'Informational' Description = 'Collects proxy configuration from environment variables' Tags = $null Remediation = "https://docs.microsoft.com/en-us/azure-stack/aks-hci/set-proxy-settings" TargetResourceID = 'cb019485-676c-4c7d-98a8-fde6e5f35dfb' TargetResourceName = $TargetResourceName TargetResourceType = 'Proxy_Setting' Timestamp = [datetime]::UtcNow Status = 'Succeeded' Service = 'System' AdditionalData = $EnvironmentProxyOutput HealthCheckSource = $ENV:EnvChkrId } return $EnvProxy } function Get-IEProxy { <# .SYNOPSIS Get Proxy configuration from IE .DESCRIPTION Get Proxy configuration from IE .EXAMPLE PS C:\> Get-IEProxy Explanation of what the example does .INPUTS URI .OUTPUTS Output (if any) .NOTES [System.Net.WebProxy]::GetDefaultProxy() Address : BypassProxyOnLocal : False BypassList : {} Credentials : UseDefaultCredentials : False BypassArrayList : {} #> [CmdletBinding()] param ( [Parameter()] [System.Management.Automation.Runspaces.PSSession] $PsSession ) Log-Info "Gathering IE Proxy settings" $ieProxySb = { $ErrorActionPreference = 'SilentlyContinue' if ($PSVersionTable['Platform'] -eq 'Win32NT' -or $PSVersionTable['PSEdition'] -eq 'Desktop' ) { $IeProxySettings = Get-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings' | Select-Object ProxyServer, ProxyEnable New-Object PsObject -Property @{ Source = "$($ENV:COMPUTERNAME)" Resource = if ([string]::IsNullOrEmpty($IeProxySettings.ProxyServer) -and [string]::IsNullOrEmpty($IeProxySettings.ProxyEnable)) { '<Not configured>' } else { "{0} (Enabled:{1})" -f $IeProxySettings.ProxyServer, $IeProxySettings.ProxyEnable } Detail = $IeProxySettings Status = 'Succeeded' } } } [array]$AdditionalData = if ($PsSession) { Invoke-Command -Session $PsSession -ScriptBlock $ieProxySb $TargetResourceName = "IE_Proxy_$($PsSession.ComputerName)" } else { Invoke-Command -ScriptBlock $ieProxySb $TargetResourceName = "IE_Proxy_$($ENV:COMPUTERNAME)" } if (-not $AdditionalData) { Log-Info "No IE Proxy settings available" return $null } else { $ieProxy = New-Object -Type ProxyDiagnostics -Property @{ Name = 'AzStackHci_Connectivity_Collect_Proxy_Diagnostics_IEProxy' Title = 'IE Proxy Settings' Severity = 'Informational' Description = 'Collects Proxy configuration from IE' Tags = $null Remediation = "https://docs.microsoft.com/en-us/azure-stack/hci/concepts/firewall-requirements?tabs=allow-table#set-up-a-proxy-server" TargetResourceID = 'fe961ba6-295d-4880-82aa-2dd7322658d5' TargetResourceName = $TargetResourceName TargetResourceType = 'Proxy_Setting' Timestamp = [datetime]::UtcNow Status = 'Succeeded' Service = 'System' AdditionalData = $AdditionalData HealthCheckSource = $ENV:EnvChkrId } return $ieProxy } } function Write-FailedUrls { [CmdletBinding()] param ( $result ) if (-not [string]::IsNullOrEmpty($Global:AzStackHciEnvironmentLogFile)) { $file = Join-Path -Path (Split-Path $Global:AzStackHciEnvironmentLogFile -Parent) -ChildPath FailedUrls.txt } $failedUrls = $result.AdditionalData | Where-Object Status -NE Succeeded | Select-Object -ExpandProperty Resource if ($failedUrls.count -gt 0) { Log-Info ("[Over]Writing {0} to {1}" -f ($failedUrls -split ','), $file) $failedUrls | Out-File $file -Force Log-Info "`nFailed Urls log: $file" -ConsoleOut } } function Select-AzStackHciConnectivityTarget { <# .SYNOPSIS Apply user exclusions to Connectivity Targets #> [CmdletBinding()] param ( [Parameter()] [psobject] $Targets, [Parameter()] [string[]] $Exclude, [Parameter()] [string] $FilePath = "$PSScriptRoot\..\ExcludeTests.txt" ) try { $returnList = @($Targets) if ($exclude) { Log-Info "Removing tests $($exclude -join ',')" $returnList = $returnList | Where-Object { $_.Service | Select-String -Pattern $exclude -NotMatch } } if ($returnList.count -eq 0) { throw "No tests to perform after filtering" } if (Test-Path -Path $FilePath) { $fileExclusion = Get-Content -Path $FilePath Log-Info "Reading exclusion file $FilePath" -ConsoleOut Log-Info "Applying file exclusions: $($fileExclusion -join ',')" -ConsoleOut $returnList = $returnList | Where-Object {( $_.Service | Select-String -Pattern $fileExclusion -NotMatch ) -and ( $_.endpoint | Select-String -Pattern $fileExclusion -NotMatch )} } Log-Info "Test list: $($returnList -join ',')" if ($returnList.Count -eq 0) { Log-Info -Message "No tests to run." -ConsoleOut -Type Warning break noTestsBreak } return $returnList } catch { Log-Info "Failed to filter test list. Error: $($_.exception)" -Type Warning } } function Get-CloudEndpointFromManifest { [CmdletBinding()] param ( [Parameter()] [system.uri] $Uri = 'https://aka.ms/hciconnectivitytargets' ) try { $tempXmlFile = Join-Path -Path $env:temp -ChildPath 'AzStackHciConnectivityTarget.xml' Write-Verbose "Retrieving connectivity targets from $Uri to temp location: $tempXmlFile..." $iwrParams = @{ Uri = $Uri UseBasicParsing = $true OutFile = $tempXmlFile } $response = Invoke-WebRequest @iwrParams $testSigningScript = Join-Path -Path $PSScriptRoot -ChildPath 'TestXmlSigning.ps1' Write-Verbose "Validating signature of $tempXmlFile..." [bool]$checkSigningResult = Start-Job -PSVersion 5.1 -ScriptBlock { & $USING:testSigningScript $USING:tempXmlFile } | Wait-Job | Receive-Job -ErrorAction SilentlyContinue Write-Verbose "Signature validation result: $checkSigningResult" if (-not $checkSigningResult) { throw "Failed to validate signature of $tempXmlFile from $url" } [xml]$manifest = Get-Content -Path $tempXmlFile $version = $manifest.Objects.Object.Property | Where-Object Name -eq Version | Select -ExpandProperty '#text' $title = $manifest.Objects.Object.Property | Where-Object Name -eq Title | Select -ExpandProperty '#text' $targets = $manifest.Objects.Object.Property | Where-Object Name -eq Targets | Select -ExpandProperty 'Property' [AzStackHciConnectivityTarget[]]$targets = New-AzStackHciConnectivityTargetFromXml -TargetXml $targets $msg = "Retrieved $($targets.count) connectivity targets from $($title) version $($version)" Write-Verbose $msg return $targets } catch { $msg = "Failed to get connectivity targets from $Uri. Error: $($_.exception.message)" Write-Verbose $msg } finally { if (Test-Path -Path $tempXmlFile) { Remove-Item -Path $tempXmlFile -ErrorAction SilentlyContinue } } } function Export-AzStackHciConnectivityTargetToXml { [CmdletBinding()] param ( [Parameter(Mandatory)] [string] $TargetDirectory, [Parameter(Mandatory)] [string] $TargetFileName, [Parameter(Mandatory)] [string] $Version ) $AzStackHciConnectivityTargets = Get-AzStackHciConnectivityTarget -LocalOnly -IncludeSystem Write-Host ("Found {0} connectivity targets for manifest" -f [int]($Script:AzStackHciConnectivityTargets.Count)) Write-Host "Creating manifest with version $version" $manifest = New-Object AzStackHciConnectivityManifest -Property @{ Title = 'AzStackHci Connectivity Endpoint Definitions' Version = $Version Targets = $Script:AzStackHciConnectivityTargets } $manifest | ConvertTo-Xml -Depth 5 -As Stream | Out-File $TargetDirectory\$TargetFileName -Encoding utf8 } function New-AzStackHciConnectivityTargetFromXml { param ($TargetXml) foreach ($target in $TargetXml) { $ErrorActionPreference = 'Stop' $AzStackHciConnectivityTargetObject = New-Object AzStackHciConnectivityTarget # Arrays 'EndPoint', 'Protocol', 'Service', 'OperationType' | Foreach-Object { $AzStackHciConnectivityTargetObject.$PSITEM = ($target.Property | Where-Object Name -eq $PSITEM).ChildNodes.'#text' } # Booleans 'Mandatory', 'System' | ForEach-Object { $AzStackHciConnectivityTargetObject.$PSITEM = if (($target.Property | Where-Object Name -eq $PSITEM).'#text' -eq 'True') { $true } else { $false } } # Strings 'Name', 'Title', 'Severity', 'Description', 'Remediation', 'TargetResourceID', 'TargetResourceName', 'TargetResourceType', 'Group' | Foreach-Object { $AzStackHciConnectivityTargetObject.$PSITEM = ($target.Property | Where-Object Name -eq $PSITEM).'#text' } # Tags $ErrorActionPreference = 'SilentlyContinue' $tagsProperties = $target.Property | Where-Object Name -eq Tags | Select-Object -ExpandProperty Property $Groups = $tagsProperties | Where-Object Name -eq Group $Mandatory = $tagsProperties | Where-Object Name -eq Mandatory $Service = $tagsProperties | Where-Object Name -eq Service $OperationType = $tagsProperties | Where-Object Name -eq OperationType $ExpectedSubject = $tagsProperties | Where-Object Name -eq ExpectedSubject # Tags are optional, we iterate through them to $tagHash = @{} if ($Groups) { $tagHash += @{Group = $Groups.'#text'}} if ($Service) { $tagHash += @{Service = $Service.ChildNodes.'#text'}} if ($Mandatory) { $tagHash += @{Mandatory = if ($Mandatory.'#text' -eq 'True') { $true } else { $false }}} if ($OperationType) { $tagHash += @{OperationType = $OperationType.ChildNodes.'#text'}} if ($ExpectedSubject) { $tagHash += @{ExpectedSubject = $ExpectedSubject.ChildNodes.'#text'}} $AzStackHciConnectivityTargetObject.Tags = New-Object PsObject -Property $tagHash Write-Verbose $AzStackHciConnectivityTargetObject.Name $AzStackHciConnectivityTargetObject } } # SIG # Begin signature block # MIIoLQYJKoZIhvcNAQcCoIIoHjCCKBoCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAmhuQN0Ig48wl/ # A4PhnkPGXlMV0qTSXzdjDmbwtsJ78aCCDXYwggX0MIID3KADAgECAhMzAAADTrU8 # esGEb+srAAAAAANOMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjMwMzE2MTg0MzI5WhcNMjQwMzE0MTg0MzI5WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQDdCKiNI6IBFWuvJUmf6WdOJqZmIwYs5G7AJD5UbcL6tsC+EBPDbr36pFGo1bsU # p53nRyFYnncoMg8FK0d8jLlw0lgexDDr7gicf2zOBFWqfv/nSLwzJFNP5W03DF/1 # 1oZ12rSFqGlm+O46cRjTDFBpMRCZZGddZlRBjivby0eI1VgTD1TvAdfBYQe82fhm # WQkYR/lWmAK+vW/1+bO7jHaxXTNCxLIBW07F8PBjUcwFxxyfbe2mHB4h1L4U0Ofa # +HX/aREQ7SqYZz59sXM2ySOfvYyIjnqSO80NGBaz5DvzIG88J0+BNhOu2jl6Dfcq # jYQs1H/PMSQIK6E7lXDXSpXzAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUnMc7Zn/ukKBsBiWkwdNfsN5pdwAw # RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW # MBQGA1UEBRMNMjMwMDEyKzUwMDUxNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci # tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG # CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 # MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAD21v9pHoLdBSNlFAjmk # mx4XxOZAPsVxxXbDyQv1+kGDe9XpgBnT1lXnx7JDpFMKBwAyIwdInmvhK9pGBa31 # TyeL3p7R2s0L8SABPPRJHAEk4NHpBXxHjm4TKjezAbSqqbgsy10Y7KApy+9UrKa2 # kGmsuASsk95PVm5vem7OmTs42vm0BJUU+JPQLg8Y/sdj3TtSfLYYZAaJwTAIgi7d # hzn5hatLo7Dhz+4T+MrFd+6LUa2U3zr97QwzDthx+RP9/RZnur4inzSQsG5DCVIM # pA1l2NWEA3KAca0tI2l6hQNYsaKL1kefdfHCrPxEry8onJjyGGv9YKoLv6AOO7Oh # JEmbQlz/xksYG2N/JSOJ+QqYpGTEuYFYVWain7He6jgb41JbpOGKDdE/b+V2q/gX # UgFe2gdwTpCDsvh8SMRoq1/BNXcr7iTAU38Vgr83iVtPYmFhZOVM0ULp/kKTVoir # IpP2KCxT4OekOctt8grYnhJ16QMjmMv5o53hjNFXOxigkQWYzUO+6w50g0FAeFa8 # 5ugCCB6lXEk21FFB1FdIHpjSQf+LP/W2OV/HfhC3uTPgKbRtXo83TZYEudooyZ/A # Vu08sibZ3MkGOJORLERNwKm2G7oqdOv4Qj8Z0JrGgMzj46NFKAxkLSpE5oHQYP1H # tPx1lPfD7iNSbJsP6LiUHXH1MIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq # hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 # IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG # EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG # A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg # Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC # CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03 # a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr # rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg # OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy # 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9 # sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh # dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k # A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB # w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn # Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90 # lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w # ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o # ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD # VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa # BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny # bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG # AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t # L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV # HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG # AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl # AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb # C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l # hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6 # I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0 # wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560 # STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam # ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa # J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah # XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA # 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt # Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr # /Xmfwb1tbWrJUnMTDXpQzTGCGg0wghoJAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp # Z25pbmcgUENBIDIwMTECEzMAAANOtTx6wYRv6ysAAAAAA04wDQYJYIZIAWUDBAIB # BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO # MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIGmRnKFNKhHmNOnBV8UTDHHB # gBOmb9B2qv3wJm37haxEMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A # cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB # BQAEggEASHPNbebeQWamGc3a8I6P22sB4/Mo2XvuSWFhwUPXNSr4cWjVDRphmSkQ # rhDjBssFYWz7FbqyobKv8Icn/PAsfm+kM/hSOL8y8o0bLaSrXG6E/RBFUqIZkYA8 # sjlOzthX6OdMXQcX8J6D7FVZWpmReBCLZT+XNPunUfL7RwHbDeVbT7aB04bait3L # ePzI/ms7DsReR5YE+l62sLeVJyIPcIkYb6KvdV/Uzyu3gRcptgogjgHO1HYwVOh4 # lTKO+WfGxSlqp42FFxbVnlt9wTMHXoRhv9R33CyoJd7n6WV2C9YTmd5ffWFdNlA4 # zXe8EM3dm/uvRL64d4KND7ea9VAodKGCF5cwgheTBgorBgEEAYI3AwMBMYIXgzCC # F38GCSqGSIb3DQEHAqCCF3AwghdsAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq # hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl # AwQCAQUABCCnAfCkurB6x7DHZQ7vb2hYEHJDR7/iTXS/MbI9xRs6ogIGZQPePKSv # GBMyMDIzMDkyMjA4MzEwNS4zMzFaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l # cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046QTkzNS0w # M0UwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg # ghHtMIIHIDCCBQigAwIBAgITMwAAAdGyW0AobC7SRQABAAAB0TANBgkqhkiG9w0B # AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD # VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMzA1MjUxOTEy # MThaFw0yNDAyMDExOTEyMThaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz # aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv # cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z # MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046QTkzNS0wM0UwLUQ5NDcxJTAjBgNV # BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQCZTNo0OeGz2XFd2gLg5nTlBm8XOpuwJIiXsMU61rwq # 1ZKDpa443RrSG/pH8Gz6XNnFQKGnCqNCtmvoKULApwrT/s7/e1X0lNFKmj7U7X4p # 00S0uQbW6LwSn/zWHaG2c54ZXsGY+BYfhWDgbFpCTxRzTnRCG62bkWPp6ZHbZPg4 # Ht1CRCAMhhOGTR8wI4G7wwWZwdMc6UvUUlq0ql9AxAfzkYRpi2tRvDHMdmZ3vyXp # qhFwvRG8cgCH/TTCjW5q6aNbdqKL3BFDPzUtuCNsPXL3/E0dR2bDMqa0aNH+iIfh # GC4/vcwuteOMCPUIDVSqDCNfIaPDEwYci1fd9gu1zVw+HEhDZM7Ea3nxIUrzt+Rf # p5ToMMj4QAmJ6Uadm+TPbDbo8kFIK70ShmW8wn8fJk9ReQQEpTtIN43eRv9QmXy3 # Ued80osOBE+WkdMvSCFh+qgCsKdzQxQJG62cTeoU2eqNhH3oppXmyfVUwbsefQzM # PtbinCZd0FUlmlM/dH+4OniqQyaHvrtYy3wqIafY3zeFITlVAoP9q9vF4W7KHR/u # F0mvTpAL5NaTDN1plQS0MdjMkgzZK5gtwqOe/3rTlqBzxwa7YYp3urP5yWkTzISG # nhNWIZOxOyQIOxZfbiIbAHbm3M8hj73KQWcCR5JavgkwUmncFHESaQf4Drqs+/1L # 1QIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFAuO8UzF7DcH0mmsF4XQxxHQvS2jMB8G # A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG # Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy # MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w # XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy # dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG # A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD # AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQCbu9rTAHV24mY0qoG5eEnImz5akGXTviBw # Kp2Y51s26w8oDrWor+m00R4/3BcDmYlUK8Nrx/auYFYidZddcUjw42QxSStmv/qW # nCQi/2OnH32KVHQ+kMOZPABQTG1XkcnYPUOOEEor6f/3Js1uj4wjHzE4V4aumYXB # Asr4L5KR8vKes5tFxhMkWND/O7W/RaHYwJMjMkxVosBok7V21sJAlxScEXxfJa+/ # qkqUr7CZgw3R4jCHRkPqQhMWibXPMYar/iF0ZuLB9O89DMJNhjK9BSf6iqgZoMuz # IVt+EBoTzpv/9p4wQ6xoBCs29mkj/EIWFdc+5a30kuCQOSEOj07+WI29A4k6QIRB # 5w+eMmZ0Jec0sSyeQB5KjxE51iYMhtlMrUKcr06nBqCsSKPYsSAITAzgssJD+Z/c # TS7Cu35fJrWhM9NYX24uAxYLAW0ipNtWptIeV6akuZEeEV6BNtM3VTk+mAlV5/eC # /0Y17aVSjK5/gyDoLNmrgVwv5TAaBmq/wgRRFHmW9UJ3zv8Lmk6mIoAyTpqBbuUj # MLyrtajuSsA/m2DnKMO0Qiz1v+FSVbqM38J/PTlhCTUbFOx0kLT7Y/7+ZyrilVCz # yAYfFIinDIjWlM85tDeU8ZfJCjFKwq3DsRxV4JY18xww8TTmod3lkr9NqGQ54Lmy # PVc+5ibNrjCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI # hvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw # DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x # MjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAy # MDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp # bWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC # AQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25Phdg # M/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPF # dvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6 # GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBp # Dco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50Zu # yjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3E # XzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0 # lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1q # GFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ # +QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PA # PBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkw # EgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxG # NSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARV # MFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWlj # cm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAK # BggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC # AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX # zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v # cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI # KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG # 9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0x # M7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmC # VgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449 # xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wM # nosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDS # PeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2d # Y3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxn # GSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+Crvs # QWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokL # jzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL # 6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNQ # MIICOAIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp # bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw # b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn # MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOkE5MzUtMDNFMC1EOTQ3MSUwIwYDVQQD # ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQBH # JY2Fv+GhLQtRDR2vIzBaSv/7LKCBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w # IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6LeWnzAiGA8yMDIzMDkyMjA0Mjkx # OVoYDzIwMjMwOTIzMDQyOTE5WjB3MD0GCisGAQQBhFkKBAExLzAtMAoCBQDot5af # AgEAMAoCAQACAhb0AgH/MAcCAQACAhKDMAoCBQDouOgfAgEAMDYGCisGAQQBhFkK # BAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJ # KoZIhvcNAQELBQADggEBAMMZorCacoCGWONUA0UprCJrLvLeTJ4b44I+sLVpt09M # liZFEw+lydZaB5kgmnaYEqCJQTlzvUEwmhjJPkbJQpMwlHCO56rbfNUy6NeL+ry/ # 0k+6/jVsktnFGB/bThmMKLqURQwRSjL9B/EAZK1weD7uaWj+WfAK70+UHnykQaTb # qKDjRh8/PmFyzQ+v7STwmuB1DGuZySfYfvL9TSUrf9HkzF7zW/Nsr22psW7w9FfI # ueH7ZDfksQsYTJuUWooeJf+V52ZFUo/l6hi82XIFVnj0H35uMoroyH2K52hRA72m # yhrZs64BZ33zNaxyAkj/TR0oLZORGQvAac7WrnibMxUxggQNMIIECQIBATCBkzB8 # MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk # bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1N # aWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAdGyW0AobC7SRQABAAAB # 0TANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEE # MC8GCSqGSIb3DQEJBDEiBCDFRpPZJyCttbO4m8jT18gZY+6Ndre74Aj9KqIKoYOV # wzCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIMy8YXkCALv57c5sRhrPTub1 # q4TwJ6oVA36k8IiI/AcMMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT # Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m # dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB # IDIwMTACEzMAAAHRsltAKGwu0kUAAQAAAdEwIgQgvPNc74y+0hlzLzQQhN//sdFX # fKbYz0wCrq96EjDDf4AwDQYJKoZIhvcNAQELBQAEggIAYwg14amlq8QvFuJ/Y2qF # CmubLDPJQX2PYXntx/nP2Uxj+o3a1jSn3Ob9Sgu5Dna5DoacyG6On5K9msNj5IFf # tKqOaXKJ1Pb5prQlQSv8mdPfTfBERNG/oA4fDWsYX3bjk5KWWFrkL5oXEm+QYpOh # jrJ/DJfAt+snWUMS6ZjVZQ0eQ3h4oP66KTRslrz3AmqoGGBRHW3CUr3qPhBv4TiV # vPSAduHZxTGwrh+8rNPSkIsc8knHF9TyJfzAKhokgsIUWVO2tdVcwYa9oLNo1e+t # DMvj6Udm7zUXK6RdgFrGf2/kzIzyKbcjDcHtZnGbLkIGVvLZVvJtflzo8+rpdADH # GhGqsHvBpnUvmsiRBpWkvlH1Et9sTz8f2rvvRiZg103Lp2JJn8gCRWEqTAOWUnFm # CCXxka9mOQsxkyqbjr9o4djBlOnGmGXc7wT9OZ9Fgj5YJPjYYREwWx9vlsdUSupk # H8dtgG5LeWgpR4f+kW5uBNJ7P4HbupDXsNdvTn5oKr3PgIPMeGRL97w4cmZkq+dy # xkDTsIC2hQs2nEN8MzdUVe+5g8W6IsKzAf3pjhUptWtlxptICiBDGXHwzjDiNtC9 # I5S2NQhCeGPv4pQQLzCmyBHHfyVyhCwhB+85wBxYGsRV4P3OAN+P8+KnXQ/hVj7h # FEI5AL+EbT+GDdvWzpa4RSM= # SIG # End signature block |