DiagnosticServerCLI.psm1

<#
* 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
        }

        $Global:DiagnosticServers.Add($DiagnosticServer)
    }
}

<#
    .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 $_
    }
}



################# 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()
    }
}


Set-Alias -Name Get-Connections -Value Get-Connection -Option ReadOnly -Description "Get connections from specified Diagnostic Server"
Export-ModuleMember -Function Get-* -Alias Get-*




###################################################################
#################### 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