AzStackHciConnectivity/AzStackHci.Connectivity.Helpers.psm1
class HealthModel { # Attributes for Azure Monitor schema [string]$Name #Name of the individual test/rule/alert that was executed. Unique, not exposed to the customer. [string]$Title #User-facing name; one or more sentences indicating the direct issue. [string]$Severity #Severity of the result (Critical, Warning, Informational, Hidden) – this answers how important the result is. Critical is the only update-blocking severity. [string]$Description #Detailed overview of the issue and what impact the issue has on the stamp. [psobject]$Tags #Key-value pairs that allow grouping/filtering individual tests. For example, "Group": "ReadinessChecks", "UpdateType": "ClusterAware" [string]$Status #The status of the check running (i.e. Failed, Succeeded, In Progress) – this answers whether the check ran, and passed or failed. [string]$Remediation #Set of steps that can be taken to resolve the issue found. [string]$TargetResourceID #The unique identifier for the affected resource (such as a node or drive). [string]$TargetResourceName #The name of the affected resource. [string]$TargetResourceType #The type of resource being referred to (well-known set of nouns in infrastructure, aligning with Monitoring). [datetime]$Timestamp #The Time in which the HealthCheck was called. [psobject]$AdditionalData #Property bag of key value pairs for additional information. [string]$HealthCheckSource #The name of the services called for the HealthCheck (I.E. Test-AzureStack, Test-Cluster). } class AzStackHciConnectivityTarget : HealthModel { # Attribute for performing check [string[]]$EndPoint [string[]]$Protocol # Additional Attributes for end user interaction [string[]]$Service # short cut property to Service from tags [string[]]$OperationType # short cut property to Operation Type from tags [string[]]$Group # short cut property to group from tags [bool]$Mandatory # short cut property to mandatory from tags [bool]$System # targets for system checks such as proxy traversal } #Create additional classes to help with writing/report results class Diagnostics : 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 } } } } # Always add Mandatory targets $executionTargets += $Script:AzStackHciConnectivityTargets | Where-Object Mandatory | ForEach-Object { if ($PSITEM -notin $executionTargets) { $PSITEM } } if ($IncludeSystem) { return $executionTargets } else { return ($executionTargets | Where-Object Service -NotContains 'System') } } catch { throw "Get failed: $($_.exception)" } } function Import-AzStackHciConnectivityTarget { <# .SYNOPSIS Retrieve Endpoints from built target packs .DESCRIPTION Retrieve Endpoints from built target packs .EXAMPLE PS C:\> Import-AzStackHciConnectivityTarget Explanation of what the example does .INPUTS URI .OUTPUTS PSObject .NOTES #> [CmdletBinding()] param () try { $Script:AzStackHciConnectivityTargets = @() $targetFiles = Get-ChildItem -Path "$PSScriptRoot\Targets\*.json" | Select-Object -ExpandProperty FullName Write-Verbose ("Importing {0}" -f ($targetFiles -join ',')) ForEach ($targetFile in $targetFiles) { try { # TO DO - Add validations: # - protocol should not contain :// $targetPackContent = Get-Content -Path $targetFile | ConvertFrom-Json -WarningAction SilentlyContinue foreach ($target in $targetPackContent) { #Set Name of the individual test/rule/alert that was executed. Unique, not exposed to the customer. $target | Add-Member -MemberType NoteProperty -Name HealthCheckSource -Value ((Get-PSCallStack).Command -join '\') $target.TargetResourceID = $target.EndPoint -join '_' $target.TargetResourceName = $target.EndPoint -join '_' $target.TargetResourceType = 'External Endpoint' $Script:AzStackHciConnectivityTargets += [AzStackHciConnectivityTarget]$target } } catch { Log-Info -Message ("Unable to read {0}. Error: {1}" -f (Split-Path -Path $targetFile -Leaf), $_.Exception.Message) -Type Warning } } } catch { throw "Import failed: $($_.exception)" } } function Get-CloudEndpointFromManifest { <# .SYNOPSIS Retrieve Endpoints to test from Cloud Manifest .DESCRIPTION Retrieve Endpoints to test from Cloud Manifest .EXAMPLE PS C:\> Get-CloudEndpointFromManifest -Uri Explanation of what the example does .INPUTS URI .OUTPUTS Output (if any) .NOTES URL: https://docs.microsoft.com/en-us/javascript/api/@azure/arm-azurestack/cloudmanifestfile?view=azure-node-preview #> [CmdletBinding()] param ( [Parameter()] [System.Uri] $Uri ) throw "Not implemented" } function Get-SystemProxy { <# .SYNOPSIS Get Proxy set on system .DESCRIPTION Get Proxy set on system .EXAMPLE PS C:\> Get-SystemProxy Explanation of what the example does .OUTPUTS Output (if any) .NOTES #> [CmdletBinding()] param () throw "Not implemented" } function Get-SigningRootChain { <# .SYNOPSIS Get signing root for https endpoint .DESCRIPTION Get signing root for https endpoint .EXAMPLE PS C:\> Get-SigningRoot -uri MicrosoftOnline.com Explanation of what the example does .INPUTS URI .OUTPUTS Output (if any) .NOTES #> [CmdletBinding()] param ( [Parameter()] [System.Uri] $Uri, [Parameter()] [System.Management.Automation.Runspaces.PSSession] $PsSession, [Parameter()] [string] $Proxy, [Parameter()] [pscredential] $proxyCredential ) try { $sb = { $uri = $args[0] $proxy = $args[1] $proxyCredential = $args[2] function Get-SslCertificateChain { <# .SYNOPSIS Retrieve remote ssl certificate & chain from https endpoint for Desktop and Core .NOTES Credit: https://github.com/markekraus #> [CmdletBinding()] param ( [system.uri] $url, [Parameter()] [string] $Proxy, [Parameter()] [pscredential] $ProxyCredential ) try { $cs = @' using System; using System.Collections.Generic; using System.Net.Http; using System.Net.Security; using System.Security.Cryptography.X509Certificates; namespace CertificateCapture { public class Utility { public static Func<HttpRequestMessage,X509Certificate2,X509Chain,SslPolicyErrors,Boolean> ValidationCallback = (message, cert, chain, errors) => { CapturedCertificates.Clear(); var newCert = new X509Certificate2(cert); var newChain = new X509Chain(); newChain.Build(newCert); CapturedCertificates.Add(new CapturedCertificate(){ Certificate = newCert, CertificateChain = newChain, PolicyErrors = errors, URI = message.RequestUri }); return true; }; public static List<CapturedCertificate> CapturedCertificates = new List<CapturedCertificate>(); } public class CapturedCertificate { public X509Certificate2 Certificate { get; set; } public X509Chain CertificateChain { get; set; } public SslPolicyErrors PolicyErrors { get; set; } public Uri URI { get; set; } } } '@ if ($PSEdition -ne 'Core') { Add-Type -AssemblyName System.Net.Http Add-Type $cs -ReferencedAssemblies System.Net.Http } else { Add-Type $cs } $Certs = [CertificateCapture.Utility]::CapturedCertificates $Handler = [System.Net.Http.HttpClientHandler]::new() if ($Proxy) { $Handler.Proxy = New-Object System.Net.WebProxy($proxy) if ($proxyCredential) { $Handler.DefaultProxyCredentials = $ProxyCredential } } $Handler.ServerCertificateCustomValidationCallback = [CertificateCapture.Utility]::ValidationCallback $Client = [System.Net.Http.HttpClient]::new($Handler) $null = $Client.GetAsync($url).Result return $Certs.CertificateChain } catch { throw $_ } } $chain = Get-SslCertificateChain -Url $Uri -Proxy $Proxy -ProxyCredential $ProxyCredential if ($chain.ChainElements.Certificate.Count -le 1) { throw ("Unexpected certificate chain in response. Expected 2 or more certificates in chain, found {0}. {1}" -f $chain.ChainElements.Certificate.Count, ` ($chain.ChainElements.Certificate | ForEach-Object { "Thumbprint: {0}, Subject: {1}, Issuer: {2}" -f $_.Thumbprint, $_.Subject, $_.Issuer })) } return $chain.ChainElements.Certificate } $ChainElements = if ($PsSession) { Invoke-Command -Session $PsSession -ScriptBlock $sb -ArgumentList $Uri, $Proxy, $ProxyCredential } else { Invoke-Command -ScriptBlock $sb -ArgumentList $Uri, $Proxy, $ProxyCredential } return $ChainElements } catch { throw $_ } } function Test-RootCA { <# .SYNOPSIS Short description .DESCRIPTION Long description .EXAMPLE PS C:\> <example usage> Explanation of what the example does .INPUTS Inputs (if any) .OUTPUTS Output (if any) .NOTES General notes #> param( [Parameter()] [System.Management.Automation.Runspaces.PSSession] $PsSession, [Parameter()] [string] $Proxy, [Parameter()] [pscredential] $ProxyCredential ) try { if ($Script:AzStackHciConnectivityTargets) { $rootCATarget = $Script:AzStackHciConnectivityTargets | Where-Object Name -EQ System_Check_SSL_Inspection_Detection if ($rootCATarget.count -ne 1) { throw "Expected 1 System_RootCA, found $($rootCATarget.count)" } # We have two endpoints to check, they expire 6 months apart # meaning we should get a warning if criteria needs to change # 1 only require 1 endpoint to not be re-encrypted to succeed. $rootCATargetUrls = @() $rootCATarget.EndPoint | Foreach-Object { foreach ($p in $rootCATarget.Protocol) { $rootCATargetUrls += "{0}://{1}" -f $p,$PSITEM } } $AdditionalData = @() foreach ($rootCATargetUrl in $rootCATargetUrls) { Log-Info "Testing SSL chain for $rootCATargetUrl" [array]$ChainElements = Get-SigningRootChain -Uri $rootCATargetUrl -PsSession $PsSession -Proxy $Proxy -ProxyCredential $ProxyCredential # Remove the leaf as this will always contain O=Microsoft in its subject $ChainElements = $ChainElements[1..($ChainElements.Length-1)] $subjectMatchCount = 0 # We check for 2 expected subjects and only require 1 to succeed $rootCATarget.Tags.ExpectedSubject | Foreach-Object { if ($ChainElements.Subject -match $PSITEM) { $subjectMatchCount++ } } if ($subjectMatchCount -ge 1) { $Status = 'Succeeded' $detail = "Expected at least 1 chain certificate subject to match $($rootCATarget.Tags.ExpectedSubject -join ' or '). $subjectMatchCount matched." Log-Info $detail } else { $Status = 'Failed' $detail = "Expected at least 1 chain certificate subjects to match $($rootCATarget.Tags.ExpectedSubject -join ' or '). $subjectMatchCount matched. Actual subjects $($ChainElements.Subject -join ','). SSL decryption and re-encryption detected." Log-Info $detail -Type Error } $AdditionalData += New-Object -TypeName PSObject -Property @{ Source = if ([string]::IsNullOrEmpty($PsSession.ComputerName)) { [System.Net.Dns]::GetHostName() } else { $PsSession.ComputerName } Resource = $rootCATargetUrl Status = $Status Detail = $detail TimeStamp = [datetime]::UtcNow } } $rootCATarget.AdditionalData = $AdditionalData $rootCATarget.TimeStamp = [datetime]::UtcNow $rootCATarget.Status = if ('Succeeded' -in $rootCATarget.AdditionalData.Status) { 'Succeeded' } else { 'Failed'} return $rootCATarget } else { throw "No AzStackHciConnectivityTargets" } } catch { Log-Info "Test-RootCA failed with error: $($_.exception.message)" -Type Warning } } function Invoke-WebRequestEx { <# .SYNOPSIS Get Connectivity via Invoke-WebRequest .DESCRIPTION Get Connectivity via Invoke-WebRequest, supporting proxy .EXAMPLE PS C:\> Invoke-WebRequestEx -Target $Target Explanation of what the example does .INPUTS URI .OUTPUTS Output (if any) .NOTES #> [CmdletBinding()] param ( [Parameter()] [psobject] $Target, [Parameter()] [System.Management.Automation.Runspaces.PSSession[]] $PsSession, [Parameter()] [string] $Proxy, [Parameter()] [pscredential] $ProxyCredential ) $ScriptBlock = { $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:" -Type Warning } 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 Name -EQ System_Check_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 # MIInrQYJKoZIhvcNAQcCoIInnjCCJ5oCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCL4liCYCVazTKY # 07XOlgeFsNEiuOVPZuBRF6aekrBet6CCDYEwggX/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/BvW1taslScxMNelDNMYIZgjCCGX4CAQEwgZUwfjELMAkG # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z # b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAsyOtZamvdHJTgAAAAACzDAN # BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor # BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgZeQNbb5Y # MR/ozT0PYpWl9RHt1R3hbUvz4MIHVICxFrUwQgYKKwYBBAGCNwIBDDE0MDKgFIAS # AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN # BgkqhkiG9w0BAQEFAASCAQAs37nXGnogRBEac1yW7MGIQ+YHTw4tOuBvw8F6PpPH # x8qial8cIKA3MaTgrsNuSTJjh7om9Gw380Xb9lpFjRZrNdHT28nj6iS+q4RNHlZf # Zplbb1Az2AMPWOY37Rzvfbwj3pHItT2CoVE188HuX7RbkO9ZxMWJaqVcT6agCpET # mlmZY2qgmEFeicCTCM/hVe4uuGAwTs+Es9PyFxN/fAFgkTBQa72WkDimN1UaG6jp # QSrSskgt2WKpT/c7rvFn8zIahTZXlh/IPlthUNrcAwzLbOr0VUP+DHWsz2kMlevk # 2nxLkUVp0tz4JXwn08FrUo77LAk4Kjd9o1aMI5sFfwL4oYIXDDCCFwgGCisGAQQB # gjcDAwExghb4MIIW9AYJKoZIhvcNAQcCoIIW5TCCFuECAQMxDzANBglghkgBZQME # AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB # MDEwDQYJYIZIAWUDBAIBBQAEINXc1Xyjq0+9OxZU0rqzfDePt0qU8r1ZUYDuZhO9 # K9cQAgZjc45YzicYEzIwMjIxMTI5MDgzMDExLjA5MVowBIACAfSggdSkgdEwgc4x # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p # Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg # VFNTIEVTTjowQTU2LUUzMjktNEQ0RDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt # U3RhbXAgU2VydmljZaCCEV8wggcQMIIE+KADAgECAhMzAAABpzW7LsJkhVApAAEA # AAGnMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo # aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y # cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw # MB4XDTIyMDMwMjE4NTEyMloXDTIzMDUxMTE4NTEyMlowgc4xCzAJBgNVBAYTAlVT # MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy # YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjowQTU2 # LUUzMjktNEQ0RDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj # ZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAO0jOMYdUAXecCWm5V6T # RoQZ4hsPLe0Vp6CwxFiTA5l867fAbDyxnKzdfBsf/0XJVXzIkcvzCESXoklvMDBD # a97SEv+CuLEIooMbFBH1WeYgmLVO9TbbLStJilNITmQQQ4FB5qzMEsDfpmaZZMWW # gdOjoSQed9UrjjmcuWsSskntiaUD/VQdQcLMnpeUGc7CQsAYo9HcIMb1H8DTcZ7y # Ae3aOYf766P2OT553/4hdnJ9Kbp/FfDeUAVYuEc1wZlmbPdRa/uCx4iKXpt80/5w # oAGSDl8vSNFxi4umXKCkwWHm8GeDZ3tOKzMIcIY/64FtpdqpNwbqGa3GkJToEFPR # 6D6XJ0WyqebZvOZjMQEeLCJIrSnF4LbkkfkX4D4scjKz92lI9LRurhMPTBEQ6pw3 # iGsEPY+Jrcx/DJmyQWqbpN3FskWu9xhgzYoYsRuisCb5FIMShiallzEzum5xLE4U # 5fuxEbwk0uY9ZVDNVfEhgiQedcSAd3GWvVM36gtYy6QJfixD7ltwjSm5sVa1voBf # 2WZgCC3r4RE7VnovlqbCd3nHyXv5+8UGTLq7qRdRQQaBQXekT9UjUubcNS8ZYeZw # K8d2etD98mSI4MqXcMySRKUJ9OZVQNWzI3LyS5+CjIssBHdv19aM6CjXQuZkkmlZ # OtMqkLRg1tmhgI61yFC+jxB3AgMBAAGjggE2MIIBMjAdBgNVHQ4EFgQUH2y4fwWY # LjCFb+EOQgPz9PpaRYMwHwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ6XIw # XwYDVR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9w # cy9jcmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3Js # MGwGCCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNyb3Nv # ZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENB # JTIwMjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADATBgNVHSUEDDAKBggrBgEFBQcD # CDANBgkqhkiG9w0BAQsFAAOCAgEATxL6MfPZOhR91DHShlzal7B8vOCUvzlvbVha # 0UzhZfvIcZA/bT3XTXbQPLnIDWlRdjQX7PGkORhX/mpjZCC5J7fD3TJMn9ZQQ8MX # nJ0sx3/QJIlNgPVaOpk9Yk1YEqyItOyPHr3Om3v/q9h5f5qDk0IMV2taosze0JGl # M3M5z7bGkDly+TfhH9lI03D/FzLjjzW8EtvcqmmH68QHdTsl84NWGkd2dUlVF2aB # WMUprb8H9EhPUQcBcpf11IAj+f04yB3ncQLh+P+PSS2kxNLLeRg9CWbmsugplYP1 # D5wW+aH2mdyBlPXZMIaERJFvZUZyD8RfJ8AsE3uU3JSd408QBDaXDUf94Ki3wEXT # tl8JQItGc3ixRYWNIghraI4h3d/+266OB6d0UM2iBXSqwz8tdj+xSST6G7ZYqxat # Ezt806T1BBHe9tZ/mr2S52UjJgAVQBgCQiiiixNO27g5Qy4CDS94vT4nfC2lXwLl # hrAcNqnAJKmRqK8ehI50TTIZGONhdhGcM5xUVeHmeRy9G6ufU6B6Ob0rW6LXY2qT # LXvgt9/x/XEh1CrnuWeBWa9E307AbePaBopu8+WnXjXy6N01j/AVBq1TyKnQX1nS # MjU9gZ3EaG8oS/zNM59HK/IhnAzWeVcdBYrq/hsu9JMvBpF+ZEQY2ZWpbEJm7ELl # /CuRIPAwggdxMIIFWaADAgECAhMzAAAAFcXna54Cm0mZAAAAAAAVMA0GCSqGSIb3 # DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G # A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIw # MAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAx # MDAeFw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMyMjVaMHwxCzAJBgNVBAYTAlVT # MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1l # LVN0YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA # 5OGmTOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51yMo1V/YBf2xK4OK9uT4XYDP/ # XE/HZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY6GB9alKDRLemjkZrBxTzxXb1 # hlDcwUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9cmmvHaus9ja+NSZk2pg7uhp7 # M62AW36MEBydUv626GIl3GoPz130/o5Tz9bshVZN7928jaTjkY+yOSxRnOlwaQ3K # Ni1wjjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDuaRr3tpK56KTesy+uDRedGbsoy # 1cCGMFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74kpEeHT39IM9zfUGaRnXNxF80 # 3RKJ1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2K26oElHovwUDo9Fzpk03dJQc # NIIP8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5TI4CvEJoLhDqhFFG4tG9ahha # YQFzymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZki1ugpoMhXV8wdJGUlNi5UPkL # iWHzNgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9QBXpsxREdcu+N+VLEhReTwDwV # 2xo3xwgVGD94q0W29R6HXtqPnhZyacaue7e3PmriLq0CAwEAAaOCAd0wggHZMBIG # CSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUCBBYEFCqnUv5kxJq+gpE8RjUp # zxD/LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJlpxtTNRnpcjBcBgNVHSAEVTBT # MFEGDCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jv # c29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9yeS5odG0wEwYDVR0lBAwwCgYI # KwYBBQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGG # MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186a # GMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3Br # aS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsG # AQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29t # L3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwDQYJKoZIhvcN # AQELBQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/ypb+pcFLY+TkdkeLEGk5c9MTO1 # OdfCcTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulmZzpTTd2YurYeeNg2LpypglYA # A7AFvonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM9W0jVOR4U3UkV7ndn/OOPcbz # aN9l9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECWOKz3+SmJw7wXsFSFQrP8DJ6L # GYnn8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4FOmRsqlb30mjdAy87JGA0j3m # Sj5mO0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3UwxTSwethQ/gpY3UA8x1RtnWN0 # SCyxTkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPXfx5bRAGOWhmRaw2fpCjcZxko # JLo4S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVXVAmxaQFEfnyhYWxz/gq77EFm # PWn9y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGConsXHRWJjXD+57XQKBqJC482 # 2rpM+Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU5nR0W2rRnj7tfqAxM328y+l7 # vzhwRNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEGahC0HVUzWLOhcGbyoYIC0jCC # AjsCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5n # dG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9y # YXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNv # MSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjowQTU2LUUzMjktNEQ0RDElMCMGA1UE # AxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIaAxUA # wH7vHimSAzeDLN0qzWNb2p2vRH+ggYMwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEG # A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj # cm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFt # cCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIFAOcv2LYwIhgPMjAyMjExMjkwNTAy # NDZaGA8yMDIyMTEzMDA1MDI0NlowdzA9BgorBgEEAYRZCgQBMS8wLTAKAgUA5y/Y # tgIBADAKAgEAAgIHpAIB/zAHAgEAAgIRqTAKAgUA5zEqNgIBADA2BgorBgEEAYRZ # CgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0G # CSqGSIb3DQEBBQUAA4GBACYcGe0BNidrfaJGwKgZPbZb2QFyht+4PSBVZN54AcM8 # z05304lskdHuCqAfk/Hf/ekUWD3FWC734O6RAXOo6pA9YqNAXYKQnsh6y7rDDvry # QJ0Dp4HNR43mriPexMKFWbN52r7jWcqtDkjegcHvGrZT7cJFnkZyxvCMRcnkAY/O # MYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0 # b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh # dGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMA # AAGnNbsuwmSFUCkAAQAAAacwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJ # AzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgE5EG/R2UfL3qZGFvsTW/ # /FkwPVKTocQsN4IrWV9dRbQwgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCBH # 8H/nCZUC4L0Yqbz3sH3w5kzhwJ4RqCkXXKxNPtqOGzCBmDCBgKR+MHwxCzAJBgNV # BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w # HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29m # dCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABpzW7LsJkhVApAAEAAAGnMCIEIEvq # 43zBaAIn61Jc9RxX6MVOHGxzuBd0VsfVscWoUJroMA0GCSqGSIb3DQEBCwUABIIC # AISBZfCLoq4jdasQlaJxJZi42dYpKoi8ijf05X1qzEBOfrVLcy/OUuyYlYdU8hYu # h5so6tztvybMnHx1fZUQO8rPxP7XuNNgWsKOvkOKLtqB0I09suVJAvD/3FhGWe6M # vfxq8z+ub66m2eYyOwcBvytFCotHZOk2LuGO8DJZ1hJt2j9BP42kFqQCJMaFoBl+ # seeZPzo+v+Se43OTKgM7tskYCPk3Zz7770rJCQeUpxL4XQUAi3g8W3QWShi540xO # pk3HhvJCchJUQi+pIKHUgwXR6PTe4pU+zQQijELslHfmIhK/3ewbD3C+9ND39YKl # SiF/0+Fn7Pc8JOYgPqUNFdMAvZYluBob3WAvs3DCrjYUfBqZd9D/+AXWzOv3aCtd # 52RDPg5C984LcxKHQ8gmANUH6avqKIvouv0A2NocUVUIZbhqGsyw+D7Jf2MPXkp3 # 1SYxsk8DHAAYo8MSS1JV8Pw23N0jX7xCcXHvVEsUVD2acEq4FTMOiMJzbZ5HlhrC # GSnRq+cf5ECvmw20lYxpxvhVOpOzTdBLBMUrND8j7JB8WJOWrzCacFTvXybmYL0l # ln0XBWwnm/gf/diZmvAUZB1ENgcER4lRLyiidnTflPyPNq6/FkxdRIqtRqOx2CVH # X3FDMDmaNWc5HdF00gq4OY3dce0aPyZaya4C+3qteA1+ # SIG # End signature block |