Public/New-Runspace.ps1
<# .SYNOPSIS The New-Runspace function creates a Runspace that executes the specified ScriptBlock in the background and posts results to a Global Variable called $global:RSSyncHash. .DESCRIPTION See .SYNOPSIS .NOTES .PARAMETER RunspaceName This parameter is MANDATORY. This parameter takes a string that represents the name of the new Runspace that you are creating. The name is represented as a key in the $global:RSSyncHash variable called: <RunspaceName>Result .PARAMETER ScriptBlock This parameter is MANDATORY. This parameter takes a scriptblock that will be executed in the new Runspace. .PARAMETER MirrorCurrentEnv This parameter is OPTIONAL, however, it is set to $True by default. This parameter is a switch. If used, all variables, functions, and Modules that are loaded in your current scope will be forwarded to the new Runspace. You can prevent the New-Runspace function from automatically mirroring your current environment by using this switch like: -MirrorCurrentEnv:$False .PARAMETER Wait This parameter is OPTIONAL. This parameter is a switch. If used, the main PowerShell thread will wait for the Runsapce to return output before proceeeding. .EXAMPLE # Open a PowerShell Session, source the function, and - PS C:\Users\zeroadmin> $GetProcessResults = Get-Process # In the below, Runspace1 refers to your current interactive PowerShell Session... PS C:\Users\zeroadmin> Get-Runspace Id Name ComputerName Type State Availability -- ---- ------------ ---- ----- ------------ 1 Runspace1 localhost Local Opened Busy # The below will create a 'Runspace Manager Runspace' (if it doesn't already exist) # to manage all other new Runspaces created by the New-Runspace function. # Additionally, it will create the Runspace that actually runs the -ScriptBlock. # The 'Runspace Manager Runspace' disposes of new Runspaces when they're # finished running. PS C:\Users\zeroadmin> New-RunSpace -RunSpaceName PSIds -ScriptBlock {$($GetProcessResults | Where-Object {$_.Name -eq "powershell"}).Id} # The 'Runspace Manager Runspace' persists just in case you create any additional # Runspaces, but the Runspace that actually ran the above -ScriptBlock does not. # In the below, 'Runspace2' is the 'Runspace Manager Runspace. PS C:\Users\zeroadmin> Get-Runspace Id Name ComputerName Type State Availability -- ---- ------------ ---- ----- ------------ 1 Runspace1 localhost Local Opened Busy 2 Runspace2 localhost Local Opened Busy # You can actively identify (as opposed to infer) the 'Runspace Manager Runspace' # by using one of three Global variables created by the New-Runspace function: PS C:\Users\zeroadmin> $global:RSJobCleanup.PowerShell.Runspace Id Name ComputerName Type State Availability -- ---- ------------ ---- ----- ------------ 2 Runspace2 localhost Local Opened Busy # As mentioned above, the New-RunspaceName function creates three Global # Variables. They are $global:RSJobs, $global:RSJobCleanup, and # $global:RSSyncHash. Your output can be found in $global:RSSyncHash. PS C:\Users\zeroadmin> $global:RSSyncHash Name Value ---- ----- PSIdsResult @{Done=True; Errors=; Output=System.Object[]} ProcessedJobRecords {@{Name=PSIdsHelper; PSInstance=System.Management.Automation.PowerShell; Runspace=System.Management.Automation.Runspaces.Loca... PS C:\Users\zeroadmin> $global:RSSyncHash.PSIdsResult Done Errors Output ---- ------ ------ True {1300, 2728, 2960, 3712...} PS C:\Users\zeroadmin> $global:RSSyncHash.PSIdsResult.Output 1300 2728 2960 3712 4632 # Important Note: You don't need to worry about passing variables / functions / # Modules to the Runspace. Everything in your current session/scope is # automatically forwarded by the New-Runspace function: PS C:\Users\zeroadmin> function Test-Func {'This is Test-Func output'} PS C:\Users\zeroadmin> New-RunSpace -RunSpaceName FuncTest -ScriptBlock {Test-Func} PS C:\Users\zeroadmin> $global:RSSyncHash Name Value ---- ----- FuncTestResult @{Done=True; Errors=; Output=This is Test-Func output} PSIdsResult @{Done=True; Errors=; Output=System.Object[]} ProcessedJobRecords {@{Name=PSIdsHelper; PSInstance=System.Management.Automation.PowerShell; Runspace=System.Management.Automation.Runspaces.Loca... PS C:\Users\zeroadmin> $global:RSSyncHash.FuncTestResult.Output This is Test-Func output #> function New-RunSpace { [CmdletBinding()] Param ( [Parameter(Mandatory=$True)] [string]$RunspaceName, [Parameter(Mandatory=$True)] [scriptblock]$ScriptBlock, [Parameter(Mandatory=$False)] [switch]$MirrorCurrentEnv = $True, [Parameter(Mandatory=$False)] [switch]$Wait ) #region >> Helper Functions function NewUniqueString { [CmdletBinding()] Param( [Parameter(Mandatory=$False)] [string[]]$ArrayOfStrings, [Parameter(Mandatory=$True)] [string]$PossibleNewUniqueString ) if (!$ArrayOfStrings -or $ArrayOfStrings.Count -eq 0 -or ![bool]$($ArrayOfStrings -match "[\w]")) { $PossibleNewUniqueString } else { $OriginalString = $PossibleNewUniqueString $Iteration = 1 while ($ArrayOfStrings -contains $PossibleNewUniqueString) { $AppendedValue = "_$Iteration" $PossibleNewUniqueString = $OriginalString + $AppendedValue $Iteration++ } $PossibleNewUniqueString } } #endregion >> Helper Functions #region >> Runspace Prep # Create Global Variable Names that don't conflict with other exisiting Global Variables $ExistingGlobalVariables = Get-Variable -Scope Global $DesiredGlobalVariables = @("RSSyncHash","RSJobCleanup","RSJobs") if ($ExistingGlobalVariables.Name -notcontains 'RSSyncHash') { $GlobalRSSyncHashName = NewUniqueString -PossibleNewUniqueString "RSSyncHash" -ArrayOfStrings $ExistingGlobalVariables.Name Invoke-Expression "`$global:$GlobalRSSyncHashName = [hashtable]::Synchronized(@{})" $globalRSSyncHash = Get-Variable -Name $GlobalRSSyncHashName -Scope Global -ValueOnly } else { $GlobalRSSyncHashName = 'RSSyncHash' # Also make sure that $RunSpaceName is a unique key in $global:RSSyncHash if ($RSSyncHash.Keys -contains $RunSpaceName) { $RSNameOriginal = $RunSpaceName $RunSpaceName = NewUniqueString -PossibleNewUniqueString $RunSpaceName -ArrayOfStrings $RSSyncHash.Keys if ($RSNameOriginal -ne $RunSpaceName) { Write-Warning "The RunspaceName '$RSNameOriginal' already exists. Your new RunspaceName will be '$RunSpaceName'" } } $globalRSSyncHash = $global:RSSyncHash } if ($ExistingGlobalVariables.Name -notcontains 'RSJobCleanup') { $GlobalRSJobCleanupName = NewUniqueString -PossibleNewUniqueString "RSJobCleanup" -ArrayOfStrings $ExistingGlobalVariables.Name Invoke-Expression "`$global:$GlobalRSJobCleanupName = [hashtable]::Synchronized(@{})" $globalRSJobCleanup = Get-Variable -Name $GlobalRSJobCleanupName -Scope Global -ValueOnly } else { $GlobalRSJobCleanupName = 'RSJobCleanup' $globalRSJobCleanup = $global:RSJobCleanup } if ($ExistingGlobalVariables.Name -notcontains 'RSJobs') { $GlobalRSJobsName = NewUniqueString -PossibleNewUniqueString "RSJobs" -ArrayOfStrings $ExistingGlobalVariables.Name Invoke-Expression "`$global:$GlobalRSJobsName = [System.Collections.ArrayList]::Synchronized([System.Collections.ArrayList]::new())" $globalRSJobs = Get-Variable -Name $GlobalRSJobsName -Scope Global -ValueOnly } else { $GlobalRSJobsName = 'RSJobs' $globalRSJobs = $global:RSJobs } $GlobalVariables = @($GlobalSyncHashName,$GlobalRSJobCleanupName,$GlobalRSJobsName) #Write-Host "Global Variable names are: $($GlobalVariables -join ", ")" # Prep an empty pscustomobject for the RunspaceNameResult Key in $globalRSSyncHash $globalRSSyncHash."$RunspaceName`Result" = [pscustomobject]@{} #endregion >> Runspace Prep ##### BEGIN Runspace Manager Runspace (A Runspace to Manage All Runspaces) ##### $globalRSJobCleanup.Flag = $True if ($ExistingGlobalVariables.Name -notcontains 'RSJobCleanup') { #Write-Host '$global:RSJobCleanup does NOT already exists. Creating New Runspace Manager Runspace...' $RunspaceMgrRunspace = [runspacefactory]::CreateRunspace() if ($PSVersionTable.PSEdition -ne "Core") { $RunspaceMgrRunspace.ApartmentState = "STA" } $RunspaceMgrRunspace.ThreadOptions = "ReuseThread" $RunspaceMgrRunspace.Open() # Prepare to Receive the Child Runspace Info to the RunspaceManagerRunspace $RunspaceMgrRunspace.SessionStateProxy.SetVariable("JobCleanup",$globalRSJobCleanup) $RunspaceMgrRunspace.SessionStateProxy.SetVariable("jobs",$globalRSJobs) $RunspaceMgrRunspace.SessionStateProxy.SetVariable("SyncHash",$globalRSSyncHash) $globalRSJobCleanup.PowerShell = [PowerShell]::Create().AddScript({ ##### BEGIN Runspace Manager Runspace Helper Functions ##### # Load the functions we packed up $FunctionsForSBUse | foreach { Invoke-Expression $_ } ##### END Runspace Manager Runspace Helper Functions ##### # Routine to handle completed Runspaces $ProcessedJobRecords = [System.Collections.ArrayList]::new() $SyncHash.ProcessedJobRecords = $ProcessedJobRecords while ($JobCleanup.Flag) { if ($jobs.Count -gt 0) { $Counter = 0 foreach($job in $jobs) { if ($ProcessedJobRecords.Runspace.InstanceId.Guid -notcontains $job.Runspace.InstanceId.Guid) { $job | Export-CliXml "$HOME\job$Counter.xml" -Force $CollectJobRecordPrep = Import-CliXML -Path "$HOME\job$Counter.xml" Remove-Item -Path "$HOME\job$Counter.xml" -Force $null = $ProcessedJobRecords.Add($CollectJobRecordPrep) } if ($job.AsyncHandle.IsCompleted -or $job.AsyncHandle -eq $null) { [void]$job.PSInstance.EndInvoke($job.AsyncHandle) $job.Runspace.Dispose() $job.PSInstance.Dispose() $job.AsyncHandle = $null $job.PSInstance = $null } $Counter++ } # Determine if we can have the Runspace Manager Runspace rest $temparray = $jobs.clone() $temparray | Where-Object { $_.AsyncHandle.IsCompleted -or $_.AsyncHandle -eq $null } | foreach { $temparray.remove($_) } <# if ($temparray.Count -eq 0 -or $temparray.AsyncHandle.IsCompleted -notcontains $False) { $JobCleanup.Flag = $False } #> Start-Sleep -Seconds 5 # Optional - # For realtime updates to a GUI depending on changes in data within the $globalRSSyncHash, use # a something like the following (replace with $RSSyncHash properties germane to your project) <# if ($RSSyncHash.WPFInfoDatagrid.Items.Count -ne 0 -and $($RSSynchash.IPArray.Count -ne 0 -or $RSSynchash.IPArray -ne $null)) { if ($RSSyncHash.WPFInfoDatagrid.Items.Count -ge $RSSynchash.IPArray.Count) { Update-Window -Control $RSSyncHash.WPFInfoPleaseWaitLabel -Property Visibility -Value "Hidden" } } #> } } }) # Start the RunspaceManagerRunspace $globalRSJobCleanup.PowerShell.Runspace = $RunspaceMgrRunspace $globalRSJobCleanup.Thread = $globalRSJobCleanup.PowerShell.BeginInvoke() } ##### END Runspace Manager Runspace ##### ##### BEGIN New Generic Runspace ##### $GenericRunspace = [runspacefactory]::CreateRunspace() if ($PSVersionTable.PSEdition -ne "Core") { $GenericRunspace.ApartmentState = "STA" } $GenericRunspace.ThreadOptions = "ReuseThread" $GenericRunspace.Open() # Pass the $globalRSSyncHash to the Generic Runspace so it can read/write properties to it and potentially # coordinate with other runspaces $GenericRunspace.SessionStateProxy.SetVariable("SyncHash",$globalRSSyncHash) # Pass $globalRSJobCleanup and $globalRSJobs to the Generic Runspace so that the Runspace Manager Runspace can manage it $GenericRunspace.SessionStateProxy.SetVariable("JobCleanup",$globalRSJobCleanup) $GenericRunspace.SessionStateProxy.SetVariable("Jobs",$globalRSJobs) $GenericRunspace.SessionStateProxy.SetVariable("ScriptBlock",$ScriptBlock) # Pass all other notable environment characteristics if ($MirrorCurrentEnv) { [System.Collections.ArrayList]$SetEnvStringArray = @() $VariablesNotToForward = @('globalRSSyncHash','RSSyncHash','globalRSJobCleanUp','RSJobCleanup', 'globalRSJobs','RSJobs','ExistingGlobalVariables','DesiredGlobalVariables','$GlobalRSSyncHashName', 'RSNameOriginal','GlobalRSJobCleanupName','GlobalRSJobsName','GlobalVariables','RunspaceMgrRunspace', 'GenericRunspace','ScriptBlock') $Variables = Get-Variable foreach ($VarObj in $Variables) { if ($VariablesNotToForward -notcontains $VarObj.Name) { try { $GenericRunspace.SessionStateProxy.SetVariable($VarObj.Name,$VarObj.Value) } catch { Write-Verbose "Skipping `$$($VarObj.Name)..." } } } # Set Environment Variables $EnvVariables = Get-ChildItem Env:\ if ($PSBoundParameters['EnvironmentVariablesToForward'] -and $EnvironmentVariablesToForward -notcontains '*') { $EnvVariables = foreach ($VarObj in $EnvVariables) { if ($EnvironmentVariablesToForward -contains $VarObj.Name) { $VarObj } } } $SetEnvVarsPrep = foreach ($VarObj in $EnvVariables) { if ([char[]]$VarObj.Name -contains '(' -or [char[]]$VarObj.Name -contains ' ') { $EnvStringArr = @( 'try {' $(' ${env:' + $VarObj.Name + '} = ' + "@'`n$($VarObj.Value)`n'@") '}' 'catch {' " Write-Verbose 'Unable to forward environment variable $($VarObj.Name)'" '}' ) } else { $EnvStringArr = @( 'try {' $(' $env:' + $VarObj.Name + ' = ' + "@'`n$($VarObj.Value)`n'@") '}' 'catch {' " Write-Verbose 'Unable to forward environment variable $($VarObj.Name)'" '}' ) } $EnvStringArr -join "`n" } $SetEnvVarsString = $SetEnvVarsPrep -join "`n" $null = $SetEnvStringArray.Add($SetEnvVarsString) # Set Modules $Modules = Get-Module if ($PSBoundParameters['ModulesToForward'] -and $ModulesToForward -notcontains '*') { $Modules = foreach ($ModObj in $Modules) { if ($ModulesToForward -contains $ModObj.Name) { $ModObj } } } $ModulesNotToForward = @('MiniLab') $SetModulesPrep = foreach ($ModObj in $Modules) { if ($ModulesNotToForward -notcontains $ModObj.Name) { $ModuleManifestFullPath = $(Get-ChildItem -Path $ModObj.ModuleBase -Recurse -File | Where-Object { $_.Name -eq "$($ModObj.Name).psd1" }).FullName $ModStringArray = @( '$tempfile = [IO.Path]::Combine([IO.Path]::GetTempPath(), [IO.Path]::GetRandomFileName())' "if (![bool]('$($ModObj.Name)' -match '\.WinModule')) {" ' try {' " Import-Module '$($ModObj.Name)' -NoClobber -ErrorAction Stop 2>`$tempfile" ' }' ' catch {' ' try {' " Import-Module '$ModuleManifestFullPath' -NoClobber -ErrorAction Stop 2>`$tempfile" ' }' ' catch {' " Write-Warning 'Unable to Import-Module $($ModObj.Name)'" ' }' ' }' '}' 'if (Test-Path $tempfile) {' ' Remove-Item $tempfile -Force' '}' ) $ModStringArray -join "`n" } } $SetModulesString = $SetModulesPrep -join "`n" $null = $SetEnvStringArray.Add($SetModulesString) # Set Functions $Functions = Get-ChildItem Function:\ | Where-Object {![System.String]::IsNullOrWhiteSpace($_.Name)} if ($PSBoundParameters['FunctionsToForward'] -and $FunctionsToForward -notcontains '*') { $Functions = foreach ($FuncObj in $Functions) { if ($FunctionsToForward -contains $FuncObj.Name) { $FuncObj } } } $SetFunctionsPrep = foreach ($FuncObj in $Functions) { $FunctionText = Invoke-Expression $('@(${Function:' + $FuncObj.Name + '}.Ast.Extent.Text)') if ($($FunctionText -split "`n").Count -gt 1) { if ($($FunctionText -split "`n")[0] -match "^function ") { if ($($FunctionText -split "`n") -match "^'@") { Write-Warning "Unable to forward function $($FuncObj.Name) due to heredoc string: '@" } else { 'Invoke-Expression ' + "@'`n$FunctionText`n'@" } } } elseif ($($FunctionText -split "`n").Count -eq 1) { if ($FunctionText -match "^function ") { 'Invoke-Expression ' + "@'`n$FunctionText`n'@" } } } $SetFunctionsString = $SetFunctionsPrep -join "`n" $null = $SetEnvStringArray.Add($SetFunctionsString) $GenericRunspace.SessionStateProxy.SetVariable("SetEnvStringArray",$SetEnvStringArray) } $GenericPSInstance = [powershell]::Create() # Define the main PowerShell Script that will run the $ScriptBlock $null = $GenericPSInstance.AddScript({ $SyncHash."$RunSpaceName`Result" | Add-Member -Type NoteProperty -Name Done -Value $False $SyncHash."$RunSpaceName`Result" | Add-Member -Type NoteProperty -Name Errors -Value $null $SyncHash."$RunSpaceName`Result" | Add-Member -Type NoteProperty -Name ErrorsDetailed -Value $null $SyncHash."$RunspaceName`Result".Errors = [System.Collections.ArrayList]::new() $SyncHash."$RunspaceName`Result".ErrorsDetailed = [System.Collections.ArrayList]::new() ##### BEGIN Generic Runspace Helper Functions ##### # Load the environment we packed up if ($SetEnvStringArray) { foreach ($obj in $SetEnvStringArray) { if (![string]::IsNullOrWhiteSpace($obj)) { try { Invoke-Expression $obj } catch { $null = $SyncHash."$RunSpaceName`Result".Errors.Add($_) $ErrMsg = "Problem with:`n$obj`nError Message:`n" + $($_ | Out-String) $null = $SyncHash."$RunSpaceName`Result".ErrorsDetailed.Add($ErrMsg) } } } } ##### END Generic Runspace Helper Functions ##### ##### BEGIN Script To Run ##### try { # NOTE: Depending on the content of the scriptblock, InvokeReturnAsIs() and Invoke-Command can cause # the Runspace to hang. Invoke-Expression works all the time. #$Result = $ScriptBlock.InvokeReturnAsIs() #$Result = Invoke-Command -ScriptBlock $ScriptBlock #$SyncHash."$RunSpaceName`Result" | Add-Member -Type NoteProperty -Name SBString -Value $ScriptBlock.ToString() $Result = Invoke-Expression -Command $ScriptBlock.ToString() $SyncHash."$RunSpaceName`Result" | Add-Member -Type NoteProperty -Name Output -Value $Result } catch { $SyncHash."$RunSpaceName`Result" | Add-Member -Type NoteProperty -Name Output -Value $Result $null = $SyncHash."$RunSpaceName`Result".Errors.Add($_) $ErrMsg = "Problem with:`n$($ScriptBlock.ToString())`nError Message:`n" + $($_ | Out-String) $null = $SyncHash."$RunSpaceName`Result".ErrorsDetailed.Add($ErrMsg) } ##### END Script To Run ##### $SyncHash."$RunSpaceName`Result".Done = $True }) # Start the Generic Runspace $GenericPSInstance.Runspace = $GenericRunspace if ($Wait) { # The below will make any output of $GenericRunspace available in $Object in current scope $Object = New-Object 'System.Management.Automation.PSDataCollection[psobject]' $GenericAsyncHandle = $GenericPSInstance.BeginInvoke($Object,$Object) $GenericRunspaceInfo = [pscustomobject]@{ Name = $RunSpaceName + "Generic" PSInstance = $GenericPSInstance Runspace = $GenericRunspace AsyncHandle = $GenericAsyncHandle } $null = $globalRSJobs.Add($GenericRunspaceInfo) #while ($globalRSSyncHash."$RunSpaceName`Done" -ne $True) { while ($GenericAsyncHandle.IsCompleted -ne $True) { #Write-Host "Waiting for -ScriptBlock to finish..." Start-Sleep -Milliseconds 10 } $globalRSSyncHash."$RunspaceName`Result".Output #$Object } else { $HelperRunspace = [runspacefactory]::CreateRunspace() if ($PSVersionTable.PSEdition -ne "Core") { $HelperRunspace.ApartmentState = "STA" } $HelperRunspace.ThreadOptions = "ReuseThread" $HelperRunspace.Open() # Pass the $globalRSSyncHash to the Helper Runspace so it can read/write properties to it and potentially # coordinate with other runspaces $HelperRunspace.SessionStateProxy.SetVariable("SyncHash",$globalRSSyncHash) # Pass $globalRSJobCleanup and $globalRSJobs to the Helper Runspace so that the Runspace Manager Runspace can manage it $HelperRunspace.SessionStateProxy.SetVariable("JobCleanup",$globalRSJobCleanup) $HelperRunspace.SessionStateProxy.SetVariable("Jobs",$globalRSJobs) # Set any other needed variables in the $HelperRunspace $HelperRunspace.SessionStateProxy.SetVariable("GenericRunspace",$GenericRunspace) $HelperRunspace.SessionStateProxy.SetVariable("GenericPSInstance",$GenericPSInstance) $HelperRunspace.SessionStateProxy.SetVariable("RunSpaceName",$RunSpaceName) $HelperPSInstance = [powershell]::Create() # Define the main PowerShell Script that will run the $ScriptBlock $null = $HelperPSInstance.AddScript({ ##### BEGIN Script To Run ##### # The below will make any output of $GenericRunspace available in $Object in current scope $Object = New-Object 'System.Management.Automation.PSDataCollection[psobject]' $GenericAsyncHandle = $GenericPSInstance.BeginInvoke($Object,$Object) $GenericRunspaceInfo = [pscustomobject]@{ Name = $RunSpaceName + "Generic" PSInstance = $GenericPSInstance Runspace = $GenericRunspace AsyncHandle = $GenericAsyncHandle } $null = $Jobs.Add($GenericRunspaceInfo) #while ($SyncHash."$RunSpaceName`Done" -ne $True) { while ($GenericAsyncHandle.IsCompleted -ne $True) { #Write-Host "Waiting for -ScriptBlock to finish..." Start-Sleep -Milliseconds 10 } ##### END Script To Run ##### }) # Start the Helper Runspace $HelperPSInstance.Runspace = $HelperRunspace $HelperAsyncHandle = $HelperPSInstance.BeginInvoke() $HelperRunspaceInfo = [pscustomobject]@{ Name = $RunSpaceName + "Helper" PSInstance = $HelperPSInstance Runspace = $HelperRunspace AsyncHandle = $HelperAsyncHandle } $null = $globalRSJobs.Add($HelperRunspaceInfo) } ##### END Generic Runspace } # SIG # Begin signature block # MIIMiAYJKoZIhvcNAQcCoIIMeTCCDHUCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQU0GHSM2lXHD+S+Pbjf6P9JWTS # KTOgggn9MIIEJjCCAw6gAwIBAgITawAAAB/Nnq77QGja+wAAAAAAHzANBgkqhkiG # 9w0BAQsFADAwMQwwCgYDVQQGEwNMQUIxDTALBgNVBAoTBFpFUk8xETAPBgNVBAMT # CFplcm9EQzAxMB4XDTE3MDkyMDIxMDM1OFoXDTE5MDkyMDIxMTM1OFowPTETMBEG # CgmSJomT8ixkARkWA0xBQjEUMBIGCgmSJomT8ixkARkWBFpFUk8xEDAOBgNVBAMT # B1plcm9TQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDCwqv+ROc1 # bpJmKx+8rPUUfT3kPSUYeDxY8GXU2RrWcL5TSZ6AVJsvNpj+7d94OEmPZate7h4d # gJnhCSyh2/3v0BHBdgPzLcveLpxPiSWpTnqSWlLUW2NMFRRojZRscdA+e+9QotOB # aZmnLDrlePQe5W7S1CxbVu+W0H5/ukte5h6gsKa0ktNJ6X9nOPiGBMn1LcZV/Ksl # lUyuTc7KKYydYjbSSv2rQ4qmZCQHqxyNWVub1IiEP7ClqCYqeCdsTtfw4Y3WKxDI # JaPmWzlHNs0nkEjvnAJhsRdLFbvY5C2KJIenxR0gA79U8Xd6+cZanrBUNbUC8GCN # wYkYp4A4Jx+9AgMBAAGjggEqMIIBJjASBgkrBgEEAYI3FQEEBQIDAQABMCMGCSsG # AQQBgjcVAgQWBBQ/0jsn2LS8aZiDw0omqt9+KWpj3DAdBgNVHQ4EFgQUicLX4r2C # Kn0Zf5NYut8n7bkyhf4wGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwDgYDVR0P # AQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUdpW6phL2RQNF # 7AZBgQV4tgr7OE0wMQYDVR0fBCowKDAmoCSgIoYgaHR0cDovL3BraS9jZXJ0ZGF0 # YS9aZXJvREMwMS5jcmwwPAYIKwYBBQUHAQEEMDAuMCwGCCsGAQUFBzAChiBodHRw # Oi8vcGtpL2NlcnRkYXRhL1plcm9EQzAxLmNydDANBgkqhkiG9w0BAQsFAAOCAQEA # tyX7aHk8vUM2WTQKINtrHKJJi29HaxhPaHrNZ0c32H70YZoFFaryM0GMowEaDbj0 # a3ShBuQWfW7bD7Z4DmNc5Q6cp7JeDKSZHwe5JWFGrl7DlSFSab/+a0GQgtG05dXW # YVQsrwgfTDRXkmpLQxvSxAbxKiGrnuS+kaYmzRVDYWSZHwHFNgxeZ/La9/8FdCir # MXdJEAGzG+9TwO9JvJSyoGTzu7n93IQp6QteRlaYVemd5/fYqBhtskk1zDiv9edk # mHHpRWf9Xo94ZPEy7BqmDuixm4LdmmzIcFWqGGMo51hvzz0EaE8K5HuNvNaUB/hq # MTOIB5145K8bFOoKHO4LkTCCBc8wggS3oAMCAQICE1gAAAH5oOvjAv3166MAAQAA # AfkwDQYJKoZIhvcNAQELBQAwPTETMBEGCgmSJomT8ixkARkWA0xBQjEUMBIGCgmS # JomT8ixkARkWBFpFUk8xEDAOBgNVBAMTB1plcm9TQ0EwHhcNMTcwOTIwMjE0MTIy # WhcNMTkwOTIwMjExMzU4WjBpMQswCQYDVQQGEwJVUzELMAkGA1UECBMCUEExFTAT # BgNVBAcTDFBoaWxhZGVscGhpYTEVMBMGA1UEChMMRGlNYWdnaW8gSW5jMQswCQYD # VQQLEwJJVDESMBAGA1UEAxMJWmVyb0NvZGUyMIIBIjANBgkqhkiG9w0BAQEFAAOC # AQ8AMIIBCgKCAQEAxX0+4yas6xfiaNVVVZJB2aRK+gS3iEMLx8wMF3kLJYLJyR+l # rcGF/x3gMxcvkKJQouLuChjh2+i7Ra1aO37ch3X3KDMZIoWrSzbbvqdBlwax7Gsm # BdLH9HZimSMCVgux0IfkClvnOlrc7Wpv1jqgvseRku5YKnNm1JD+91JDp/hBWRxR # 3Qg2OR667FJd1Q/5FWwAdrzoQbFUuvAyeVl7TNW0n1XUHRgq9+ZYawb+fxl1ruTj # 3MoktaLVzFKWqeHPKvgUTTnXvEbLh9RzX1eApZfTJmnUjBcl1tCQbSzLYkfJlJO6 # eRUHZwojUK+TkidfklU2SpgvyJm2DhCtssFWiQIDAQABo4ICmjCCApYwDgYDVR0P # AQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMB0GA1UdDgQWBBS5d2bhatXq # eUDFo9KltQWHthbPKzAfBgNVHSMEGDAWgBSJwtfivYIqfRl/k1i63yftuTKF/jCB # 6QYDVR0fBIHhMIHeMIHboIHYoIHVhoGubGRhcDovLy9DTj1aZXJvU0NBKDEpLENO # PVplcm9TQ0EsQ049Q0RQLENOPVB1YmxpYyUyMEtleSUyMFNlcnZpY2VzLENOPVNl # cnZpY2VzLENOPUNvbmZpZ3VyYXRpb24sREM9emVybyxEQz1sYWI/Y2VydGlmaWNh # dGVSZXZvY2F0aW9uTGlzdD9iYXNlP29iamVjdENsYXNzPWNSTERpc3RyaWJ1dGlv # blBvaW50hiJodHRwOi8vcGtpL2NlcnRkYXRhL1plcm9TQ0EoMSkuY3JsMIHmBggr # BgEFBQcBAQSB2TCB1jCBowYIKwYBBQUHMAKGgZZsZGFwOi8vL0NOPVplcm9TQ0Es # Q049QUlBLENOPVB1YmxpYyUyMEtleSUyMFNlcnZpY2VzLENOPVNlcnZpY2VzLENO # PUNvbmZpZ3VyYXRpb24sREM9emVybyxEQz1sYWI/Y0FDZXJ0aWZpY2F0ZT9iYXNl # P29iamVjdENsYXNzPWNlcnRpZmljYXRpb25BdXRob3JpdHkwLgYIKwYBBQUHMAKG # Imh0dHA6Ly9wa2kvY2VydGRhdGEvWmVyb1NDQSgxKS5jcnQwPQYJKwYBBAGCNxUH # BDAwLgYmKwYBBAGCNxUIg7j0P4Sb8nmD8Y84g7C3MobRzXiBJ6HzzB+P2VUCAWQC # AQUwGwYJKwYBBAGCNxUKBA4wDDAKBggrBgEFBQcDAzANBgkqhkiG9w0BAQsFAAOC # AQEAszRRF+YTPhd9UbkJZy/pZQIqTjpXLpbhxWzs1ECTwtIbJPiI4dhAVAjrzkGj # DyXYWmpnNsyk19qE82AX75G9FLESfHbtesUXnrhbnsov4/D/qmXk/1KD9CE0lQHF # Lu2DvOsdf2mp2pjdeBgKMRuy4cZ0VCc/myO7uy7dq0CvVdXRsQC6Fqtr7yob9NbE # OdUYDBAGrt5ZAkw5YeL8H9E3JLGXtE7ir3ksT6Ki1mont2epJfHkO5JkmOI6XVtg # anuOGbo62885BOiXLu5+H2Fg+8ueTP40zFhfLh3e3Kj6Lm/NdovqqTBAsk04tFW9 # Hp4gWfVc0gTDwok3rHOrfIY35TGCAfUwggHxAgEBMFQwPTETMBEGCgmSJomT8ixk # ARkWA0xBQjEUMBIGCgmSJomT8ixkARkWBFpFUk8xEDAOBgNVBAMTB1plcm9TQ0EC # E1gAAAH5oOvjAv3166MAAQAAAfkwCQYFKw4DAhoFAKB4MBgGCisGAQQBgjcCAQwx # CjAIoAKAAKECgAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGC # NwIBCzEOMAwGCisGAQQBgjcCARUwIwYJKoZIhvcNAQkEMRYEFBpbuCYYFw2p/RBg # 4YTO+WNYlDn2MA0GCSqGSIb3DQEBAQUABIIBAKZu+QZYCMak6WLSdR0hqy+Iccop # c8PC92w8PPBHotO9In3qCuMniYLClDJAjPmSS4MaQhazZUvcoz7pzxLi3kjtkbUG # FvTiiwFDsJ8ci65MpvkpwCnEzwdvm1eTYMR9Rl/TLwZrQE5eDDulfSRTU11ZWiYp # cNgsL4uZwNySkFOy/R2gw283X0TZ8/CUNDqnIiD+HkJzsWe5rm6ANJT++lYLDw6u # pTJsz0Z6JLGJU5gxJb9UGmuw/psLI/rHr8VST3MUpiXOTPP0wT5ICTVcKurfJUo+ # We8JI9wkveEF/trvQVNX5Yk9xHDVA13EL3lhpT1aM8x2o4sxCUwa2rLNTiQ= # SIG # End signature block |