Public/Test-NexthinkNutanixConnector.ps1
|
function Test-NexthinkNutanixConnector { <# .SYNOPSIS Checks a Nutanix connector installation and reports, for each problem, what to fix. .DESCRIPTION Runs the checks below in order and prints a PASS/WARN/FAIL/SKIP report. A check whose prerequisite failed is skipped instead of failing again. - Environment: PowerShell version, operating system, execution policy, current account. - Prerequisites: Logging and CredentialManager modules, connector module signature. - Configuration: config.json found, valid, environment present, no template placeholder, host names without scheme or port, risky options, log level, and both credential entries readable by the current account (user name only shown). - Logging: write access to the connector folder and to Logs, State, Spool and Storage, then one marker line written to today's connector log file. - Connectivity: DNS, TCP and a TLS handshake to Prism Central and Nexthink (skipped for an endpoint reached through the system proxy: the API checks test that path), then one read-only Prism Central call, a Nexthink access token, and one test entry prefixed with [diagnose] sent to the logs API. - Scheduling: on Windows, the scheduled tasks starting the Runner for this environment, their account, executable, execution policy, state and last result. Then, unless -SkipCollection is set, one real, read-only collection cycle against Prism Central in a new collection-<id> folder under OutputPath. Nothing is sent to Nexthink except the [diagnose] test entry, and nothing is changed in the connector folder except one marker line appended to today's log file. The report is also saved to a text and a JSON file. .PARAMETER NutanixEnvironment The environment name, as in nutanixEnvironments[].Name in Config/config.json. .PARAMETER ScriptRootPath The connector folder, the one holding Config/config.json and the Runner. Defaults to the current folder. .PARAMETER SkipCollection Skips the collection test. By default the checks end with one collection cycle against Prism Central with every connector folder redirected to a collection-<id> folder created under OutputPath: the connector's state, spool and VM cache are not read or written, even when OutputPath is the connector folder, and the payloads are written to files instead of being sent. .PARAMETER OutputPath Where the report, the collection test's DEBUG log and the payload files are written. Defaults to a new folder under the temporary directory. .PARAMETER PassThru Returns the result records instead of the exit code. .OUTPUTS 0 when no check failed, otherwise 1. With -PassThru, the result records. .EXAMPLE Test-NexthinkNutanixConnector -NutanixEnvironment 'prod' -ScriptRootPath 'C:\NutanixConnector' .EXAMPLE Test-NexthinkNutanixConnector -NutanixEnvironment 'prod' -ScriptRootPath 'C:\NutanixConnector' -SkipCollection -Verbose -Verbose also prints the connector's DEBUG log lines while the checks run. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$NutanixEnvironment, [Parameter(Mandatory = $false)] [string]$ScriptRootPath = (Get-Location).Path, [Parameter(Mandatory = $false)] [switch]$SkipCollection, [Parameter(Mandatory = $false)] [string]$OutputPath, [Parameter(Mandatory = $false)] [switch]$PassThru ) $version = Get-ModuleVersion $identity = Get-DiagnosticCurrentIdentity $onWindows = (Get-OperatingSystemType) -eq 'Windows' $consoleLogLevel = if ($VerbosePreference -eq 'Continue') { 'DEBUG' } else { 'OFF' } $previousConsoleLogLevel = $script:CONSOLE_LOG_LEVEL $results = [System.Collections.Generic.List[object]]::new() $ScriptRootPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($ScriptRootPath) if ([string]::IsNullOrWhiteSpace($OutputPath)) { $safeEnvironment = $NutanixEnvironment -replace '[^A-Za-z0-9._-]', '_' $OutputPath = Join-Path -Path ([System.IO.Path]::GetTempPath()) ` -ChildPath "nutanix-connector-diagnose-$safeEnvironment-$((Get-Date).ToString('yyyyMMdd-HHmmss'))" } $OutputPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($OutputPath) try { $script:IN_MEMORY_LOGS.Clear() Set-Variable -Name 'CONSOLE_LOG_LEVEL' -Value $consoleLogLevel -Scope Script -Force # # Environment and prerequisites # Add-DiagnosticResult -Results $results -Result (New-DiagnosticResult -Category 'Environment' -Check 'Connector module' ` -Status 'INFO' -Detail "Nexthink-Nutanix-Connector $version from $script:ModuleRoot") Add-DiagnosticResult -Results $results -Result (Test-DiagnosticPowerShellVersion) Add-DiagnosticResult -Results $results -Result (Test-DiagnosticOperatingSystem) Add-DiagnosticResult -Results $results -Result (Test-DiagnosticExecutionPolicy ` -RunnerPath (Join-Path -Path $ScriptRootPath -ChildPath $script:DIAGNOSTIC_RUNNER_SCRIPT_NAME)) Add-DiagnosticResult -Results $results -Result (New-DiagnosticResult -Category 'Environment' -Check 'Current account' ` -Status 'INFO' -Detail $identity ` -Hint 'Credential entries belong to one account: run this as the account the scheduled task uses.') Add-DiagnosticResult -Results $results -Result (New-DiagnosticResult -Category 'Environment' -Check 'Connector folder' ` -Status 'INFO' -Detail $ScriptRootPath) $loggingModule = Test-DiagnosticModuleInstalled -Name 'Logging' Add-DiagnosticResult -Results $results -Result $loggingModule if ($onWindows) { Add-DiagnosticResult -Results $results -Result (Test-DiagnosticModuleInstalled -Name 'CredentialManager') } Add-DiagnosticResult -Results $results -Result (Test-DiagnosticModuleSignature -ModuleRoot $script:ModuleRoot) # # Configuration # $config = $null $configPath = Get-DiagnosticConfigurationPath -ScriptRootPath $ScriptRootPath if ($null -eq $configPath) { Add-DiagnosticResult -Results $results -Result (New-DiagnosticResult -Category 'Configuration' -Check 'Configuration file' ` -Status 'FAIL' -Detail "No Config/config.json under $ScriptRootPath." ` -Hint 'Run this from the connector folder, or pass -ScriptRootPath <connector folder>.') } else { Add-DiagnosticResult -Results $results -Result (New-DiagnosticResult -Category 'Configuration' -Check 'Configuration file' ` -Status 'PASS' -Detail $configPath) try { # Stop-ConfigError writes to the error stream before throwing; the report carries the message. $config = Get-Configuration -ScriptRootPath $ScriptRootPath -NutanixEnvironment $NutanixEnvironment 2>$null Add-DiagnosticResult -Results $results -Result (New-DiagnosticResult -Category 'Configuration' -Check 'Configuration valid' ` -Status 'PASS' -Detail "Environment '$NutanixEnvironment' found and valid.") } catch { $hint = 'Fix the value named in the message in Config/config.json.' $environmentNames = @(Get-DiagnosticEnvironmentNames -ConfigurationPath $configPath) if ($_.Exception.Message -like '*not found in configuration*' -and $environmentNames.Count -gt 0) { $hint = "Environments in the file: $($environmentNames -join ', '). Pass one of them as -NutanixEnvironment." } Add-DiagnosticResult -Results $results -Result (New-DiagnosticResult -Category 'Configuration' -Check 'Configuration valid' ` -Status 'FAIL' -Detail $_.Exception.Message -Hint $hint) } } if ($null -eq $config) { Add-DiagnosticResult -Results $results -Result (New-DiagnosticResult -Category 'Configuration' -Check 'Remaining checks' ` -Status 'SKIP' -Detail 'Credentials, logging and folders, connectivity, API access, scheduling and the collection test need a valid configuration.') } else { Add-DiagnosticResult -Results $results -Result (Test-DiagnosticConfigurationValues -Config $config) $environmentName = $config.EnvironmentConfig.Name # Credentials are local reads of the entries named in the configuration, so they are part of it. $prismCredential = Test-DiagnosticCredential -Check 'Prism Central credential' -Target $config.EnvironmentConfig.WindowsCredentialEntry $nexthinkCredential = Test-DiagnosticCredential -Check 'Nexthink API credential' -Target $config.NexthinkAPI.WindowsCredentialEntry Add-DiagnosticResult -Results $results -Result $prismCredential Add-DiagnosticResult -Results $results -Result $nexthinkCredential # # Logging and folders: the log file goes into the Logs folder, so the folders come first. # Add-DiagnosticResult -Results $results -Result (Test-DiagnosticFolderAccess -Path $ScriptRootPath -Check 'Connector folder') $folderResults = @{} foreach ($folder in $script:DIAGNOSTIC_DATA_FOLDERS) { $folderResults[$folder] = Test-DiagnosticFolderAccess -Check $folder ` -Path (Join-Path -Path $ScriptRootPath -ChildPath (Join-Path -Path $folder -ChildPath $environmentName)) Add-DiagnosticResult -Results $results -Result $folderResults[$folder] } if ($loggingModule.Status -eq 'PASS' -and $folderResults['Logs'].Status -eq 'PASS') { Add-DiagnosticResult -Results $results -Result (Test-DiagnosticLocalLogging -Config $config -ConsoleLogLevel $consoleLogLevel ` -Message "$($script:DIAGNOSTIC_LOG_MARKER) Diagnostic run by Test-NexthinkNutanixConnector $version as $identity") } else { Add-DiagnosticResult -Results $results -Result (New-DiagnosticResult -Category 'Logging' -Check 'Local log file' ` -Status 'SKIP' -Detail 'Needs the Logging module and a writable Logs folder.') } # # Connectivity and API access: DNS, TCP and TLS first, no credential needed. Behind a proxy the # probe is INFO and the API checks still run: they go through the proxy like the connector. # $prismNetwork = Test-DiagnosticEndpoint -Check 'Prism Central network' ` -HostName $config.EnvironmentConfig.PrismCentralFQDN -Port $config.EnvironmentConfig.PrismCentralPort ` -TlsHint 'PrismCentralPort must be the Prism Central HTTPS port (9440 by default). Also check that no firewall or proxy intercepts the connection.' $loginNetwork = Test-DiagnosticEndpoint -Check 'Nexthink login network' -HostName $config.NexthinkAPI.LoginFQDN -Port 443 $apiNetwork = $loginNetwork Add-DiagnosticResult -Results $results -Result $prismNetwork Add-DiagnosticResult -Results $results -Result $loginNetwork if ($config.NexthinkAPI.HostFQDN -ne $config.NexthinkAPI.LoginFQDN) { $apiNetwork = Test-DiagnosticEndpoint -Check 'Nexthink API network' -HostName $config.NexthinkAPI.HostFQDN -Port 443 Add-DiagnosticResult -Results $results -Result $apiNetwork } # Authenticated calls, needing both the network and the credential $prismApi = $null if ($prismCredential.Status -eq 'PASS' -and $prismNetwork.Status -ne 'FAIL') { $prismApi = Test-DiagnosticPrismCentralApi -EnvironmentConfig $config.EnvironmentConfig Add-DiagnosticResult -Results $results -Result $prismApi } else { Add-DiagnosticResult -Results $results -Result (New-DiagnosticResult -Category 'Connectivity' -Check 'Prism Central API' ` -Status 'SKIP' -Detail 'Needs the Prism Central credential and network checks to pass.') } $token = $null if ($nexthinkCredential.Status -eq 'PASS' -and $loginNetwork.Status -ne 'FAIL') { $token = Test-DiagnosticNexthinkToken -NexthinkApi $config.NexthinkAPI Add-DiagnosticResult -Results $results -Result $token } else { Add-DiagnosticResult -Results $results -Result (New-DiagnosticResult -Category 'Connectivity' -Check 'Nexthink access token' ` -Status 'SKIP' -Detail 'Needs the Nexthink API credential and login network checks to pass.') } if ($null -ne $token -and $token.Status -eq 'PASS' -and $apiNetwork.Status -ne 'FAIL') { Add-DiagnosticResult -Results $results -Result (Test-DiagnosticLogsApi -Config $config -Version $version) } else { Add-DiagnosticResult -Results $results -Result (New-DiagnosticResult -Category 'Connectivity' -Check 'Nexthink logs API' ` -Status 'SKIP' -Detail 'Needs an access token and the Nexthink API network check to pass.') } # # Scheduling and collection test # Add-DiagnosticResult -Results $results -Result (Test-DiagnosticScheduledTask -NutanixEnvironment $environmentName -CurrentIdentity $identity) if ($SkipCollection) { Add-DiagnosticResult -Results $results -Result (New-DiagnosticResult -Category 'Scheduling' -Check 'Collection cycle' ` -Status 'SKIP' -Detail 'Skipped with -SkipCollection.') } elseif ($null -eq $prismApi -or $prismApi.Status -ne 'PASS') { Add-DiagnosticResult -Results $results -Result (New-DiagnosticResult -Category 'Scheduling' -Check 'Collection cycle' ` -Status 'SKIP' -Detail 'Needs the Prism Central API check to pass.') } else { New-Item -Path $OutputPath -ItemType Directory -Force -ErrorAction Stop | Out-Null Add-DiagnosticResult -Results $results -Result (Invoke-DiagnosticCollection -Config $config -OutputPath $OutputPath -ConsoleLogLevel $consoleLogLevel) } } } catch { Add-DiagnosticResult -Results $results -Result (New-DiagnosticResult -Category 'Diagnostics' -Check 'Unexpected error' ` -Status 'FAIL' -Detail (Get-DiagnosticErrorMessage -ErrorRecord $_) ` -Hint 'Run again with -Verbose and share the report with Nexthink support.') } finally { # Nothing collected while diagnosing may be shipped by a later connector run in this session. $script:IN_MEMORY_LOGS.Clear() Wait-LoggingFlush Set-Variable -Name 'CONSOLE_LOG_LEVEL' -Value $previousConsoleLogLevel -Scope Script -Force } $title = "Nexthink Nutanix connector diagnostics - environment '$NutanixEnvironment', module $version, $((Get-Date).ToString('u'))" $lines = [System.Collections.Generic.List[object]]::new() foreach ($line in (ConvertTo-DiagnosticReportLines -Results $results.ToArray() -Title $title)) { $lines.Add($line) } try { New-Item -Path $OutputPath -ItemType Directory -Force -ErrorAction Stop | Out-Null $reportFile = Join-Path -Path $OutputPath -ChildPath 'diagnostics-report.txt' Set-Content -LiteralPath $reportFile -Value @($lines | ForEach-Object { $_.Text }) -Encoding utf8 -ErrorAction Stop $results | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath (Join-Path -Path $OutputPath -ChildPath 'diagnostics-report.json') -Encoding utf8 -ErrorAction Stop $lines.Add([pscustomobject]@{ Text = "Report saved to $reportFile"; Colour = 'White' }) } catch { $lines.Add([pscustomobject]@{ Text = "Could not save the report to $OutputPath. $(Get-DiagnosticErrorMessage -ErrorRecord $_)"; Colour = 'Yellow' }) } Write-DiagnosticReport -Lines $lines.ToArray() if ($PassThru) { return $results.ToArray() } if (@($results | Where-Object { $_.Status -eq 'FAIL' }).Count -gt 0) { return 1 } return 0 } # SIG # Begin signature block # MIIohQYJKoZIhvcNAQcCoIIodjCCKHICAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBvYxHcGHTSJdtp # 1mjT8eX4pldV5eFGp9mhDyIGJ1nDdqCCIZ0wggWNMIIEdaADAgECAhAOmxiO+dAt # 5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK # EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV # BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBa # Fw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy # dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD # ZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC # ggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3E # MB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKy # unWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsF # xl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU1 # 5zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJB # MtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObUR # WBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6 # nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxB # YKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5S # UUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+x # q4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIB # NjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwP # TzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMC # AYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp # Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv # bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0 # aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB # LmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0Nc # Vec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnov # Lbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65Zy # oUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFW # juyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPF # mCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9z # twGpn1eqXijiuZQwggawMIIEmKADAgECAhAIrUCyYNKcTJ9ezam9k67ZMA0GCSqG # SIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx # GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRy # dXN0ZWQgUm9vdCBHNDAeFw0yMTA0MjkwMDAwMDBaFw0zNjA0MjgyMzU5NTlaMGkx # CzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4 # RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEzODQg # MjAyMSBDQTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDVtC9C0Cit # eLdd1TlZG7GIQvUzjOs9gZdwxbvEhSYwn6SOaNhc9es0JAfhS0/TeEP0F9ce2vnS # 1WcaUk8OoVf8iJnBkcyBAz5NcCRks43iCH00fUyAVxJrQ5qZ8sU7H/Lvy0daE6ZM # swEgJfMQ04uy+wjwiuCdCcBlp/qYgEk1hz1RGeiQIXhFLqGfLOEYwhrMxe6TSXBC # Mo/7xuoc82VokaJNTIIRSFJo3hC9FFdd6BgTZcV/sk+FLEikVoQ11vkunKoAFdE3 # /hoGlMJ8yOobMubKwvSnowMOdKWvObarYBLj6Na59zHh3K3kGKDYwSNHR7OhD26j # q22YBoMbt2pnLdK9RBqSEIGPsDsJ18ebMlrC/2pgVItJwZPt4bRc4G/rJvmM1bL5 # OBDm6s6R9b7T+2+TYTRcvJNFKIM2KmYoX7BzzosmJQayg9Rc9hUZTO1i4F4z8ujo # 7AqnsAMrkbI2eb73rQgedaZlzLvjSFDzd5Ea/ttQokbIYViY9XwCFjyDKK05huzU # tw1T0PhH5nUwjewwk3YUpltLXXRhTT8SkXbev1jLchApQfDVxW0mdmgRQRNYmtwm # KwH0iU1Z23jPgUo+QEdfyYFQc4UQIyFZYIpkVMHMIRroOBl8ZhzNeDhFMJlP/2NP # TLuqDQhTQXxYPUez+rbsjDIJAsxsPAxWEQIDAQABo4IBWTCCAVUwEgYDVR0TAQH/ # BAgwBgEB/wIBADAdBgNVHQ4EFgQUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHwYDVR0j # BBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1Ud # JQQMMAoGCCsGAQUFBwMDMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0 # cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0 # cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8E # PDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVz # dGVkUm9vdEc0LmNybDAcBgNVHSAEFTATMAcGBWeBDAEDMAgGBmeBDAEEATANBgkq # hkiG9w0BAQwFAAOCAgEAOiNEPY0Idu6PvDqZ01bgAhql+Eg08yy25nRm95RysQDK # r2wwJxMSnpBEn0v9nqN8JtU3vDpdSG2V1T9J9Ce7FoFFUP2cvbaF4HZ+N3HLIvda # qpDP9ZNq4+sg0dVQeYiaiorBtr2hSBh+3NiAGhEZGM1hmYFW9snjdufE5BtfQ/g+ # lP92OT2e1JnPSt0o618moZVYSNUa/tcnP/2Q0XaG3RywYFzzDaju4ImhvTnhOE7a # brs2nfvlIVNaw8rpavGiPttDuDPITzgUkpn13c5UbdldAhQfQDN8A+KVssIhdXNS # y0bYxDQcoqVLjc1vdjcshT8azibpGL6QB7BDf5WIIIJw8MzK7/0pNVwfiThV9zeK # iwmhywvpMRr/LhlcOXHhvpynCgbWJme3kuZOX956rEnPLqR0kq3bPKSchh/jwVYb # KyP/j7XqiHtwa+aguv06P0WmxOgWkVKLQcBIhEuWTatEQOON8BUozu3xGFYHKi8Q # xAwIZDwzj64ojDzLj4gLDb879M4ee47vtevLt/B3E+bnKD+sEq6lLyJsQfmCXBVm # zGwOysWGw/YmMwwHS6DTBwJqakAwSEs0qFEgu60bhQjiWQ1tygVQK+pKHJ6l/aCn # HwZ05/LWUpD9r4VIIflXO7ScA+2GRfS0YW6/aOImYIbqyK+p/pQd52MbOoZWeE4w # gga0MIIEnKADAgECAhANx6xXBf8hmS5AQyIMOkmGMA0GCSqGSIb3DQEBCwUAMGIx # CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 # dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH # NDAeFw0yNTA1MDcwMDAwMDBaFw0zODAxMTQyMzU5NTlaMGkxCzAJBgNVBAYTAlVT # MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1 # c3RlZCBHNCBUaW1lU3RhbXBpbmcgUlNBNDA5NiBTSEEyNTYgMjAyNSBDQTEwggIi # MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC0eDHTCphBcr48RsAcrHXbo0Zo # dLRRF51NrY0NlLWZloMsVO1DahGPNRcybEKq+RuwOnPhof6pvF4uGjwjqNjfEvUi # 6wuim5bap+0lgloM2zX4kftn5B1IpYzTqpyFQ/4Bt0mAxAHeHYNnQxqXmRinvuNg # xVBdJkf77S2uPoCj7GH8BLuxBG5AvftBdsOECS1UkxBvMgEdgkFiDNYiOTx4OtiF # cMSkqTtF2hfQz3zQSku2Ws3IfDReb6e3mmdglTcaarps0wjUjsZvkgFkriK9tUKJ # m/s80FiocSk1VYLZlDwFt+cVFBURJg6zMUjZa/zbCclF83bRVFLeGkuAhHiGPMvS # GmhgaTzVyhYn4p0+8y9oHRaQT/aofEnS5xLrfxnGpTXiUOeSLsJygoLPp66bkDX1 # ZlAeSpQl92QOMeRxykvq6gbylsXQskBBBnGy3tW/AMOMCZIVNSaz7BX8VtYGqLt9 # MmeOreGPRdtBx3yGOP+rx3rKWDEJlIqLXvJWnY0v5ydPpOjL6s36czwzsucuoKs7 # Yk/ehb//Wx+5kMqIMRvUBDx6z1ev+7psNOdgJMoiwOrUG2ZdSoQbU2rMkpLiQ6bG # RinZbI4OLu9BMIFm1UUl9VnePs6BaaeEWvjJSjNm2qA+sdFUeEY0qVjPKOWug/G6 # X5uAiynM7Bu2ayBjUwIDAQABo4IBXTCCAVkwEgYDVR0TAQH/BAgwBgEB/wIBADAd # BgNVHQ4EFgQU729TSunkBnx6yuKQVvYv1Ensy04wHwYDVR0jBBgwFoAU7NfjgtJx # XWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUF # BwMIMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGln # aWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5j # b20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8EPDA6MDigNqA0hjJo # dHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNy # bDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwDQYJKoZIhvcNAQEL # BQADggIBABfO+xaAHP4HPRF2cTC9vgvItTSmf83Qh8WIGjB/T8ObXAZz8OjuhUxj # aaFdleMM0lBryPTQM2qEJPe36zwbSI/mS83afsl3YTj+IQhQE7jU/kXjjytJgnn0 # hvrV6hqWGd3rLAUt6vJy9lMDPjTLxLgXf9r5nWMQwr8Myb9rEVKChHyfpzee5kH0 # F8HABBgr0UdqirZ7bowe9Vj2AIMD8liyrukZ2iA/wdG2th9y1IsA0QF8dTXqvcnT # mpfeQh35k5zOCPmSNq1UH410ANVko43+Cdmu4y81hjajV/gxdEkMx1NKU4uHQcKf # ZxAvBAKqMVuqte69M9J6A47OvgRaPs+2ykgcGV00TYr2Lr3ty9qIijanrUR3anzE # wlvzZiiyfTPjLbnFRsjsYg39OlV8cipDoq7+qNNjqFzeGxcytL5TTLL4ZaoBdqbh # OhZ3ZRDUphPvSRmMThi0vw9vODRzW6AxnJll38F0cuJG7uEBYTptMSbhdhGQDpOX # gpIUsWTjd6xpR6oaQf/DJbg3s6KCLPAlZ66RzIg9sC+NJpud/v4+7RWsWCiKi9EO # LLHfMR2ZyJ/+xhCx9yHbxtl5TPau1j/1MIDpMPx0LckTetiSuEtQvLsNz3Qbp7wG # WqbIiOWCnb5WqxL3/BAPvIXKUjPSxyZsq8WhbaM2tszWkPZPubdcMIIG7TCCBNWg # AwIBAgIQCE/cM09+RU7bww+P+ZIYNTANBgkqhkiG9w0BAQsFADBpMQswCQYDVQQG # EwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0 # IFRydXN0ZWQgRzQgVGltZVN0YW1waW5nIFJTQTQwOTYgU0hBMjU2IDIwMjUgQ0Ex # MB4XDTI2MDgwNTAwMDAwMFoXDTM3MTEwNDIzNTk1OVowYzELMAkGA1UEBhMCVVMx # FzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2VydCBTSEEy # NTYgUlNBNDA5NiBUaW1lc3RhbXAgUmVzcG9uZGVyIDIwMjYgMTCCAiIwDQYJKoZI # hvcNAQEBBQADggIPADCCAgoCggIBALZ7pvLJ/s1K+NSbTGWz/TjGMPh8CQ6RucZC # Lv5anHzWJjF/NWJrFIhy24fcpKXlgRiky4WAawDfU3YP0BMxt9l3Dm5oCG5Z69Aq # EN1kgHg2epx+l+lZBcmJCcN0ASURML5uFIS80sZsDwO3BSkUxDjLJhBI+qiZP3ai # xAC/qEGLjsBNlLol9VZ7pfGEXiMlneJIC5/YKuizVzNFKZZEeoy/0B8Zm+nzKBgS # WG52lCO1w+nCg6XpCtklTJXeIg283hw7TmmsZXR+SMbjbrEOvZ3fP2VxIgeR28Y9 # 0ZStd3F9VuA5RVynb/whITPAo9b75Zr4Ta6Mj3URm26QZYMn/FnbuTegcoRcFEZ9 # FOqM5T6MTdtr/n74lIT/ug0eeOzmZ6QTFg33otX+bFRsIolvykE1jive4PuESaT8 # zzVeFWDAMDtozNgLctkGD1ZjkEyZtJrLl5ya0m5doH/ScpaZCZVl6pNUOCybMc/k # xC6EAmSJY24L0yYKD1Nkddsnb/ItVKi/2nXpQNMu1PT5prW83vV8d67WowuUs0Hd # Y4H8AMLGvdL/WHEj3ZnqMqAQQP9u3Ai9t+5eQ02GDwy0ODjdzi0xlp70W+ow63/0 # ++YDEX1M0iwgUHwbrJvfpklkZQvw3+kv3vUPItdwroczk9icflf55W1zOEKAcJVA # IXpcMCU9AgMBAAGjggGVMIIBkTAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBQUyWOK # MC7USvtulPPm40B+9ezN4jAfBgNVHSMEGDAWgBTvb1NK6eQGfHrK4pBW9i/USezL # TjAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwgZUGCCsG # AQUFBwEBBIGIMIGFMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5j # b20wXQYIKwYBBQUHMAKGUWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdp # Q2VydFRydXN0ZWRHNFRpbWVTdGFtcGluZ1JTQTQwOTZTSEEyNTYyMDI1Q0ExLmNy # dDBfBgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGln # aUNlcnRUcnVzdGVkRzRUaW1lU3RhbXBpbmdSU0E0MDk2U0hBMjU2MjAyNUNBMS5j # cmwwIAYDVR0gBBkwFzAIBgZngQwBBAIwCwYJYIZIAYb9bAcBMA0GCSqGSIb3DQEB # CwUAA4ICAQCNxTphHp1SCt+ZrAmAfn0oQLFr0mLywSLaDXQIENoyKqxrFbJblzCV # P/pkXmwXOdrOpWygLzlT12os5ipDCy35RBCg2UMeApEtrfGhz45F4Wt4WGdNdIbR # Wt3YTYJmpR+b7lr4d7Uwn+H600u4D7RnOGf8Wj4UNgAdZkfHhHv1mx9EVh71SJel # cEN/oORSjXzdjfw1iZH9d8Nh/thn6hH23d+VsPAr6GAYyzSA02nXD1nYLI7Ijmiv # +xLCiYC41DSFYL3GhTiy0PxpawPtGRyaBVGzq+UiTfM8pD7KVyF5aQyWP4KhVGUU # Tnmm/RlYJoW3TiXA/+t0YcT2oRVBm3JETjajHug2AL+v5jhtKVnd3D0rbHXEu27o # +Q8p4sEWPMqKDB+qbceb6T/6WcwTwXmQ9lOCLLYcsQeSWmvKqzpAec9etE14jOQA # zLKWdE3w/TCaKtLRaRT7LCkRYVnhA2D73FLje1O5b3HR5eHs0NzU/+xX7NbEdcof # y0W3Wdwd1XOqtlpg/JgwtKfZM5dqO94lbUveOiJBI+xZEbGRsMNbXmMREUTgu+Oc # a7Y73MPWcslIx2VhkSKSXjDbD6rgg39H5Mh7QfieAIjWagkJNt68Yfim6cjEzVSi # LSeZfdkr5dtFPTW6jATlWJdYeeDRGCyatf8R1hSjzSvdN8yWQPT9gzCCB6swggWT # oAMCAQICEA20PGu9jXp4I/Dy4yL8Si8wDQYJKoZIhvcNAQELBQAwaTELMAkGA1UE # BhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYDVQQDEzhEaWdpQ2Vy # dCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBSU0E0MDk2IFNIQTM4NCAyMDIxIENB # MTAeFw0yNTExMTMwMDAwMDBaFw0yODExMTQyMzU5NTlaMIGzMRMwEQYLKwYBBAGC # NzwCAQMTAkNIMRUwEwYLKwYBBAGCNzwCAQITBFZhdWQxHTAbBgNVBA8MFFByaXZh # dGUgT3JnYW5pemF0aW9uMRgwFgYDVQQFEw9DSEUtMTEyLjAwMC41NzkxCzAJBgNV # BAYTAkNIMQ8wDQYDVQQHEwZQcmlsbHkxFjAUBgNVBAoTDU5FWFRoaW5rIFMuQS4x # FjAUBgNVBAMTDU5FWFRoaW5rIFMuQS4wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw # ggIKAoICAQDIbNJpFq6aDbvQP7zFPymmt3WRPkvbmnRL4g0imzFNbaj+U/RIGPL0 # 3l9TtYxzraPbwDdidmT5P50mK1JqjF5FzcZEC4kZfMMojY4J/gOylo8jOdLjBscL # SYwsY+85ZRs2bvxibPXIu4AcZttxBx5XdbVyt2Bd1vtuJGj+wfU1vJu7NB3GHjPy # 4QE1BFrgidqQTL+1HASQaWwNMetW8B1U1POZjr6uWp6Ga3DF/MAKxpbvU3iVFI8b # YFpKiVIQ9Namj3DQ7Xj26+fSEP959+bukG8LyObHN5DDkRdpnCSVVMT6qxZ2QGri # DFkyjOVaGkjJTI6mbKY1JPUJhYVo+7Jqhu+2EFGRy6wSj9wZBubnbwfuvgr/XlH9 # /xQQuX9i/EJvxJRQQyz9IzLtB/DBSoEQvtyCh8Po87k9x9CGYn7k1HxUg1F6oBKa # kemv3G474RWKZwSEIbwUlygFydEpTDkRTqBDZKOQf3VaUvfUgGmBmEfq1m+cljHj # fLDBtkDIGaZ7SvyO72q9Uj0HrIWvJDHHIYK84gqhe58vKgaQ0uH9V5BCUT5cLvau # +1w93iq5AMCTtG96SEyjI/T9Jzh+2sM+PoUwlLNLqYFUvMI+l19X+5UtQnjU5k1c # ooxqCebatsS642gd3X1VoeNyfJ5FB67b6KGNfrDOXBt5FXgKZAUOHwIDAQABo4IC # AjCCAf4wHwYDVR0jBBgwFoAUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHQYDVR0OBBYE # FICGOh06CZAfMG3M4l2zy9590p0OMD0GA1UdIAQ2MDQwMgYFZ4EMAQMwKTAnBggr # BgEFBQcCARYbaHR0cDovL3d3dy5kaWdpY2VydC5jb20vQ1BTMA4GA1UdDwEB/wQE # AwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzCBtQYDVR0fBIGtMIGqMFOgUaBPhk1o # dHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2ln # bmluZ1JTQTQwOTZTSEEzODQyMDIxQ0ExLmNybDBToFGgT4ZNaHR0cDovL2NybDQu # ZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25pbmdSU0E0MDk2 # U0hBMzg0MjAyMUNBMS5jcmwwgZQGCCsGAQUFBwEBBIGHMIGEMCQGCCsGAQUFBzAB # hhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wXAYIKwYBBQUHMAKGUGh0dHA6Ly9j # YWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNENvZGVTaWduaW5n # UlNBNDA5NlNIQTM4NDIwMjFDQTEuY3J0MAkGA1UdEwQCMAAwDQYJKoZIhvcNAQEL # BQADggIBALSLCU45H6xjr2Ce9mmr6WtqdnykOcc2s1iqd/UN88f4GlmzVmtA9Dts # 9j8X77hxHYIAJwWM3NqKi8m55CB5HQBuOC3hOI8bWFdQLBsrAbBAApFh3cjRruGn # r0mu7wlZo9HLgpM1PEy+hf90+cANYRBITrRkfVT4o2rTc8mmJyYzZM8WVwxGx+wm # PAuvyjYKsjZNTeJ+miYqwJ/zHjc8LQP+/hYPNmP9ubYpMMkgC49vP9Uir0E4YI2C # VTbUuhxao4Pmy2MnqhK20lDu50ZCNDNFurYhzXWBhK1cel6Ku4yuT6TlsjNye0AY # v92FDyAaYH9VZuub7qZBNv/RwmcLUDqqh5rnEdeGMH7KFkUyN63RMEr7hLXV/Tav # 7K0kWlXKPLIFenfqJnPYceni6PdJS/7WlxnBE0UwgoSoe4HawnEF0YvYBU3uyK+v # TJWVau5Jptjp94uEQmW6PidWwUWNzVl4LQ7eEZe4ykZ0JLErbGnOeoUoZCeVzpx4 # uUEFiZv1KLeLEkB/8yFIrQp03sVMItqhNJ5mbDOoWW4y3Z8/jduhxVPwJMHhgglK # 3z3sZr5xrpY3n1Qcqn1xFsI6U7rOrkzyQBw+d/yF82MW3LldZ7tPyU8LTEO89oOC # EsGFwOfEb/wB7UJ1NulZ1R2nf5erYEnVsOsrGV0X1fYh2OoRQyxWMYIGPjCCBjoC # AQEwfTBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/ # BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBTaWduaW5nIFJTQTQwOTYg # U0hBMzg0IDIwMjEgQ0ExAhANtDxrvY16eCPw8uMi/EovMA0GCWCGSAFlAwQCAQUA # oGowGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwG # CisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIExwmiKEzCgD7gllNAO03u/QiaEZ # fdHBkoQWEh7kkcgeMA0GCSqGSIb3DQEBAQUABIICAH+KqAbi5DV8I2yAveUwlesX # 4hpfjy+TSustZ3qQXtwdet14MGG2Y9iXUJoXT7d+JXR3SASTMQYhGvforfP4XT8k # P7zmSDBHbTWwM/4WZj7MeawZ62UhAEyDgjDcIJzz4sINHj9b/+Y2pSxmpX+k6Ce9 # bDqXuydTamMNQ3Foo7lLWmp7F3wGjLlL1zj4hFc3waIohAF6/UsZXdovvbdvVo2n # wQI5yZFYupbLW6acswMF0KpWuuw5vJgsKoLh+gE3q9jwnIlngpyLES9SFTWxcjt3 # aMF4ShONuRvXdoNQF80MUqLucPJNbLoUHXNNqMPHjkoFlCB1BXtoOWsUO4XIg4/P # FylTEofoVYU668g9Ay1LJQxdtYbvhknihoGtEJOGfoTvBO+EEm9r6/GCjjyE4GJq # qd4ugppa016YhBRHHBUnAesEqgtrF0Cc4th/9rcaR4qSiPKUywjNdfjw7NekYK65 # di7FTt+bE9SoM9mgR3SMjPwZv5U0mc6W9A5HvFSXGU2Rc4yyFSI38B/BaxkCorif # S6izbFDZhqfgXavJxUlxo6h4d+KickoWXV/OyQjZ2jf237dGl6yzyw+7NGPPbqIO # myK9eaGjB7X2tPCAZL3y4MFZJyuxKh+vpS8wjyVu6EPLl+Ya1E8Eyp6auVIMBXVR # c0nO2/29sawljC2kk28XoYIDJjCCAyIGCSqGSIb3DQEJBjGCAxMwggMPAgEBMH0w # aTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYDVQQD # EzhEaWdpQ2VydCBUcnVzdGVkIEc0IFRpbWVTdGFtcGluZyBSU0E0MDk2IFNIQTI1 # NiAyMDI1IENBMQIQCE/cM09+RU7bww+P+ZIYNTANBglghkgBZQMEAgEFAKBpMBgG # CSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTI2MTAyMTA4 # NDUzNVowLwYJKoZIhvcNAQkEMSIEILRkRKhPL/wjFlim7xdWNHJ00R48FC7gzvK6 # detcdbD3MA0GCSqGSIb3DQEBAQUABIICAHiBSMEZa8mHf5OXrwAqNaAj0UVQLCTE # Q3j64j9faB3vQXm0MHocD+fXS/ojlVWJ92/TupFXzVkaOGkxcPv0Q23Xp5AjtsGZ # WvR3Rokxp+tF9VDXmQnRbfCS+4ZWTcIYdzOS2bPM9IFnLj8I6WeECXWjt+JeIqmP # f/dQg87geRxyiVIxR10MVhFV+7L10kxgIBwHi87ujXbSDakyIcamur5Uuoxp+ftA # VefoI5+6n2vaBgFZPmjpaMPYBmY4weuKgDxLZ46hCKsszwcKe1lczCrdwWjvuL6m # wX0D+wy2LEm9FuIDgejjB0ToXtgCngkxelX3jf+/ERMd2o9DRppfKh62IJ6e8k0t # OqzW2ygC30LkptpG5q7asOdz3Vk23OwHl/PcqNh6DZx71dpYUEVRinrF3o7k/P/O # dgdcRodhR/cgkZrTh4n4H7rFn4AESRDLoMZhjGVaFo+fw9MXFVX6WA7yTeKaESZx # 5gVMsH93bYdCJVZUULMhJ/CWN5diO6eY3dVjL09IYQGU06sa6JxwVpOv5JvvrJFh # TmVCdypAP4odVkKBdPx8yhZdQuT4BttXdVF5Ibn51d+asTwcmGUJ9KvXjNM3X0Rn # DWagMqycgqaHsNqlF1Y45I7pDyarjFWNJvOsBzsAFvkZ+j6lppqX/Fgtl5WAGnQx # mSkBFrSKss7o # SIG # End signature block |