ScreenConnectUtils.psm1
# module variables $ScriptPath = Split-Path (Get-Variable MyInvocation -Scope Script).Value.Mycommand.Definition -Parent $ModuleName = (Get-Item (Get-Variable MyInvocation -Scope Script).Value.Mycommand.Definition).BaseName # turn on informational messages $InformationPreference = 'Continue' # load localized language Import-LocalizedData -BindingVariable 'Messages' -FileName 'Messages' -BaseDirectory (Join-Path $ScriptPath 'lang') # load the config if ( Test-Path "$ScriptPath\DefaultConfig.psd1" ) { # configuration parameters # we have to add it in the loader script so that it's available to the dot sourced files $ConfigSplat = @{ Name = $ModuleName CompanyName = 'Brooksworks' DefaultPath = "$ScriptPath\DefaultConfig.psd1" } # create config variable # we have to add it in the loader script so that it's available to the dot sourced files $Config = Import-Configuration @ConfigSplat } # import cached data if ( Test-Path "$ScriptPath\data\*.json" ) { $Data = @{} Get-ChildItem -Path "$ScriptPath\data" -Filter '*.json' | ForEach-Object { $Data.($_.BaseName) = Get-Content $_.FullName | ConvertFrom-Json } } <# .SYNOPSIS Extracts attachments from a JNLP file. .PARAMETER Path Path to the JNLP file to search for attachments. .PARAMETER Destination Path to destination directory. #> function Expand-JnlpAttachments { param( [Parameter(Mandatory)] [ValidatePattern('\.jnlp$')] $Path ) $Path = [System.IO.FileInfo][string]( Resolve-Path $Path ) Get-Content $Path -Raw | ForEach-Object { ([xml]$_).jnlp.'application-desc'.argument } | Where-Object { $_ -match 'JNLP_ATTACHMENTS' } | ForEach-Object { $_.Split('=',2)[1].Split(';') } | Select-Object @{N='Path';E={ Join-Path $Path.Directory.FullName $_.Split(',',2)[0] }}, @{N='Base64Data';E={ $_.Split(':',2)[1] }} | ForEach-Object { $DecodedData = [System.Convert]::FromBase64String( $_.Base64Data ) $MemoryStream = New-Object System.IO.MemoryStream ( , $DecodedData ) $DeflateStream = New-Object System.IO.Compression.DeflateStream ( $MemoryStream, [System.IO.Compression.CompressionMode]::Decompress ) $ByteList = New-Object collections.generic.list[byte] while ( ( $Byte = $DeflateStream.ReadByte() ) -ne -1 ) { $ByteList.Add( $Byte) } Set-Content -Encoding Byte -Value $ByteList.ToArray() -Path $_.Path } } <# .SYNOPSIS Creates an Immediate Scheduled Task .PARAMETER ComputerName The computer(s) for the task to be executed. .PARAMETER TaskName The name of the task. Note: Can not be a duplicate name .PARAMETER Description The description for the task. .PARAMETER Command The main command to execute. .PARAMETER ArgumentList The list of parameters to pass to the executable. Note: A single parameter will not be modified, however if an array of parameters is passed any parameter containing a space will be wrapped in double quotes. .PARAMETER WorkingDirectory The working directory for the task if applicable. Note: This variable is interpereted on the local machine. .PARAMETER Credential The credential to use when creating the task. .PARAMETER Wait Wait for the task to complete before continuing. #> function New-ImmediateScheduledTask { [CmdletBinding()] param( [string[]] $ComputerName, [Parameter(Mandatory)] [string] $TaskName, [string] $Description, [Parameter(Mandatory)] [string] $Command, [string[]] $ArgumentList, [string] $WorkingDirectory, [pscredential] $Credential, [switch] $Wait ) # immediate task template $TaskXmlTemplate = @' <?xml version="1.0" encoding="UTF-16"?> <Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task"> <RegistrationInfo> <Date>2019-09-11T15:07:41.203317</Date> <Author>METHODE\sgraybrook</Author> <URI>\Test Task</URI> </RegistrationInfo> <Triggers> <RegistrationTrigger> <EndBoundary>2020-09-11T15:10:31</EndBoundary> <Enabled>true</Enabled> </RegistrationTrigger> </Triggers> <Principals> <Principal id="Author"> <UserId>S-1-5-18</UserId> <RunLevel>HighestAvailable</RunLevel> </Principal> </Principals> <Settings> <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy> <DisallowStartIfOnBatteries>true</DisallowStartIfOnBatteries> <StopIfGoingOnBatteries>true</StopIfGoingOnBatteries> <AllowHardTerminate>true</AllowHardTerminate> <StartWhenAvailable>true</StartWhenAvailable> <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable> <IdleSettings> <StopOnIdleEnd>true</StopOnIdleEnd> <RestartOnIdle>false</RestartOnIdle> </IdleSettings> <AllowStartOnDemand>true</AllowStartOnDemand> <Enabled>true</Enabled> <Hidden>false</Hidden> <RunOnlyIfIdle>false</RunOnlyIfIdle> <WakeToRun>false</WakeToRun> <ExecutionTimeLimit>PT72H</ExecutionTimeLimit> <DeleteExpiredTaskAfter>PT1M</DeleteExpiredTaskAfter> <Priority>7</Priority> </Settings> <Actions Context="Author"> <Exec> <Command></Command> </Exec> </Actions> </Task> '@ # parse the arguments # if there is only one argument we pass as is # otherwise we wrap arguments with spaces in double quotes and join together $Arguments = if ( $ArgumentList.Count -eq 1 ) { $ArgumentList[0].Trim() } elseif ( $ArgumentList.Count -gt 1 ) { ( $Arguments | % Trim | %{ if ( $_.IndexOf(' ') -ne -1 ) { '"{0}"' -f $_ } else { $_ } } ) -join ' ' } # set the parameters $ParamSplat = @{} if ( $Credential ) { $ParamSplat.Credential = $Credential } if ( $ComputerName ) { $ParamSplat.ComputerName = $ComputerName } # register the task Invoke-Command @ParamSplat -ScriptBlock { param([xml]$TaskXml, [string]$TaskName, [string]$Description, [string]$Command, [string]$Arguments, [string]$WorkingDirectory, [bool]$Wait) # set the task name $TaskXml.Task.RegistrationInfo.URI = '\' + $TaskName # set the description or remove it if ( -not [string]::IsNullOrEmpty( $Description ) ) { $DescriptionNode = $TaskXml.CreateElement('Description', $TaskXml.DocumentElement.NamespaceURI) $DescriptionNode.InnerText = $Description [void]$TaskXml.Task.RegistrationInfo.AppendChild( $DescriptionNode ) } # set the author to the executing user $TaskXml.Task.RegistrationInfo.Author = $env:USERDOMAIN, $env:USERNAME -join '\' # registration time is now $TaskXml.Task.RegistrationInfo.Date = (Get-Date).ToString('yyyy-MM-ddTHH:mm:ss.fffffff') # set trigger to expire in one minute $TaskXml.Task.Triggers.RegistrationTrigger.EndBoundary = (Get-Date).AddMinutes(1).ToString('yyyy-MM-ddTHH:mm:ss') # task executable $TaskXml.Task.Actions.Exec.Command = $Command # task arguments if ( -not [string]::IsNullOrEmpty( $Arguments ) ) { $ArgumentsNode = $TaskXml.CreateElement('Arguments', $TaskXml.DocumentElement.NamespaceURI) $ArgumentsNode.InnerText = $Arguments [void]$TaskXml.Task.Actions.Exec.AppendChild($ArgumentsNode) } # set the working directory if ( -not [string]::IsNullOrEmpty( $WorkingDirectory ) ) { $WorkingDirectoryNode = $TaskXml.CreateElement('WorkingDirectory', $TaskXml.DocumentElement.NamespaceURI) $WorkingDirectoryNode.InnerText = $WorkingDirectory [void]$TaskXml.Task.Actions.Exec.AppendChild( $WorkingDirectoryNode ) } # create the temporary task xml file $TempXmlPath = Join-Path $env:TEMP 'ImmediateTaskDefinition.xml' Set-Content -Path $TempXmlPath -Value $TaskXml.InnerXml -Encoding Unicode -Force -Confirm:$false # schedule the task if ( (Start-Process -FilePath 'schtasks.exe' -ArgumentList "/Create /XML ""$TempXmlPath"" /tn ""$TaskName""" -Wait -PassThru).ExitCode -eq 0 ) { Write-Host ( 'Task ''{0}'' was scheduled successfully on computer {1}' -f $TaskName, $env:COMPUTERNAME ) # should we wait? if ( $Wait ) { Start-Sleep -Seconds 5 while ( (Start-Process -FilePath 'schtasks.exe' -ArgumentList "/Query /tn ""$TaskName""" -Wait -PassThru).ExitCode -eq 0 ) { Write-Host ( 'Waiting for task ''{0}'' to finish on {1}' -f $TaskName, $env:COMPUTERNAME ) Start-Sleep -Seconds 5 } } } else { Write-Error ( 'Failed to create task ''{0}'' on computer {1}' -f $TaskName, $env:COMPUTERNAME ) } } -ArgumentList $TaskXmlTemplate, $TaskName, $Description, $Command, $Arguments, $WorkingDirectory, $Wait.IsPresent } <# .SYNOPSIS Utility function to check if host is online. #> function Test-HostConnection { [CmdletBinding()] param( [string] $ComputerName ) if ( $PSBoundParameters.Keys -notcontains 'ErrorAction' ) { $ErrorActionPreference = 'Stop' } Write-Verbose ( $Messages.CheckingHostConnectionVerboseMessage -f $ComputerName ) # verify computer is responding if ( -not( Test-Connection -ComputerName $ComputerName -Count 1 -Quiet ) ) { Write-Error ( $Messages.HostConnectionFailedError -f $ComputerName ) return $false } # check that port 445 is open $Socket= New-Object Net.Sockets.TcpClient $IAsyncResult= [IAsyncResult] $Socket.BeginConnect( $ComputerName, 445, $null, $null ) $IAsyncResult.AsyncWaitHandle.WaitOne( 500, $true ) > $null $PortOpen = $Socket.Connected $Socket.close() if ( -not $PortOpen ) { Write-Error ( $Messages.HostPortConnectionFailedError -f $ComputerName ) return $false } return $true } <# .DESCRIPTION Connect to a ScreenConnect remote support session from PowerShell. Note that you must have the Guest Session starter extension enabled. See: https://docs.connectwise.com/ConnectWise_Control_Documentation/Supported_extensions/Productivity/Guest_Session_Starter .PARAMETER ScreenConnectUri URI for ScreenConnect instance .PARAMETER SessionName What should your session be named .PARAMETER ScreenConnectPath Path to ScreenConnect files #> function Connect-SupportSession { param( [Parameter(Mandatory=$true)] [ValidatePattern('(?# must include http/https )^https?://.+')] [ValidateNotNullOrEmpty()] [string] $ScreenConnectUri, [ValidateNotNullOrEmpty()] [string] $SessionName = "PowerShell Session - $env:COMPUTERNAME", [ValidateNotNullOrEmpty()] [string] $ScreenConnectPath = ( Join-Path $env:TEMP 'ScreenConnectClient' ) ) $ErrorActionPreference = 'Stop' if ( $PSVersionTable.PSVersion.Major -lt 3 ) { throw 'Minimum supported version of PowerShell is 3.0' } $ScreenConnectPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($ScreenConnectPath) if ( -not( Test-Path -Path $ScreenConnectPath -PathType Container ) ) { New-Item -Path $ScreenConnectPath -ItemType Directory -Force > $null } $ConnectionParams = @{ y = 'Guest' h = $null p = $null s = $null k = $null i = $SessionName } $InvokeWebRequestSplat = @{ Uri = '{0}/Script.ashx' -f $ScreenConnectUri.Trim('/') UseBasicParsing = $true } $ScreenConnectJS = Invoke-WebRequest @InvokeWebRequestSplat if ( $ScreenConnectJS.RawContent -match '"h":"(?<h>[^"]+)","p":(?<p>\d+),"k":"(?<k>[^"]+)"' ) { $ConnectionParams.h = $Matches.h $ConnectionParams.p = $Matches.p $ConnectionParams.k = [uri]::EscapeDataString($Matches.k) } else { Write-Error 'Could not parse connection params!' } $InvokeRestMethodSplat = @{ Method = 'Post' Uri = '{0}/App_Extensions/2d4e908b-8471-431d-b4e0-2390f43bfe67/Service.ashx/CreateGuestSupportSession' -f $ScreenConnectUri.Trim('/') Body = (ConvertTo-Json @($SessionName) -Compress) ContentType = 'application/json' } $ConnectionParams.s = Invoke-RestMethod @InvokeRestMethodSplat $ScreenConnectArguments = ( $ConnectionParams.Keys | %{ '{0}={1}' -f $_, $ConnectionParams.$_ } ) -join '&' -replace '^', '"?' -replace '$', '"' $ScreenConnectExe = Join-Path $ScreenConnectPath 'ScreenConnect.WindowsClient.exe' if ( -not (Test-Path -Path $ScreenConnectExe ) ) { $URIs = @( '{0}/Bin/ConnectWiseControl.ClientBootstrap.jnlp{1}' -f $ScreenConnectUri.Trim('/'), $ScreenConnectArguments.Trim('"') '{0}/Bin/ScreenConnect.Client.exe.jar' -f $ScreenConnectUri.Trim('/') ) $URIs | ForEach-Object {@{ Uri = $_ ; OutFile = Join-Path $ScreenConnectPath ( Split-Path -Path ( $_ -replace '\?.*' ) -Leaf ) }} | ForEach-Object { Invoke-WebRequest @_ } Add-Type -Assembly System.IO.Compression.Filesystem [System.IO.Compression.ZipFile]::ExtractToDirectory( "$ScreenConnectPath\ScreenConnect.Client.exe.jar", "$ScreenConnectPath" ) Expand-JnlpAttachments -Path "$ScreenConnectPath\ConnectWiseControl.ClientBootstrap.jnlp" } if ( Test-Path -Path $ScreenConnectExe ) { Start-Process -FilePath $ScreenConnectExe -ArgumentList $ScreenConnectArguments } else { Write-Error 'Could not locate ScreenConnect.WindowsClient.exe' } } <# .SYNOPSIS Attempts to install ScreenConnect Host Client on a remote machine. .PARAMETER Computer Computer(s) to attempt to install. .PARAMETER Credential PSCredential object to use for authentication. .PARAMETER Username The plaintext username to use for authentication. Defaults to 'Administrator'. .PARAMETER Password The plaintext password to use for authentication. .OUTPUTS No output. #> function Install-HostClient { [CmdletBinding(SupportsShouldProcess)] param( [parameter(Mandatory=$true, Position=1, ValueFromPipeline=$True)] [string[]] $Computer, [Parameter(Mandatory=$true, Position=2)] [ValidatePattern('(?# must be an EXE or MSI )\.(exe|msi)$')] [string] $Installer, [pscredential] $Credential = [pscredential]::Empty ) begin { if ( $PSBoundParameters.Keys -notcontains 'InformationAction' ) { $InformationPreference = 'Continue' } if ( $PSBoundParameters.Keys -notcontains 'ErrorAction' ) { $ErrorActionPreference = 'Stop' } Get-Command -Name PsExec.exe > $null $CredentialSplat = @{} if ( $Credential -ne [pscredential]::Empty ) { $CredentialSplat.Credential = $Credential } $InstallerPath = Resolve-Path $Installer | Get-Item $ScheduledTaskSplat = switch ( $InstallerPath.Extension ) { '.exe' {@{ TaskName = 'Deploy ScreenConnect Client (EXE)' Command = Join-Path 'C:\_ScreenConnectDeployment' $InstallerPath.Name }} '.msi' {@{ TaskName = 'Deploy ScreenConnect Client (MSI)' Command = 'C:\Windows\System32\msiexec.exe' ArgumentList = '/i {0} /qn' -f ( Join-Path 'C:\_ScreenConnectDeployment' $InstallerPath.Name ) }} } } process { foreach ( $ComputerItem in $Computer ) { if ( -not( Test-HostConnection $ComputerItem ) ) { continue } Write-Verbose ( $Messages.MappingTemporaryDriveVerboseMessage -f "\\$ComputerItem\C$" ) if ( -not( New-PSDrive -Name 'RemoteComputer' -PSProvider FileSystem -Root "\\$ComputerItem\C$" @CredentialSplat ) ) { Write-Error $Messages.CouldNotMapRemoteDriveError continue } if ( -not( Test-Path -Path 'RemoteComputer:\_ScreenConnectDeployment' ) ) { Write-Verbose ( $Messages.CreatingDeploymentDirectoryVerboseMessage -f 'C:\_ScreenConnectDeployment' ) New-Item 'RemoteComputer:\_ScreenConnectDeployment' -ItemType Directory > $null } Write-Verbose $Messages.PushingInstallerFileVerboseMessage Copy-Item -Path $InstallerPath -Destination 'RemoteComputer:\_ScreenConnectDeployment\' -Force Write-Information ( $Messages.InvokingScreenConnectInstallerMessage -f $ComputerItem ) New-ImmediateScheduledTask @ScheduledTaskSplat -ComputerName $ComputerItem @CredentialSplat -Wait Write-Verbose ( $Messages.RemovingDeploymentDirectoryVerboseMessage -f 'C:\_ScreenConnectDeployment' ) Remove-Item 'RemoteComputer:\_ScreenConnectDeployment' -Recurse -Confirm:$false -ErrorAction Continue Write-Verbose $Messages.UnMappingTemporaryDriveVerboseMessage Remove-PSDrive -Name 'RemoteComputer' Write-Information $Messages.InstallationFinishedMessage } } } # cleanup $ExecutionContext.SessionState.Module.OnRemove = {} # SIG # Begin signature block # MIIesgYJKoZIhvcNAQcCoIIeozCCHp8CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUgPlWIgT746+JDoyJGL2PtTaJ # s1Wgghm9MIIEhDCCA2ygAwIBAgIQQhrylAmEGR9SCkvGJCanSzANBgkqhkiG9w0B # AQUFADBvMQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNV # BAsTHUFkZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRU # cnVzdCBFeHRlcm5hbCBDQSBSb290MB4XDTA1MDYwNzA4MDkxMFoXDTIwMDUzMDEw # NDgzOFowgZUxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJVVDEXMBUGA1UEBxMOU2Fs # dCBMYWtlIENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEhMB8G # A1UECxMYaHR0cDovL3d3dy51c2VydHJ1c3QuY29tMR0wGwYDVQQDExRVVE4tVVNF # UkZpcnN0LU9iamVjdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6q # gT+jo2F4qjEAVZURnicPHxzfOpuCaDDASmEd8S8O+r5596Uj71VRloTN2+O5bj4x # 2AogZ8f02b+U60cEPgLOKqJdhwQJ9jCdGIqXsqoc/EHSoTbL+z2RuufZcDX65OeQ # w5ujm9M89RKZd7G3CeBo5hy485RjiGpq/gt2yb70IuRnuasaXnfBhQfdDWy/7gbH # d2pBnqcP1/vulBe3/IW+pKvEHDHd17bR5PDv3xaPslKT16HUiaEHLr/hARJCHhrh # 2JU022R5KP+6LhHC5ehbkkj7RwvCbNqtMoNB86XlQXD9ZZBt+vpRxPm9lisZBCzT # bafc8H9vg2XiaquHhnUCAwEAAaOB9DCB8TAfBgNVHSMEGDAWgBStvZh6NLQm9/rE # JlTvA73gJMtUGjAdBgNVHQ4EFgQU2u1kdBScFDyr3ZmpvVsoTYs8ydgwDgYDVR0P # AQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAGBgRVHSAAMEQG # A1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9BZGRUcnVz # dEV4dGVybmFsQ0FSb290LmNybDA1BggrBgEFBQcBAQQpMCcwJQYIKwYBBQUHMAGG # GWh0dHA6Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEFBQADggEBAE1C # L6bBiusHgJBYRoz4GTlmKjxaLG3P1NmHVY15CxKIe0CP1cf4S41VFmOtt1fcOyu9 # 08FPHgOHS0Sb4+JARSbzJkkraoTxVHrUQtr802q7Zn7Knurpu9wHx8OSToM8gUmf # ktUyCepJLqERcZo20sVOaLbLDhslFq9s3l122B9ysZMmhhfbGN6vRenf+5ivFBjt # pF72iZRF8FUESt3/J90GSkD2tLzx5A+ZArv9XQ4uKMG+O18aP5cQhLwWPtijnGMd # ZstcX9o+8w8KCTUi29vAPwD55g1dZ9H9oB4DK9lA977Mh2ZUgKajuPUZYtXSJrGY # Ju6ay0SnRVqBlRUa9VEwggTmMIIDzqADAgECAhBiXE2QjNVC+6supXM/8VQZMA0G # CSqGSIb3DQEBBQUAMIGVMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNV # BAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdv # cmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTEdMBsGA1UEAxMU # VVROLVVTRVJGaXJzdC1PYmplY3QwHhcNMTEwNDI3MDAwMDAwWhcNMjAwNTMwMTA0 # ODM4WjB6MQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVy # MRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDEg # MB4GA1UEAxMXQ09NT0RPIFRpbWUgU3RhbXBpbmcgQ0EwggEiMA0GCSqGSIb3DQEB # AQUAA4IBDwAwggEKAoIBAQCqgvGEqVvYcbXSXSvt9BMgDPmb6dGPdF5u7uspSNjI # vizrCmFgzL2SjXzddLsKnmhOqnUkcyeuN/MagqVtuMgJRkx+oYPp4gNgpCEQJ0Ca # WeFtrz6CryFpWW1jzM6x9haaeYOXOh0Mr8l90U7Yw0ahpZiqYM5V1BIR8zsLbMaI # upUu76BGRTl8rOnjrehXl1/++8IJjf6OmqU/WUb8xy1dhIfwb1gmw/BC/FXeZb5n # OGOzEbGhJe2pm75I30x3wKoZC7b9So8seVWx/llaWm1VixxD9rFVcimJTUA/vn9J # AV08m1wI+8ridRUFk50IYv+6Dduq+LW/EDLKcuoIJs0ZAgMBAAGjggFKMIIBRjAf # BgNVHSMEGDAWgBTa7WR0FJwUPKvdmam9WyhNizzJ2DAdBgNVHQ4EFgQUZCKGtkqJ # yQQP0ARYkiuzbj0eJ2wwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8C # AQAwEwYDVR0lBAwwCgYIKwYBBQUHAwgwEQYDVR0gBAowCDAGBgRVHSAAMEIGA1Ud # HwQ7MDkwN6A1oDOGMWh0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZp # cnN0LU9iamVjdC5jcmwwdAYIKwYBBQUHAQEEaDBmMD0GCCsGAQUFBzAChjFodHRw # Oi8vY3J0LnVzZXJ0cnVzdC5jb20vVVROQWRkVHJ1c3RPYmplY3RfQ0EuY3J0MCUG # CCsGAQUFBzABhhlodHRwOi8vb2NzcC51c2VydHJ1c3QuY29tMA0GCSqGSIb3DQEB # BQUAA4IBAQARyT3hBeg7ZazJdDEDt9qDOMaSuv3N+Ntjm30ekKSYyNlYaDS18Ash # U55ZRv1jhd/+R6pw5D9eCJUoXxTx/SKucOS38bC2Vp+xZ7hog16oYNuYOfbcSV4T # p5BnS+Nu5+vwQ8fQL33/llqnA9abVKAj06XCoI75T9GyBiH+IV0njKCv2bBS7vzI # 7bec8ckmONalMu1Il5RePeA9NbSwyVivx1j/YnQWkmRB2sqo64sDvcFOrh+RMrjh # JDt77RRoCYaWKMk7yWwowiVp9UphreAn+FOndRWwUTGw8UH/PlomHmB+4uNqOZrE # 6u4/5rITP1UDBE0LkHLU6/u8h5BRsjgZMIIE/jCCA+agAwIBAgIQK3PbdGMRTFpb # MkryMFdySTANBgkqhkiG9w0BAQUFADB6MQswCQYDVQQGEwJHQjEbMBkGA1UECBMS # R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFD # T01PRE8gQ0EgTGltaXRlZDEgMB4GA1UEAxMXQ09NT0RPIFRpbWUgU3RhbXBpbmcg # Q0EwHhcNMTkwNTAyMDAwMDAwWhcNMjAwNTMwMTA0ODM4WjCBgzELMAkGA1UEBhMC # R0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBwwHU2FsZm9y # ZDEYMBYGA1UECgwPU2VjdGlnbyBMaW1pdGVkMSswKQYDVQQDDCJTZWN0aWdvIFNI # QS0xIFRpbWUgU3RhbXBpbmcgU2lnbmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A # MIIBCgKCAQEAv1I2gjrcdDcNeNV/FlAZZu26GpnRYziaDGayQNungFC/aS42Lwpn # P0ChSopjNZvQGcx0qhcZkSu1VSAZ+8AaOm3KOZuC8rqVoRrYNMe4iXtwiHBRZmns # d/7GlHJ6zyWB7TSCmt8IFTcxtG2uHL8Y1Q3P/rXhxPuxR3Hp+u5jkezx7M5ZBBF8 # rgtgU+oq874vAg/QTF0xEy8eaQ+Fm0WWwo0Si2euH69pqwaWgQDfkXyVHOaeGWTf # dshgRC9J449/YGpFORNEIaW6+5H6QUDtTQK0S3/f4uA9uKrzGthBg49/M+1BBuJ9 # nj9ThI0o2t12xr33jh44zcDLYCQD3npMqwIDAQABo4IBdDCCAXAwHwYDVR0jBBgw # FoAUZCKGtkqJyQQP0ARYkiuzbj0eJ2wwHQYDVR0OBBYEFK7u2WC6XvUsARL9jo2y # VXI1Rm/xMA4GA1UdDwEB/wQEAwIGwDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQM # MAoGCCsGAQUFBwMIMEAGA1UdIAQ5MDcwNQYMKwYBBAGyMQECAQMIMCUwIwYIKwYB # BQUHAgEWF2h0dHBzOi8vc2VjdGlnby5jb20vQ1BTMEIGA1UdHwQ7MDkwN6A1oDOG # MWh0dHA6Ly9jcmwuc2VjdGlnby5jb20vQ09NT0RPVGltZVN0YW1waW5nQ0FfMi5j # cmwwcgYIKwYBBQUHAQEEZjBkMD0GCCsGAQUFBzAChjFodHRwOi8vY3J0LnNlY3Rp # Z28uY29tL0NPTU9ET1RpbWVTdGFtcGluZ0NBXzIuY3J0MCMGCCsGAQUFBzABhhdo # dHRwOi8vb2NzcC5zZWN0aWdvLmNvbTANBgkqhkiG9w0BAQUFAAOCAQEAen+pStKw # pBwdDZ0tXMauWt2PRR3wnlyQ9l6scP7T2c3kGaQKQ3VgaoOkw5mEIDG61v5MzxP4 # EPdUCX7q3NIuedcHTFS3tcmdsvDyHiQU0JzHyGeqC2K3tPEG5OfkIUsZMpk0uRlh # dwozkGdswIhKkvWhQwHzrqJvyZW9ljj3g/etfCgf8zjfjiHIcWhTLcuuquIwF4Mi # KRi14YyJ6274fji7kE+5Xwc0EmuX1eY7kb4AFyFu4m38UnnvgSW6zxPQ+90rzYG2 # V4lO8N3zC0o0yoX/CLmWX+sRE+DhxQOtVxzhXZIGvhvIPD+lIJ9p0GnBxcLJPufF # cvfqG5bilK+GLjCCBUwwggQ0oAMCAQICEQCV7K1bRdp1yZPPBYrFbG8VMA0GCSqG # SIb3DQEBCwUAMHwxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNo # ZXN0ZXIxEDAOBgNVBAcTB1NhbGZvcmQxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRl # ZDEkMCIGA1UEAxMbU2VjdGlnbyBSU0EgQ29kZSBTaWduaW5nIENBMB4XDTE5MTAx # NTAwMDAwMFoXDTIwMTAwNzIzNTk1OVowgZQxCzAJBgNVBAYTAlVTMQ4wDAYDVQQR # DAU2MDEyMDERMA8GA1UECAwISWxsaW5vaXMxDjAMBgNVBAcMBUVsZ2luMRowGAYD # VQQJDBExMjg3IEJsYWNraGF3ayBEcjEaMBgGA1UECgwRU2hhbm5vbiBHcmF5YnJv # b2sxGjAYBgNVBAMMEVNoYW5ub24gR3JheWJyb29rMIIBIjANBgkqhkiG9w0BAQEF # AAOCAQ8AMIIBCgKCAQEA1A3wiJRalXGleCYOLaKdlD5iZrswpu4ChSnCx8XvkWeL # R/XBQSvebJXpF99sdVwwUeouEk1i5EA2AIU88DoEw0+1XxC6DAUwYAVXmo3M+dkv # OwNXHrWwSRqNwmhABHVejGOInKsi1jYa3DPI2dFBL19Trg0ez0oXkMVwbKGDpwt9 # U7WbbjveLcAPnpvR65dk3Jhb9bmCMirCnALjaOOnFzlCUiagx9nDszzw7fYRAlf6 # EJNnicwwBujOmA59q9urwAuEA7/VXTAMpE2wmhVsM4xqscbzAPs7PSVgkOTrZR6a # 51r1HSCzrULISVZKxF0mD4/6qOElqM/X/nd7q7dmSQIDAQABo4IBrjCCAaowHwYD # VR0jBBgwFoAUDuE6qFM6MdWKvsG7rWcaA4WtNA4wHQYDVR0OBBYEFJHiTLW7XSJv # Xn/hpQzh7bxSIUZ2MA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMBMGA1Ud # JQQMMAoGCCsGAQUFBwMDMBEGCWCGSAGG+EIBAQQEAwIEEDBABgNVHSAEOTA3MDUG # DCsGAQQBsjEBAgEDAjAlMCMGCCsGAQUFBwIBFhdodHRwczovL3NlY3RpZ28uY29t # L0NQUzBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsLnNlY3RpZ28uY29tL1Nl # Y3RpZ29SU0FDb2RlU2lnbmluZ0NBLmNybDBzBggrBgEFBQcBAQRnMGUwPgYIKwYB # BQUHMAKGMmh0dHA6Ly9jcnQuc2VjdGlnby5jb20vU2VjdGlnb1JTQUNvZGVTaWdu # aW5nQ0EuY3J0MCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5zZWN0aWdvLmNvbTAm # BgNVHREEHzAdgRtzaGFubm9uLmdyYXlicm9va0BnbWFpbC5jb20wDQYJKoZIhvcN # AQELBQADggEBACNm23H5GuT8THomfaxBDdgN/4g4FgsClLsxhAyRyWxqnE4udxre # x1Dq3FQtdXoeXFPaaFYVH/zvmEFuh+oz65Ejomo2WPSOVKiF6NbLpxScHW2c1+yO # NHDqn/TGtx0+RrfUgOFgao/AzuRqxei90CotgUe73cpmG0JPdmV1+hnMAhojoO4g # bhfdb69y8fCaDzLoTmybz1JOfcinR12TLntNV+Def2CXaNoOV2VNKpauAiIh2BkK # 7LoabyBtMNQbMNCY33dyNq9V7tvVxdYOlPRoANB3SfATPtKQCrix7T85qrFoRHBC # SxTfYFHsyGQVno6lmMfQstJ6q+TQJz1gFcUwggX1MIID3aADAgECAhAdokgwb5sm # GNCC4JZ9M9NqMA0GCSqGSIb3DQEBDAUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UE # CBMKTmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRo # ZSBVU0VSVFJVU1QgTmV0d29yazEuMCwGA1UEAxMlVVNFUlRydXN0IFJTQSBDZXJ0 # aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xODExMDIwMDAwMDBaFw0zMDEyMzEyMzU5 # NTlaMHwxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIx # EDAOBgNVBAcTB1NhbGZvcmQxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDEkMCIG # A1UEAxMbU2VjdGlnbyBSU0EgQ29kZSBTaWduaW5nIENBMIIBIjANBgkqhkiG9w0B # AQEFAAOCAQ8AMIIBCgKCAQEAhiKNMoV6GJ9J8JYvYwgeLdx8nxTP4ya2JWYpQIZU # RnQxYsUQ7bKHJ6aZy5UwwFb1pHXGqQ5QYqVRkRBq4Etirv3w+Bisp//uLjMg+gwZ # iahse60Aw2Gh3GllbR9uJ5bXl1GGpvQn5Xxqi5UeW2DVftcWkpwAL2j3l+1qcr44 # O2Pej79uTEFdEiAIWeg5zY/S1s8GtFcFtk6hPldrH5i8xGLWGwuNx2YbSp+dgcRy # QLXiX+8LRf+jzhemLVWwt7C8VGqdvI1WU8bwunlQSSz3A7n+L2U18iLqLAevRtn5 # RhzcjHxxKPP+p8YU3VWRbooRDd8GJJV9D6ehfDrahjVh0wIDAQABo4IBZDCCAWAw # HwYDVR0jBBgwFoAUU3m/WqorSs9UgOHYm8Cd8rIDZsswHQYDVR0OBBYEFA7hOqhT # OjHVir7Bu61nGgOFrTQOMA4GA1UdDwEB/wQEAwIBhjASBgNVHRMBAf8ECDAGAQH/ # AgEAMB0GA1UdJQQWMBQGCCsGAQUFBwMDBggrBgEFBQcDCDARBgNVHSAECjAIMAYG # BFUdIAAwUAYDVR0fBEkwRzBFoEOgQYY/aHR0cDovL2NybC51c2VydHJ1c3QuY29t # L1VTRVJUcnVzdFJTQUNlcnRpZmljYXRpb25BdXRob3JpdHkuY3JsMHYGCCsGAQUF # BwEBBGowaDA/BggrBgEFBQcwAoYzaHR0cDovL2NydC51c2VydHJ1c3QuY29tL1VT # RVJUcnVzdFJTQUFkZFRydXN0Q0EuY3J0MCUGCCsGAQUFBzABhhlodHRwOi8vb2Nz # cC51c2VydHJ1c3QuY29tMA0GCSqGSIb3DQEBDAUAA4ICAQBNY1DtRzRKYaTb3moq # jJvxAAAeHWJ7Otcywvaz4GOz+2EAiJobbRAHBE++uOqJeCLrD0bs80ZeQEaJEvQL # d1qcKkE6/Nb06+f3FZUzw6GDKLfeL+SU94Uzgy1KQEi/msJPSrGPJPSzgTfTt2Sw # piNqWWhSQl//BOvhdGV5CPWpk95rcUCZlrp48bnI4sMIFrGrY1rIFYBtdF5KdX6l # uMNstc/fSnmHXMdATWM19jDTz7UKDgsEf6BLrrujpdCEAJM+U100pQA1aWy+nyAl # EA0Z+1CQYb45j3qOTfafDh7+B1ESZoMmGUiVzkrJwX/zOgWb+W/fiH/AI57SHkN6 # RTHBnE2p8FmyWRnoao0pBAJ3fEtLzXC+OrJVWng+vLtvAxAldxU0ivk2zEOS5LpP # 8WKTKCVXKftRGcehJUBqhFfGsp2xvBwK2nxnfn0u6ShMGH7EezFBcZpLKewLPVdQ # 0srd/Z4FUeVEeN0B3rF1mA1UJP3wTuPi+IO9crrLPTru8F4XkmhtyGH5pvEqCgul # ufSe7pgyBYWe6/mDKdPGLH29OncuizdCoGqC7TtKqpQQpOEN+BfFtlp5MxiS47V1 # +KHpjgolHuQe8Z9ahyP/n6RRnvs5gBHN27XEp6iAb+VT1ODjosLSWxr6MiYtaldw # HDykWC6j81tLB9wyWfOHpxptWDGCBF8wggRbAgEBMIGRMHwxCzAJBgNVBAYTAkdC # MRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAOBgNVBAcTB1NhbGZvcmQx # GDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDEkMCIGA1UEAxMbU2VjdGlnbyBSU0Eg # Q29kZSBTaWduaW5nIENBAhEAleytW0XadcmTzwWKxWxvFTAJBgUrDgMCGgUAoHgw # GAYKKwYBBAGCNwIBDDEKMAigAoAAoQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGC # NwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAjBgkqhkiG9w0BCQQx # FgQUP5JCU3+GoUrJogwj/TaUTlcUQTgwDQYJKoZIhvcNAQEBBQAEggEAflB3bj2k # 2OlU9z+M8AflJ0D3mo0jsghGPZbJq9wHMqy5Rbu3Kqa2eB+6j2BcvQ3iAsGN4Cbu # T27+sJXZtDmm3M1bsuXRlKsokS2cxWC1vjW7YjR/Eg5JIgLIlYTczl9ytK5Eka1u # /QFcjHTFhl1nPVLTu74miI8+0QDzLFGceQxemaepIJdy17SrE9mL2IcCTU8pcPBG # TVC1jtfCIO6W/GNk8UEHXLoK8QPemH17JQs5oJriyzrPN0xLgyI+jvYT/pQTm1xI # uCJkgnXI4CbOlPSxNi+N2VDnbUq+hTWdgowSVr4Q/yFdwwcFqt0zKrO47bWppq5f # wWRY7yecR/twjaGCAigwggIkBgkqhkiG9w0BCQYxggIVMIICEQIBATCBjjB6MQsw # CQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQH # EwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDEgMB4GA1UEAxMX # Q09NT0RPIFRpbWUgU3RhbXBpbmcgQ0ECECtz23RjEUxaWzJK8jBXckkwCQYFKw4D # AhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8X # DTIwMDEyMzE5MTkzMlowIwYJKoZIhvcNAQkEMRYEFPOUZrK8wNhIjeoSzUTFU06F # 8566MA0GCSqGSIb3DQEBAQUABIIBAFRLHencXOYoy8wWVBAb5P/XfY2wn9/lzso3 # IqdAeJn0CjoQTLJ3jedR/lZ+iT3/KiBQr7SvCKj8Cw0zI57kqUDH2ei4uTnomc+4 # C+4QvEi5KPI3og3i/Cb+pCG4jNgzgj0R0sFBq8rRw1U5eptrYqr4D/UgxNdvHPFQ # RlXL8tzp3LOWuYTTPrih7qC95jkuKd0onaMLEa8fs0sx4FafjbkmeO3Y12lDvUr4 # t2rhohMGjkJua74KeSuzRWjaxW7NWEScg1jBsvidnZbebkl9aTvmD57j+O3opqCY # IFe0PcecfKHyxe2TKkGY/uq1+DeuSMNqhvWuryLNrHkRekuwINI= # SIG # End signature block |