DiagnosticServerCLI.ps1
<#PSScriptInfo .VERSION 0.0.4 .GUID 01e1b65a-32a9-4937-beed-1cb1cf74344c .AUTHOR Quest Software inc. .COMPANYNAME Quest Software inc. .COPYRIGHT Copyright 2019 Quest Software and/or its affiliates and other contributors as indicated by the @author tags. Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. .TAGS Spotlight DiagnosticServer PSModule Core Desktop CLI Cmdlet Function .LICENSEURI .PROJECTURI https://www.quest.com/products/spotlight-on-sql-server-enterprise/ .ICONURI .EXTERNALMODULEDEPENDENCIES .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES Add Get-Connection cmdlet .PRIVATEDATA #> #Requires -Module .\Module\DiagnosticServerCLI.psm1 <# .DESCRIPTION The Diagnostic Server CLI module #> Param() <# * Copyright 2019 Quest Software and/or its affiliates * and other contributors as indicated by the @author tags. * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. #> ################# Define Global Variables ######################### $Script:DiagnosticServerWebProtocol = "https" $Script:DiagnosticServerPort = 40403 ################################################################### ################# Initialization ######################### # Allow TLS, TLS1.0, TLS1.1 and TLS1.2 [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls -bor [System.Net.SecurityProtocolType]::Tls11 -bor [System.Net.SecurityProtocolType]::Tls12 if(($PSVersionTable.PSEdition -eq "Core") -or ($PSVersionTable.PSVersion.CompareTo([version]"6.0.0") -ge 0)) { # Do nothing here Write-Debug "PowerShell ${PSVersionTable.PSEdition} ${PSVersionTable.PSVersion}" } else { # Trust all certificates(Diagnostic Server certificate is self-signed) #[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $True } add-type @" using System.Net; using System.Security.Cryptography.X509Certificates; public class TrustAllCertsPolicy : ICertificatePolicy { public bool CheckValidationResult( ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem) { return true; } } "@ [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy } ################################################################### ################# Public cmdlets ######################### <# .Synopsis Add specified DS into cache or import Diagnostic Server configuration of Spotlight client on current machine into cache. .Description Add specified DS into cache or import Diagnostic Server configuration of Spotlight client into cache. This function only supports import Diagnostic Server configuration of Spotlight client on Windows. .Parameter DiagnosticServerHost The Diagnostic Server host name without protocol and port. .Parameter UserName The user name that used to login the speicified Diagnostic Server .Parameter Password The password for the UserName parameter. .Example # Import configuration of Spotlight client that installed on current Windows system. Import-DS .Example # Import the specified Diagnostic Server into cache Import-DS -DiagnosticServerHost "zhu123456.prod.quest.com" -UserName "domain\user1" -Password "pwd.12345" #> function Import-DS { [CmdletBinding(DefaultParameterSetName="DiagnosticServerCLI", HelpURI="http://help.spotlightessentials.com/enterprise_welcome.html", SupportsPaging=$false, SupportsShouldProcess=$false, PositionalBinding=$true)] param ( # Specifies a Diagnostic Server host name. The value DiagnosticServerHost parameter should not include protocol(HTTPS) # and port(40403). [Parameter(Mandatory=$false, Position=0, ParameterSetName="AddressSet", ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, HelpMessage="The Diagnostic Server host name.")] [String] [Alias("DSHost")] [ValidateNotNullOrEmpty()] $DiagnosticServerHost, # Specifies a user name that used to login the specified Diagnostic Server. [Parameter(Mandatory=$false, Position=1, ParameterSetName="AddressSet", ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, HelpMessage="The user name that used to login the speicified Diagnostic Server.")] [Alias("User")] [ValidateNotNullOrEmpty()] [string] $UserName, # Specifies a password for the UserName that used to login the specified Diagnostic Server. [Parameter(Mandatory=$false, Position=2, ParameterSetName="AddressSet", ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, HelpMessage="The password for the UserName parameter that used to login the speicified Diagnostic Server.")] [Alias("PWD")] [ValidateNotNullOrEmpty()] [securestring] $Password ) if ($PSCmdlet.ParameterSetName -eq "AddressSet") { $DiagnosticServer = [PSCustomObject]@{ DiagnosticServerHost = $DiagnosticServerHost UserName = $UserName Password = $Password } $Script:DiagnosticServers.Add($DiagnosticServer) } } Export-ModuleMember -Function Import-DS <# .Synopsis Get connections. .Parameter DiagnosticServerHost The Diagnostic Server that the operation works on .Description Get connections from a specified Diagnostic Server and filter result by specified filters. .Example # Get all connections from "hostname-domian" Get-Connection -DiagnosticServerHost "hostname-domian" #> function Get-Connection { [CmdletBinding(DefaultParameterSetName="DiagnosticServerCLI", HelpURI="http://help.spotlightessentials.com/enterprise_welcome.html", SupportsPaging=$false, SupportsShouldProcess=$false, PositionalBinding=$true)] param ( # Specifies a Diagnostic Server host name. The value DiagnosticServerHost parameter should not include protocol(HTTPS) # and port(40403). [Parameter(Mandatory=$true, Position=0, ParameterSetName="AddressSet", ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, HelpMessage="The Diagnostic Server host name that the cmdlet operates on.")] [Alias("DSHost", "DS")] [String] [ValidateNotNullOrEmpty()] $DiagnosticServerHost, # Specifies connection display name that used to filter the result. [Parameter(Mandatory=$true, Position=1, ParameterSetName="FilterSet", ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, HelpMessage="The connection display name that used to filter result.")] [Alias("Connection", "Connections", "ConnectionNames")] [String[]] [AllowEmptyCollection] $ConnectionName ) try { # Request connections via /api/connections $Response = SendRequest -DiagnosticServerHost $DiagnosticServerHost -RelativeUrl "/api/connections" -Method "GET" if($null -eq $Response) { Write-Error -Message "No response from ${DiagnosticServerHost}, please make sure the Spotlight Diagnostic Server service on ${DiagnosticServerHost} is running." } # Handle http response $StatusCode = $Response.StatusCode if(200 -eq $StatusCode) { $ResponseObj = ConvertFrom-Json -InputObject $Response.Content if($null -ne $ResponseObj) { $Connections = $ResponseObj.Connections if($null -eq $Connections) { Write-Information "No response from Diagnostic Server." } else { $Result = New-Object System.Collections.ArrayList foreach($Connection in $Connections) { $Matched = $true # Filter by connection display name if(($null -ne $ConnectionName) -and (0 -lt $ConnectionName.Count)) { $ConnectionDisplayNameMatched = $false foreach ($ConnectionDisplayName in $ConnectionName) { if($ConnectionDisplayName -ieq $Connection.DisplayName) { $ConnectionDisplayNameMatched = $true break } } $Matched = $ConnectionDisplayNameMatched } # Add to result list if($true -eq $Matched) { $R = [System.Management.Automation.PSObject]::new() $R | Add-Member -MemberType NoteProperty -Name "DisplayName" -Value $Connection.DisplayName -Force $R | Add-Member -MemberType NoteProperty -Name "Technology" -Value $Connection.TechnologyDisplayName -Force $R | Add-Member -MemberType NoteProperty -Name "Enabled" -Value $Connection.Enabled -Force $R | Add-Member -MemberType NoteProperty -Name "WinAuth" -Value $Connection.winAuth -Force $R | Add-Member -MemberType NoteProperty -Name "Tags" -Value $Connection.Tags -Force $Result.Add($R) >$null } } Format-List -InputObject $Result } } else { Write-Information "No response from Diagnostic Server." } } else { Write-Error -ErrorId "${StatusCode}" -Message "An error occurred in Diagnostic Server, see ${Response.StatusDescription}." } } catch { PrintException -Exception $_.Exception Write-Debug $_ } } Set-Alias -Name Get-Connections -Value Get-Connection -Option ReadOnly -Description "Get connections from specified Diagnostic Server" Export-ModuleMember -Function Get-Connection -Alias Get-Connections ################# Private Shared Functions ######################### <# .Synopsis Send HTTP request to specified Diagnostic Server. .Parameter DiagnosticServerHost The Diagnostic Server host name .Description Send HTTP request to specified Diagnostic Server. .Example # Request all connections from a Diagnostic Server SendRequest -DiagnosticServerHost $DiagnosticServerHost -RelativeUrl "/api/connections" -Method "GET" #> function SendRequest { param ( # Specifies a Diagnostic Server host name. The value DiagnosticServerHost parameter should not include protocol(HTTPS) # and port(40403). [Parameter(Mandatory=$true)] [String] [ValidateNotNullOrEmpty()] $DiagnosticServerHost, # Specifies the relative url, like /api/login, including query and fragment. [Parameter(Mandatory=$true)] [string] [ValidateNotNullOrEmpty()] $RelativeUrl, # Specifies the HTTP request method, the value should be one of GET, POST, DELETE, UPDATE. [Parameter(Mandatory=$true)] [Microsoft.PowerShell.Commands.WebRequestMethod] [ValidateNotNullOrEmpty()] $Method, # Specifies the HTTP request body(json). [Parameter(Mandatory=$false)] [PSCustomObject] $Body, # Specifies the HTTP request body(json). [Parameter(Mandatory=$false)] [string] $Query ) $Uri = New-Object System.Uri("${Global:DiagnosticServerWebProtocol}://${DiagnosticServerHost}:${Global:DiagnosticServerPort}${RelativeUrl}") Write-Debug $Uri $ContentType = "application/json" $UserAgent = "DiagnosticServerCLI/1.0" if(($PSVersionTable.PSEdition -eq "Core") -or ($PSVersionTable.PSVersion.Major -gt 6)) { if(($Method -ne [Microsoft.PowerShell.Commands.WebRequestMethod]"GET") -and ($null -ne $Body)) { $json = ConvertTo-Json -InputObject $Body return Invoke-WebRequest $Uri -UserAgent $UserAgent -Method $Method -ContentType $ContentType -SkipCertificateCheck -UseDefaultCredentials -Body $json } else { return Invoke-WebRequest $Uri -UserAgent $UserAgent -Method $Method -ContentType $ContentType -SkipCertificateCheck -UseDefaultCredentials } } else { if(($Method -ne [Microsoft.PowerShell.Commands.WebRequestMethod]"GET") -and ($null -ne $Body)) { $json = ConvertTo-Json -InputObject $Body return Invoke-WebRequest $Uri -UserAgent $UserAgent -Method $Method -ContentType $ContentType -UseDefaultCredentials -Body $json } else { return Invoke-WebRequest $Uri -UserAgent $UserAgent -Method $Method -ContentType $ContentType -UseDefaultCredentials } } throw [System.InvalidOperationException]::new("Error occurred when sending request to ${DiagnosticServerHost}") } <# .Synopsis Print the exception details to console. .Parameter Exception The exception. .Description Print the exception details to console. .Example # Print the exception to error stream PrintException -Exception $_.Exception #> function PrintException { param ( # Specifies a Diagnostic Server host name. The value DiagnosticServerHost parameter should not include protocol(HTTPS) # and port(40403). [Parameter(Mandatory=$false)] [System.Exception] $Exception ) if ($null -eq $Exception) { Write-Debug "The exception is null." } if ($null -eq $Exception.Response) { # Exception thrown by Invoke-WebRequest if(200 -eq $Exception.Response.StatusCode) { Write-Debug "The http request in fact is succeed." } elseif (400 -eq $Exception.Response.StatusCode) { Write-Error -ErrorId "bad request(400)" -Message "The request is bad, please try again or contact Quest support to seek help." } elseif (401 -eq $Exception.Response.StatusCode) { Write-Error -ErrorId "Unauthenticated(401)" -Message "The Diagnostic Server responses 401(unauthenticated), please make sure the current user ${env:USERDOMAIN}\${$env:USERName} in Diagnostic Server groups." } elseif (403 -eq $Exception.Response.StatusCode) { Write-Error -ErrorId "forbidden(403)" -Message "The Diagnostic Server responses 403(forbidden), please make sure the current user ${env:USERDOMAIN}\${$env:USERName} in Spotlight Diagnostic Administrators group." } elseif (500 -eq $Exception.Response.StatusCode) { Write-Error -ErrorId "internal error(500)" -Message "An internal error(500) occurred in Diagnostic Server." } else { Write-Error -ErrorId "${Exception.Response.StatusCode}" -Message "An error occurred in Diagnostic Server, see ${Exception.Response.StatusDescription}." } } else { Write-Error -Message $Exception.ToString() } } ################################################################### #################### Code Signature below ####################### #################### DONOT DELETE !!! ####################### ################################################################### # SIG # Begin signature block # MIIN6gYJKoZIhvcNAQcCoIIN2zCCDdcCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUF4Xwg6/M8guiOpDkiqkPxIhY # +UegggshMIIFJDCCBAygAwIBAgIRAKUiPaTPNplDzk48ofdLsiEwDQYJKoZIhvcN # AQELBQAwfDELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3Rl # cjEQMA4GA1UEBxMHU2FsZm9yZDEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMSQw # IgYDVQQDExtTZWN0aWdvIFJTQSBDb2RlIFNpZ25pbmcgQ0EwHhcNMTkwNjI0MDAw # MDAwWhcNMjEwNjIzMjM1OTU5WjCBlDELMAkGA1UEBhMCVVMxDjAMBgNVBBEMBTky # NjU2MQswCQYDVQQIDAJDQTEUMBIGA1UEBwwLQWxpc28gVmllam8xFjAUBgNVBAkM # DTQgUG9sYXJpcyBXYXkxHDAaBgNVBAoME1F1ZXN0IFNvZnR3YXJlIEluYy4xHDAa # BgNVBAMME1F1ZXN0IFNvZnR3YXJlIEluYy4wggEiMA0GCSqGSIb3DQEBAQUAA4IB # DwAwggEKAoIBAQCwZsIXCTY9zj+xysmh6S1WnRYMPD32F+myo78qrcha91NX9UZn # UPXn+4eXWdVOU54BRaAEM/BCs4Hwa58KIIJY17Jy3zZV3BFCZEmDt5EEpMqyjzlk # hAZOEvWDD3EWZ2WuWo978hSOE2SO0p0a9GcDYLzN2nK/qJAWXc9RF4K0OeY+V4At # Wsb7Jgbrxh0q7rxNzfydq/IYRsXyNi6GygzSZknbMTZ2Me6vHhCpyWEz58Vn/r9v # B2sUopiGDHF/an4uIX5xeqKr3LI1j67R8Mjtz2MbZ/A7vqz6TYjRjAl5SD43iw0l # W+h3uhd5ZSSCzL5+GsaJApBFhk/qf+QhXPM5AgMBAAGjggGGMIIBgjAfBgNVHSME # GDAWgBQO4TqoUzox1Yq+wbutZxoDha00DjAdBgNVHQ4EFgQUmikN52xU3eqIY1Nm # pObsiCwut0UwDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwEwYDVR0lBAww # CgYIKwYBBQUHAwMwEQYJYIZIAYb4QgEBBAQDAgQQMEAGA1UdIAQ5MDcwNQYMKwYB # BAGyMQECAQMCMCUwIwYIKwYBBQUHAgEWF2h0dHBzOi8vc2VjdGlnby5jb20vQ1BT # MEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly9jcmwuc2VjdGlnby5jb20vU2VjdGln # b1JTQUNvZGVTaWduaW5nQ0EuY3JsMHMGCCsGAQUFBwEBBGcwZTA+BggrBgEFBQcw # AoYyaHR0cDovL2NydC5zZWN0aWdvLmNvbS9TZWN0aWdvUlNBQ29kZVNpZ25pbmdD # QS5jcnQwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLnNlY3RpZ28uY29tMA0GCSqG # SIb3DQEBCwUAA4IBAQCCSelsaQGbWS1/lHXqBiVgUgL6dZ0+FsAJldBlP6NPhVnJ # GMUMvLtAq2x/Tt3PowXiWcR4ZpaTY6Df3cVbliGl12e+Ib5z/UaI31Hg1BgxWjt2 # MZgiWUSpriB6gyxqOiCJwLySAV/IVjnTefvMRQim7Wnw9hT7Af7JgIVcIC+hY7IJ # JUa1HYxeLSA+wMOFKqvF/uMXGWNaF1fSQrlZoyk9CQzKw114BEA/ZI6QEQocmMba # MNSHaN/cYUEnN1lWqgk1XwYLNiQHNsJYKZsUpPZXfk6tQ9eOyb/hRP31wJwoG3y1 # V/J0nEV6XFyXyFKulifqfVPAeUQdg78XW/FFzmdYMIIF9TCCA92gAwIBAgIQHaJI # MG+bJhjQguCWfTPTajANBgkqhkiG9w0BAQwFADCBiDELMAkGA1UEBhMCVVMxEzAR # BgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK # ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0Eg # Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTgxMTAyMDAwMDAwWhcNMzAxMjMx # MjM1OTU5WjB8MQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVz # dGVyMRAwDgYDVQQHEwdTYWxmb3JkMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQx # JDAiBgNVBAMTG1NlY3RpZ28gUlNBIENvZGUgU2lnbmluZyBDQTCCASIwDQYJKoZI # hvcNAQEBBQADggEPADCCAQoCggEBAIYijTKFehifSfCWL2MIHi3cfJ8Uz+MmtiVm # KUCGVEZ0MWLFEO2yhyemmcuVMMBW9aR1xqkOUGKlUZEQauBLYq798PgYrKf/7i4z # IPoMGYmobHutAMNhodxpZW0fbieW15dRhqb0J+V8aouVHltg1X7XFpKcAC9o95ft # anK+ODtj3o+/bkxBXRIgCFnoOc2P0tbPBrRXBbZOoT5Xax+YvMRi1hsLjcdmG0qf # nYHEckC14l/vC0X/o84Xpi1VsLewvFRqnbyNVlPG8Lp5UEks9wO5/i9lNfIi6iwH # r0bZ+UYc3Ix8cSjz/qfGFN1VkW6KEQ3fBiSVfQ+noXw62oY1YdMCAwEAAaOCAWQw # ggFgMB8GA1UdIwQYMBaAFFN5v1qqK0rPVIDh2JvAnfKyA2bLMB0GA1UdDgQWBBQO # 4TqoUzox1Yq+wbutZxoDha00DjAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgw # BgEB/wIBADAdBgNVHSUEFjAUBggrBgEFBQcDAwYIKwYBBQUHAwgwEQYDVR0gBAow # CDAGBgRVHSAAMFAGA1UdHwRJMEcwRaBDoEGGP2h0dHA6Ly9jcmwudXNlcnRydXN0 # LmNvbS9VU0VSVHJ1c3RSU0FDZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDB2Bggr # BgEFBQcBAQRqMGgwPwYIKwYBBQUHMAKGM2h0dHA6Ly9jcnQudXNlcnRydXN0LmNv # bS9VU0VSVHJ1c3RSU0FBZGRUcnVzdENBLmNydDAlBggrBgEFBQcwAYYZaHR0cDov # L29jc3AudXNlcnRydXN0LmNvbTANBgkqhkiG9w0BAQwFAAOCAgEATWNQ7Uc0SmGk # 295qKoyb8QAAHh1iezrXMsL2s+Bjs/thAIiaG20QBwRPvrjqiXgi6w9G7PNGXkBG # iRL0C3danCpBOvzW9Ovn9xWVM8Ohgyi33i/klPeFM4MtSkBIv5rCT0qxjyT0s4E3 # 07dksKYjalloUkJf/wTr4XRleQj1qZPea3FAmZa6ePG5yOLDCBaxq2NayBWAbXRe # SnV+pbjDbLXP30p5h1zHQE1jNfYw08+1Cg4LBH+gS667o6XQhACTPlNdNKUANWls # vp8gJRANGftQkGG+OY96jk32nw4e/gdREmaDJhlIlc5KycF/8zoFm/lv34h/wCOe # 0h5DekUxwZxNqfBZslkZ6GqNKQQCd3xLS81wvjqyVVp4Pry7bwMQJXcVNIr5NsxD # kuS6T/FikyglVyn7URnHoSVAaoRXxrKdsbwcCtp8Z359LukoTBh+xHsxQXGaSyns # Cz1XUNLK3f2eBVHlRHjdAd6xdZgNVCT98E7j4viDvXK6yz067vBeF5Jobchh+abx # KgoLpbn0nu6YMgWFnuv5gynTxix9vTp3Los3QqBqgu07SqqUEKThDfgXxbZaeTMY # kuO1dfih6Y4KJR7kHvGfWocj/5+kUZ77OYARzdu1xKeogG/lU9Tg46LC0lsa+jIm # LWpXcBw8pFguo/NbSwfcMlnzh6cabVgxggIzMIICLwIBATCBkTB8MQswCQYDVQQG # EwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxm # b3JkMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxJDAiBgNVBAMTG1NlY3RpZ28g # UlNBIENvZGUgU2lnbmluZyBDQQIRAKUiPaTPNplDzk48ofdLsiEwCQYFKw4DAhoF # AKB4MBgGCisGAQQBgjcCAQwxCjAIoAKAAKECgAAwGQYJKoZIhvcNAQkDMQwGCisG # AQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwIwYJKoZIhvcN # AQkEMRYEFD8aQGOPitJ9QQJyaPPp86a/buAQMA0GCSqGSIb3DQEBAQUABIIBAHQo # YySSNLNTznu3PHoC7bsdk2BHbm/f5AxlPDDbW2yCcuHagmehrrPImqyAHVzoahka # ra69ww1hm0xtEcZGJrF4f5pCMtv8SY0Qwime/faJ9ATLbfkObQRKvtQvn53SMPF9 # ge+QRHn6VTxLHfDM9Xdognw7zJK+YZyzV7dq1dGgdcYYXa7bIiQtn+5ustv20O2C # VlDlRGif9r+5Ys1Un8Xb3o/2m7cIyFzWPowGxb8OHVrnAHeZnAb9ggxrs9njd/YK # VXyGzj6RMlTKxY0+qCoIORF8LIramAXbMJPXANKo1y+WyDH2KFjSW6EQGcnn0c2S # CR0XxbXKdiEDV0qkJc0= # SIG # End signature block |