2atMonitoring.psm1
#Requires -Version 4.0 -Modules 2atWeb,2atGeneral,2atSql $PSDefaultParameterValues.Clear() Set-StrictMode -Version 2.0 $ErrorActionPreference = 'Stop' Add-Type -Assembly System.Web Import-Module 2atGeneral Import-Module 2atWeb Import-Module 2atSql Import-Module (Join-Path -Path $PSScriptRoot -ChildPath HtmlAgilityPack.dll) [HtmlAgilityPack.HtmlNode]::ElementsFlags.Remove("form") | Out-Null Function RelToAbs { Param( [Parameter(Mandatory=$true)] [ValidateScript({(New-Object System.Uri $_)})] [string]$BaseUrl, [Parameter(Mandatory=$true)] [string]$RelativeUrl, [switch]$NoHtmlDecode ) if (! $NoHtmlDecode) { $RelativeUrl = [System.Web.HttpUtility]::HtmlDecode($RelativeUrl) } if ([System.Uri]::IsWellFormedUriString($RelativeUrl,[System.UriKind]::Absolute)) { return $RelativeUrl } if ([System.Uri]::IsWellFormedUriString($RelativeUrl,[System.UriKind]::Relative)) { $l = (New-Object System.Uri((New-Object System.Uri $BaseUrl), $RelativeUrl)).AbsoluteUri Write-Verbose "Absolute URL is $l" return $l } if ($NoHtmlDecode -and ([Uri]$RelativeUrl).IsAbsoluteUri) { Write-Warning "RelativeUrl is not correctly URL encoded '$RelativeUrl'. If this is a HTTP REDIRECT Location header, this is incorrect server behavior. Retrying with encoded URL." return ([Uri]$RelativeUrl).AbsoluteUri } throw "RelativeUrl is not a valid (absolute or relative) url: '$RelativeUrl'" } Function Step { Param( [Parameter(Mandatory=$true)] [ValidateScript({(New-Object System.Uri $_)})] [string]$Url, [Parameter(Mandatory=$true)] [PSCustomObject]$Session, [string]$Method = 'GET', [ValidateScript({ $_ -is [HashTable] -or $_ -is [System.Collections.Generic.Dictionary[string,string]] })] [object]$FormData ) $res = Get-WebResponse -Url $Url -Method $Method -FormData $FormData -HostIPs $Session.HostIPs -CookieContainer $Session.CookieContainer -Proxy $Session.Proxy -UserAgent 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0; 2AT Monitoring; +http://2at.nl)' -Credentials $Session.Credentials if ($Session.History | ?{ $Url -eq $_.Url -and $Method -eq $_.Method -and $res.ResponseBody -eq $_.ResponseBody }) { $res.WebRequestStatus='LoopDetected' $res.WebRequestStatusDescription="The same URL was already visited on the same Step and received the same ResponseBody. ($Url)" } $Session.History += $res if ($res.WebRequestStatus -eq [System.Net.WebExceptionStatus]::Success) { if ($res.HTTPStatus -In (301, 302, 303, 307)) { $l = RelToAbs -BaseUrl $url -RelativeUrl $res.ResponseHeaders['Location'] -NoHtmlDecode Write-Verbose "REDIRECT ($($res.HTTPStatus)) -> $l" Step -Url $l -Session $Session return } if ($res.HTTPStatus -eq 200) { if ($res.ResponseBody.EndsWith('<noscript><p>Script is disabled. Click Submit to continue.</p><input type="submit" value="Submit" /></noscript></form><script language="javascript">window.setTimeout(''document.forms[0].submit()'', 0);</script></body></html>')) { Write-Verbose "ADFS auto POST" ProcessForm -Previous $res -Session $Session return } $u = (new-object System.Uri $res.Url).GetLeftPart([System.UriPartial]::Path) if ($Session.LoginSteps[$u]) { RunStep -Step $Session.LoginSteps[$u] -Previous $res -Session $Session return } } } } Function ProcessForm { Param( [HashTable]$FormData, [string]$FormId, [Parameter(Mandatory=$true)] [PSCustomObject]$Previous, [Parameter(Mandatory=$true)] [PSCustomObject]$Session ) $htmldoc = New-Object HtmlAgilityPack.HtmlDocument $htmldoc.LoadHtml($Previous.ResponseBody) if ($FormId) { $form = $htmldoc.DocumentNode.SelectNodes("//form[@id='$FormId']")[0] if (!$form) { throw "No form with id='$FormId' found" } } else { $forms = $htmldoc.DocumentNode.SelectNodes('//form') if (!$forms) { throw 'No forms found' } if ($forms.Count -gt 1) { Write-Warning "Found $($forms.Count) forms but no FormId was specified, selecting the first" } $form=$forms[0] } $l = RelToAbs $Previous.Url $form.Attributes['action'].Value $b = New-Object 'System.Collections.Generic.Dictionary[string,string]' $form.SelectNodes('//input') | ?{ $_.Attributes['name'] -And $_.Attributes['value'] } | %{ $b[$_.Attributes['name'].Value] = [System.Web.HttpUtility]::HtmlDecode($_.Attributes['value'].Value) } if ($FormData) { $FormData.Keys | %{ $b[$_]=$FormData[$_] } } Write-Verbose "FORM: $(($b.Keys | %{ $_ + '=' + $b[$_] } ) -join [Environment]::NewLine)" Step -Url $l -Method $form.Attributes['method'].Value -FormData $b -Session $Session } Function ProcessLink { Param( [Parameter(Mandatory=$true)] [string]$LinkText, [Parameter(Mandatory=$true)] [PSCustomObject]$Previous, [Parameter(Mandatory=$true)] [PSCustomObject]$Session, [Parameter(Mandatory=$false)] [string]$LinkType ) $htmldoc = New-Object HtmlAgilityPack.HtmlDocument $htmldoc.LoadHtml($Previous.ResponseBody) if ($LinkType -eq 'IMG') { $links = $htmldoc.DocumentNode.SelectNodes('//img[@src]') | ? { $_ -and $_.Attributes['src'].Value.Contains($LinkText) } | % { $_.Attributes['src'].Value } | select -Unique } else { $links = $htmldoc.DocumentNode.SelectNodes('//a[@href]') | ? { $_ -and $_.InnerText.Trim() -eq $LinkText } | % { $_.Attributes['href'].Value } | select -Unique } $c = ($links | measure).Count if ($c -ne 1) { Write-Warning "Found $c links matching '$LinkText', expected exactly one." $Previous.WebRequestStatus='LinkFailed' $Previous.WebRequestStatusDescription="Found $c links matching '$LinkText', expected exactly one." return } $l = RelToAbs $Previous.Url $links Write-Verbose "LINK: $l" Step -url $l -Session $Session } Function ProcessScript { Param( [Parameter(Mandatory=$true)] [string]$ScriptId, [Parameter(Mandatory=$true)] [string]$ScriptRegEx, [Parameter(Mandatory=$true)] [PSCustomObject]$Previous, [Parameter(Mandatory=$true)] [PSCustomObject]$Session ) $htmldoc = New-Object HtmlAgilityPack.HtmlDocument $htmldoc.LoadHtml($Previous.ResponseBody) $s = $htmldoc.DocumentNode.SelectNodes('//script') | ?{ $_ -and $_.InnerText.Trim() -match [Regex]::Escape($ScriptId) } if (!$s) { throw "No script block matching '$ScriptId' found (using literal match)" } if ($s -is [HtmlAgilityPack.HtmlNodeCollection]) { Write-Warning 'Multiple matching script blocks found, using the first' $s = $s[0] } if (!($s.InnerText.Trim() -match $ScriptRegEx)) { throw "No url could be found: Script block doesn't match '$ScriptRegEx'" } if ($Matches['link']) { $l = RelToAbs $Previous.Url $Matches['link'] } else { if ($Matches.Count -eq 1) { Write-Warning 'No match groups found, using full match as link to follow' $l = RelToAbs $Previous.Url $Matches[0] } else { if ($Matches.Count -gt 2) { Write-Warning 'Found multiple match groups, using first (create a named group ''link'' to specify witch group to use)' } $l = RelToAbs $Previous.Url $Matches[1] } } Step -url $l -Session $Session } Function ProcessSOAP { param( [Parameter(Mandatory=$true)] [string]$Url, [HashTable]$RequestData, [Parameter(Mandatory=$false)] [PSCustomObject]$Previous, [Parameter(Mandatory=$true)] [PSCustomObject]$Session ) $Cred = New-Object System.Management.Automation.PSCredential ("$($session.Credentials.Domain)\$($session.Credentials.username)", (ConvertTo-SecureString $session.Credentials.Password -AsPlainText -Force) ) $FormData = @{ 'SOAP' = $RequestData['Request']; 'SOAPAction' = $RequestData['SOAPAction'] } $res = Get-WebResponse -Url $url -Method 'POST' -Credentials $cred -FormData $FormData -Proxy $Session.Proxy -UserAgent 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0; 2AT Monitoring; +http://2at.nl)' $session.History += $res } Function UpdateSession { Param( [Parameter(Mandatory=$true)] [HashTable]$Step, [Parameter(Mandatory=$true)] [PSCustomObject]$Session, [PSCustomObject]$TMGInfo, [Switch]$IsNewSession ) Write-Debug "Updating $(if($IsNewSession) {'NEW'})session" if (!$IsNewSession -and ($Step['SqlConnection'] -or $Step['Name'])) { throw 'Specifying a SqlConnection or Name is only supported for new sessions' } if ($Step['Proxy']) { $Session.Proxy = New-Object System.Net.WebProxy $Step.Proxy } if ($Step['LoginSteps']) { $Session.LoginSteps = $Step.LoginSteps } if ($Step['Servers']) { if($TMGInfo){ SetSessionCookiesTmg -Session $Session -TMGInfo $TMGInfo -Servers $Step.Servers } if($Step['CookieName'] -and $Step['TargetServer'] -and $Step['CookieUrls']) { SetSessionCookie -Session $Session -TargetServer $Step.TargetServer -CookieName $Step.CookieName -Urls $Step.CookieUrls} } if ($Step['TargetServer']) { $Session.TargetServer = $Step.TargetServer } if ($Step['NextLogMinutes']) { $Session.NextLogMinutes = $Step.NextLogMinutes } if ($Step['HostIPs']) { $Session.HostIPs = $Step.HostIPs } if ($Step['Credentials']) { if ($Step.Credentials -is [PSCredential]) { $Session.Credentials = $Step.Credentials.GetNetworkCredential() } elseif ($Step.Credentials -is [System.Net.ICredentials]) { $Session.Credentials = $Step.Credentials } else { throw "Session credentials specified are of an unsupported type: $($Step.Credentials.GetType().FullName), please use either a PSCredential or a NetworkCredential" } } if ($IsNewSession) { $Session } } Function NewSession { Param( [Parameter(Mandatory=$true)] [HashTable]$Step, [PSCustomObject]$TMGInfo ) $Session = @{ Id=-1 Name=$Step['Name'] HostIPs=@{} CookieContainer = New-Object System.Net.CookieContainer Proxy=$null SqlConnection=$Step['SqlConnection'] WSConnection=$Step['WSConnection'] ReportingConnection = $Step['ReportingConnection'] LoginSteps=@{} Credentials=$null History=@() RequestNumber=1 TargetServer=$null NextLogMinutes=10 } if ($Session.SqlConnection) { Write-Verbose "NEWSESSION: Connecting to SQL '$($Step.SqlConnection)'" Set-SqlData -ConnectionString $Session.SqlConnection -CommandText 'ops.LogJob' -Parameters @{ Job='HTTPMonitor'; Title='Job started' } $MonitorSession = Get-SqlData -ConnectionString $Session.SqlConnection -CommandText 'ops.SaveMonitorSession' -Parameters @{ Name=$Session.Name; Servers=($Step['Servers'] -Join ' / ') } $Session.Id = $MonitorSession.SessionId } if (!$Step['Servers']) { Write-Verbose 'NEWSESSION: New session created without cookies' } UpdateSession -Step $Step -Session ([PSCustomObject]$Session) -TMGInfo $TMGInfo -IsNewSession } Function SetSessionCookie{ Param( [Parameter(Mandatory=$true)] [PSCustomObject]$Session, [Parameter(Mandatory=$true)] [string]$TargetServer, [Parameter(Mandatory=$true)] [string]$CookieName, [Parameter(Mandatory=$true)] [string[]]$Urls ) foreach($url in $Urls) { $cookie = New-Object System.Net.Cookie $CookieName, $TargetServer $cookie.HttpOnly = $true $Session.CookieContainer.Add($url, $cookie) Write-Verbose "Added cookie to session: $cookieName=$TargetServer $url" } } Function SetSessionCookiesTmg { Param( [Parameter(Mandatory=$true)] [PSCustomObject]$Session, [Parameter(Mandatory=$true)] [PSCustomObject]$TMGInfo, [Parameter(Mandatory=$true)] [string[]]$Servers ) $TMGInfo.CookieIndex | %{ $cookieName = $_.Cookie $Url = New-Object System.Uri $_.Url $rule = $TMGInfo.Rules[$cookieName.SubString(7)] if (! $rule) { Write-Warning "Unable to add cookie to session. No matching rule found for cookie '$cookieName' ($Url). Possible cause is an outdated TMGRuleGUIDs.xml file." return } foreach($server in $Servers) { $entry = $rule.ServerFarm | ?{ $_.HostName -Match $server } if ($entry) { break } } if (! $entry) { Write-Warning "Unable to add cookie to session. No matching server found for rule '$($rule.Name)' ($Url)" return } $cookie = New-Object System.Net.Cookie $cookieName, $entry.GUID, '/', $Url.Host $cookie.HttpOnly = $true $Session.CookieContainer.Add($Url, $cookie) Write-Verbose "Added cookie to session: $cookieName=$($entry.GUID) ($Url)" } } Function GetServer { Param ( [Parameter(Mandatory=$true)] [PSCustomObject]$WebResponse ) if ($WebResponse.ResponseHeaders['X-WFE']) { $WebResponse.ResponseHeaders['X-WFE'] } else { $WebResponse.ResponseHeaders['X-Powered-by-server'] } } Function ValidateResponse { Param( [Parameter(Mandatory=$true)] [PSCustomObject]$Response, [Parameter(Mandatory=$true)] [PSCustomObject]$Validate ) #TODO: Transform $Response.WebRequestStatusDescription to hashtable (or xml) if ($Response.WebRequestStatus -ne [System.Net.WebExceptionStatus]::Success) { Write-Verbose "Skipped validation because request already marked as failed ($($Response.WebRequestStatus)" return } if ($Validate['Url']) { if ($Response.Url.TrimEnd('/') -ne $Validate.Url.TrimEnd('/')) { $Response.WebRequestStatus='UrlValidationFailed' $Response.WebRequestStatusDescription="Url validation failed, found '$($Response.Url)', expected '$($Validate.Url)'" Write-Warning $Response.WebRequestStatusDescription return } else { Write-Verbose 'Url validation succeeded' } } if ($Validate['ContentMatch']) { if (! ($Response.ResponseBody -match $Validate.ContentMatch)) { $Response.WebRequestStatus='ContentValidationFailed' $Response.WebRequestStatusDescription="Content validation failed, page '$($Response.Url)' does not match '$($Validate.ContentMatch)'" Write-Warning $Response.WebRequestStatusDescription return } else { Write-Verbose "Content validation succeeded '$($Validate.ContentMatch)'" } } if ($Validate['Time']) { if (! ([int]$Response.TimeToFirstByte.TotalMilliseconds -le [int]$Validate.Time)) { $Response.WebRequestStatus='TimeValidationFailed' $Response.WebRequestStatusDescription="Time exceeded, page took $([int]$Response.TimeToFirstByte.TotalMilliseconds)ms, maximum was set at $([int]$Validate.Time)ms ($($Response.Url))" Write-Warning $Response.WebRequestStatusDescription return } else { Write-Verbose "Time validation succeeded ($([int]$Response.TimeToFirstByte.TotalMilliseconds)ms < $([int]$Validate.Time)ms)" } } if ($Validate['TargetServer']) { $ResponseServer = GetServer -WebResponse $Response if ($Validate.TargetServer -ne $ResponseServer) { $Response.WebRequestStatus='TargetServerValidationFailed' $Response.WebRequestStatusDescription="Response received from other than TargetServer. TargetServer = '$($Validate.TargetServer)'. ResponseServer = '$ResponseServer'." Write-Warning $Response.WebRequestStatusDescription return } else { Write-Verbose "TargetServer validation succeeded. TargetServer = ResponseServer = '$ResponseServer'." } } if ($Validate['ResponseHeader']) { if ( $response.ResponseHeaders[$Validate.ResponseHeader] -ne $Validate.HeaderMatch) { $Response.WebRequestStatus='ResponseHeaderValidationFailed' $Response.WebRequestStatusDescription="Response header is different than expected value ($($response.ResponseHeaders[$Validate.ResponseHeader]), expected $($Validate.HeaderMatch))" Write-Warning $Response.WebRequestStatusDescription return } else { Write-Verbose "Response header validation succeeded. Header has expected value ($($Validate.HeaderMatch))" } } } Function PostEntityUpdate { Param ( [Parameter(Mandatory=$true)] [PSCustomObject]$Session, [string]$Monitor ) $lr = $Session.History[$Session.History.Length-1] $FormData = @{ name = "$Monitor-$($Session.TargetServer)"; webRequestStatus = $lr.WebRequestStatus; nextLogTime = $lr.DateTime.AddMinutes($Session.NextLogMinutes).ToUniversalTime().ToString('u'); logTime = $lr.DateTime.ToUniversalTime().ToString('u'); } if ($lr.WebRequestStatus -ne 'Success') { $information = @{ WebRequestStatusDescription = $lr.WebRequestStatusDescription } $FormData.information = New-Object PSObject -Property $information | ConvertTo-Xml -NoTypeInformation -as String } try { Write-Verbose "Posting to $($Session.WSConnection)" Invoke-RestMethod -Uri $Session.WSConnection -Method 'POST' -Body $FormData -TimeoutSec 5 } catch { #TODO This should become Write-Error once the new monitoring becomes permanent Write-Warning "Entity endpoint unavailable $($_.Exception.Message)" } } Function PostEntityReportData { Param ( [Parameter(Mandatory=$true)] [ValidateScript({(New-Object System.Uri $_)})] [string]$entityReportingUrl, [Parameter(Mandatory=$true)] [string]$entityName, [Parameter(Mandatory=$false)] [datetime]$logTime, [Parameter(Mandatory=$true)] [string]$propertyName, [Parameter(Mandatory=$true)] [int]$propertyValue ) $FormData = @{ Name = $entityName; LogTime = $logTime.ToUniversalTime().ToString('u'); PropertyName = $propertyName; PropertyValue = $propertyValue; } try { Write-Verbose "Posting reportingData to $entityReportingUrl" Invoke-RestMethod -Uri $entityReportingUrl -Method 'POST' -Body $FormData -TimeoutSec 5 } catch { #TODO This may need to become Write-Error once the new monitoring becomes permanent Write-Warning "Entity reporting endpoint unavailable $($_.Exception.Message)" } } Function LogSteps { Param( [Parameter(Mandatory=$true)] [PSCustomObject]$Session, [int] $StepNumber, [string]$Monitor, [switch] $PassThru ) $i=1 foreach($WebResponse in $Session.History) { $u = New-Object System.Uri $WebResponse.Url $hostname = "$($u.Host)" if ($u.Port -ne 80) { $hostname+=":$($u.Port)"} if ($u.Query.Length -ne 0) { $query=$u.Query.Substring(1) } else { $query='' } $p = @{ LogDateTime=$WebResponse.DateTime Host=$hostname UriStem=$u.AbsolutePath UriQuery=$query Method=$WebResponse.Method HTTPStatus=[int]$WebResponse.HTTPStatus TimeTaken=[int]$WebResponse.TimeToFirstByte.TotalMilliseconds RequestStatus=[string]$WebResponse.WebRequestStatus RequestStatusDescription=$WebResponse.WebRequestStatusDescription Url=$WebResponse.Url SessionId=$Session.Id Monitor=$Monitor TargetServer=$Session.TargetServer NextLogMinutes=$Session.NextLogMinutes StepNumber=$StepNumber RequestNumber=$Session.RequestNumber++ IsStepResult=($i -eq $Session.History.Count) } if ($WebResponse.FormData) { $p.FormData=(($WebResponse.FormData.Keys | %{ $_ + '=' + $WebResponse.FormData[$_] } ) -join [Environment]::NewLine) } if ($WebResponse.ResponseHeaders) { $p.Server=GetServer -WebResponse $WebResponse $p.XSharePointHealthScore=$WebResponse.ResponseHeaders['X-SharePointHealthScore'] $p.SPIisLatency=$WebResponse.ResponseHeaders['SPIisLatency'] $p.SPRequestDuration=$WebResponse.ResponseHeaders['SPRequestDuration'] $p.RequestGuid=$WebResponse.ResponseHeaders['request-id'] $p.RawResponse=Get-WebResponseString -WebResponse $WebResponse } Write-Debug "Writing data" if ($Session.SqlConnection) { Write-Verbose "Writing data to the database" Set-SqlData -ConnectionString $Session.SqlConnection -CommandText 'ops.SaveMonitorResult' -Parameters $p } if (!$Session.SqlConnection -And !$Session.WSConnection) { if ($p.HTTPStatus) { Write-Host "$(Get-Date) $($p.Server) ($($p.XSharePointHealthScore)) $($p.HTTPStatus) $($p.Method) $($u.GetLeftPart([System.UriPartial]::Path))" } else { Write-Host "$(Get-Date) $($p.RequestStatus) $($u.GetLeftPart([System.UriPartial]::Path))" } } if ($PassThru) { $WebResponse } $i++ } if ($Session.WSConnection) { Write-Verbose "Posting data to the entity endpoint" PostEntityUpdate -Session $Session -Monitor $Monitor } if ($Session.ReportingConnection) { Write-Verbose "Posting data to the reporting endpoint" $lastIndex = $Session.History.Length-1 if($Session.History[$lastIndex].WebRequestStatus -eq "Success"){ $logTime = $Session.History[$lastIndex].DateTime $propertyName="Response time" $propertyValue=[int]$Session.History[$lastIndex].TimeToFirstByte.TotalMilliseconds PostEntityReportData -entityReportingUrl $Session.ReportingConnection -entityName "$Monitor-$($Session.TargetServer)" -logTime $LogTime -propertyName $propertyName -propertyValue $propertyValue } } } Function RunStep { Param( [Parameter(Mandatory=$true)] [HashTable]$Step, [PSCustomObject]$Previous, [Parameter(Mandatory=$true)] [PSCustomObject]$Session ) switch ($Step.Action) { 'Url' { Write-Debug "URL: $($Step.Url)" Step -Url $Step.Url -Session $Session } 'Link' { Write-Debug "LINK: $($Step.LinkText)" ProcessLink -LinkText $Step.LinkText -Previous $Previous -Session $Session -LinkType $Step['LinkType'] } 'Form' { Write-Debug "FORM" ProcessForm -FormId $Step['FormId'] -FormData $Step['FormData'] -Previous $Previous -Session $Session } 'Script' { Write-Debug 'SCRIPT' ProcessScript -ScriptId $Step['ScriptId'] -ScriptRegEx $Step['ScriptRegEx'] -Previous $Previous -Session $Session } 'SOAP' { Write-Debug 'SOAP' ProcessSOAP -RequestData $Step['RequestData'] -Url $Step['Url'] -Previous $Previous -Session $Session } default { throw "Unrecognized step $($CurrentStep.Action)" } } if ($Step['Validate']) { ValidateResponse -Response $Session.History[$Session.History.Length-1] -Validate $Step.Validate } } Function Invoke-Monitoring { Param( [Parameter(Mandatory=$true)] [System.Array]$Steps, [PSCustomObject]$TMGInfo ) Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $i=0 foreach($CurrentStep in $Steps) { switch ($CurrentStep.Action) { 'NewSession' { Write-Progress -Activity 'Initializing session' -PercentComplete (100*$i/$Steps.Count) Write-Debug 'NEWSESSION' $Session = NewSession -Step $CurrentStep -TMGInfo $TMGInfo $Previous = $null } 'UpdateSession' { Write-Progress -Activity 'Updating session' -PercentComplete (100*$i/$Steps.Count) Write-Debug 'UPDATESESSION' UpdateSession -Step $CurrentStep -TMGInfo $TMGInfo -Session $Session } default { Write-Progress -Activity 'Retrieving webpage' -CurrentOperation $CurrentStep.Url -PercentComplete (100*$i/$Steps.Count) RunStep -Step $CurrentStep -Previous $Previous -Session $Session LogSteps -Session $Session -StepNumber ($i+1) -Monitor $CurrentStep['Monitor'] -PassThru $Previous = $Session.History | Select-Object -Last 1 $Session.History=@() } } $i++ } } Export-ModuleMember -Function Invoke-* # SIG # Begin signature block # MIIhcwYJKoZIhvcNAQcCoIIhZDCCIWACAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDxi+sJ9pc8IUqt # t1PmPusSv6kz7fMSKPuo+SX94jvzMKCCCxswggUzMIIEG6ADAgECAhEAgNHe/U3D # BzyckFGAgIDcJDANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJHQjEbMBkGA1UE # CBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQK # ExFDT01PRE8gQ0EgTGltaXRlZDEjMCEGA1UEAxMaQ09NT0RPIFJTQSBDb2RlIFNp # Z25pbmcgQ0EwHhcNMTcwMTEzMDAwMDAwWhcNMjAwMTEzMjM1OTU5WjCBgDELMAkG # A1UEBhMCTkwxEDAOBgNVBBEMBzM1NDIgRFoxEDAOBgNVBAgMB1V0cmVjaHQxEDAO # BgNVBAcMB1V0cmVjaHQxFTATBgNVBAkMDEVuZXJnaWV3ZWcgMTERMA8GA1UECgwI # MkFUIEIuVi4xETAPBgNVBAMMCDJBVCBCLlYuMIIBIjANBgkqhkiG9w0BAQEFAAOC # AQ8AMIIBCgKCAQEAzB3KZ2CBenaD2WDwOsy0cHE6mLIeIYqWP718FuWeUZ5eejvw # 8BozajbtBWgISZ2IMsTYZ1I7KFBzHgXXkNglmyboa6++x7j2Ws+T0hmHCUZ64AFb # OkXjqYsOBCPhi3yuKIRLwc4snA3F3DCH24mBpDYymrU22+0vMIlDqpzRXBNEeIhG # ss3jehu86l85fWVS54F5KGeDYQ2BT0Tc0UO6hMlcpCEVKIbthLm36q1/oSchRYjH # B4JCT1KqACRhD0hJcQmTcJZvhpgOrglUVlj1ClS5xfWgHq3ySShOOZMecl0VNMtY # xNi5TF1Ae+sie4044ioyGB6dGItGXwhObIk/9wIDAQABo4IBqDCCAaQwHwYDVR0j # BBgwFoAUKZFg/4pN+uv5pmq4z/nmS71JzhIwHQYDVR0OBBYEFDHc2o80OMg8zNfF # WMH8QB57E7rnMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQM # MAoGCCsGAQUFBwMDMBEGCWCGSAGG+EIBAQQEAwIEEDBGBgNVHSAEPzA9MDsGDCsG # AQQBsjEBAgEDAjArMCkGCCsGAQUFBwIBFh1odHRwczovL3NlY3VyZS5jb21vZG8u # bmV0L0NQUzBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsLmNvbW9kb2NhLmNv # bS9DT01PRE9SU0FDb2RlU2lnbmluZ0NBLmNybDB0BggrBgEFBQcBAQRoMGYwPgYI # KwYBBQUHMAKGMmh0dHA6Ly9jcnQuY29tb2RvY2EuY29tL0NPTU9ET1JTQUNvZGVT # aWduaW5nQ0EuY3J0MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21vZG9jYS5j # b20wGQYDVR0RBBIwEIEOc3VwcG9ydEAyYXQubmwwDQYJKoZIhvcNAQELBQADggEB # AHGDJyOKLJwzdt4Y8ow7H4ZKZXs9Hopf0GhizzhcPWyWL7GI6QHhKHzFWYGsFhh2 # vesuY7p89jthK5YqSn1u2KUQuLWzQZQj3cZCK2BwSz6FpgmmjqIo49qCfKIB5IrE # DcZAQPC9wxaXPI+R3B32JmTllBpkFQNTIJVcB7jR/Ft991iV17tMMq0GssMAHnVd # /yvTWlUaE7XNtgtNYQ5v/8HxxNtdBXsIbdjiv/A8GjUmyPN8Dum9CW82hUqOE7U9 # AXHZIBWy9yrooSieo26GA1OzrBvnDc+L42JZnjvwdhBqSnbQrSS7L6VjVHU+Ct84 # Fnb5u23Jypdmj9123Hw9qJwwggXgMIIDyKADAgECAhAufIfMDpNKUv6U/Ry3zTSv # MA0GCSqGSIb3DQEBDAUAMIGFMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRl # ciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8g # Q0EgTGltaXRlZDErMCkGA1UEAxMiQ09NT0RPIFJTQSBDZXJ0aWZpY2F0aW9uIEF1 # dGhvcml0eTAeFw0xMzA1MDkwMDAwMDBaFw0yODA1MDgyMzU5NTlaMH0xCzAJBgNV # BAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAOBgNVBAcTB1Nh # bGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMSMwIQYDVQQDExpDT01P # RE8gUlNBIENvZGUgU2lnbmluZyBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC # AQoCggEBAKaYkGN3kTR/itHd6WcxEevMHv0xHbO5Ylc/k7xb458eJDIRJ2u8UZGn # z56eJbNfgagYDx0eIDAO+2F7hgmz4/2iaJ0cLJ2/cuPkdaDlNSOOyYruGgxkx9hC # oXu1UgNLOrCOI0tLY+AilDd71XmQChQYUSzm/sES8Bw/YWEKjKLc9sMwqs0oGHVI # wXlaCM27jFWM99R2kDozRlBzmFz0hUprD4DdXta9/akvwCX1+XjXjV8QwkRVPJA8 # MUbLcK4HqQrjr8EBb5AaI+JfONvGCF1Hs4NB8C4ANxS5Eqp5klLNhw972GIppH4w # vRu1jHK0SPLj6CH5XkxieYsCBp9/1QsCAwEAAaOCAVEwggFNMB8GA1UdIwQYMBaA # FLuvfgI9+qbxPISOre44mOzZMjLUMB0GA1UdDgQWBBQpkWD/ik366/mmarjP+eZL # vUnOEjAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADATBgNVHSUE # DDAKBggrBgEFBQcDAzARBgNVHSAECjAIMAYGBFUdIAAwTAYDVR0fBEUwQzBBoD+g # PYY7aHR0cDovL2NybC5jb21vZG9jYS5jb20vQ09NT0RPUlNBQ2VydGlmaWNhdGlv # bkF1dGhvcml0eS5jcmwwcQYIKwYBBQUHAQEEZTBjMDsGCCsGAQUFBzAChi9odHRw # Oi8vY3J0LmNvbW9kb2NhLmNvbS9DT01PRE9SU0FBZGRUcnVzdENBLmNydDAkBggr # BgEFBQcwAYYYaHR0cDovL29jc3AuY29tb2RvY2EuY29tMA0GCSqGSIb3DQEBDAUA # A4ICAQACPwI5w+74yjuJ3gxtTbHxTpJPr8I4LATMxWMRqwljr6ui1wI/zG8Zwz3W # GgiU/yXYqYinKxAa4JuxByIaURw61OHpCb/mJHSvHnsWMW4j71RRLVIC4nUIBUzx # t1HhUQDGh/Zs7hBEdldq8d9YayGqSdR8N069/7Z1VEAYNldnEc1PAuT+89r8dRfb # 7Lf3ZQkjSR9DV4PqfiB3YchN8rtlTaj3hUUHr3ppJ2WQKUCL33s6UTmMqB9wea1t # QiCizwxsA4xMzXMHlOdajjoEuqKhfB/LYzoVp9QVG6dSRzKp9L9kR9GqH1NOMjBz # wm+3eIKdXP9Gu2siHYgL+BuqNKb8jPXdf2WMjDFXMdA27Eehz8uLqO8cGFjFBnfK # S5tRr0wISnqP4qNS4o6OzCbkstjlOMKo7caBnDVrqVhhSgqXtEtCtlWdvpnncG1Z # +G0qDH8ZYF8MmohsMKxSCZAWG/8rndvQIMqJ6ih+Mo4Z33tIMx7XZfiuyfiDFJN2 # fWTQjs6+NX3/cjFNn569HmwvqI8MBlD7jCezdsn05tfDNOKMhyGGYf6/VXThIXcD # Cmhsu+TJqebPWSXrfOxFDnlmaOgizbjvmIVNlhE8CYrQf7woKBP7aspUjZJczcJl # mAaezkhb1LU3k0ZBfAfdz/pD77pnYf99SeC7MH1cgOPmFjlLpzGCFa4wghWqAgEB # MIGSMH0xCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIx # EDAOBgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMSMw # IQYDVQQDExpDT01PRE8gUlNBIENvZGUgU2lnbmluZyBDQQIRAIDR3v1Nwwc8nJBR # gICA3CQwDQYJYIZIAWUDBAIBBQCgfDAQBgorBgEEAYI3AgEMMQIwADAZBgkqhkiG # 9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIB # FTAvBgkqhkiG9w0BCQQxIgQgiD8nHVbcMkJHpwPpoKXVi1kBSTDhI4lEJ63qocO1 # fuIwDQYJKoZIhvcNAQEBBQAEggEAbQ1aKDBpMGRqFA6FYVo8HoLTErQFRE9QER3Z # JdQj3YIPhsV69UZMibxz5G8mc19VAiUOwxtYa/Tvjj+WmhobTvQ6QpX02K4ysl2C # LT/4h1sZlqnk0MF0qO4dAjQDkVhynMejQtehVvxWVI373kbthN9ESPORNqCMaYkg # diE0EeSrDQXi8q3mss9e40uw5NPgNAs48JKqNXhtXedypPUOPx1NqH/tSPGbm5Iq # JJjH6ubdOrS0fn4h8T37pDpl6ieNZYWws1sB3zdDcI7IlyiZKYMUjHGrNz/4LwCi # fyoFpOTaBevHyED6LSbtYTsNoBejRDnXElCzO5sq4Ky8N3eiuKGCE24wghNqBgor # BgEEAYI3AwMBMYITWjCCE1YGCSqGSIb3DQEHAqCCE0cwghNDAgEDMQ8wDQYJYIZI # AWUDBAIBBQAwggENBgsqhkiG9w0BCRABBKCB/QSB+jCB9wIBAQYKKwYBBAGyMQIB # ATAxMA0GCWCGSAFlAwQCAQUABCCfCVqPVzbfopfTIfUAXiyQ2ejhY+6MRJH+a6kW # 8b8JVAIVAO/UjWlcTJWSOfZC89+31/MFNYDNGA8yMDE5MDUyMzEwMzAwMlqggYqk # gYcwgYQxCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1hbmNoZXN0ZXIx # EDAOBgNVBAcMB1NhbGZvcmQxGDAWBgNVBAoMD1NlY3RpZ28gTGltaXRlZDEsMCoG # A1UEAwwjU2VjdGlnbyBSU0EgVGltZSBTdGFtcGluZyBTaWduZXIgIzGggg36MIIH # BjCCBO6gAwIBAgIQPRo1cjAVgmMw0BNxfoJBCDANBgkqhkiG9w0BAQwFADB9MQsw # CQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQH # EwdTYWxmb3JkMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxJTAjBgNVBAMTHFNl # Y3RpZ28gUlNBIFRpbWUgU3RhbXBpbmcgQ0EwHhcNMTkwNTAyMDAwMDAwWhcNMzAw # ODAxMjM1OTU5WjCBhDELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFu # Y2hlc3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEYMBYGA1UECgwPU2VjdGlnbyBMaW1p # dGVkMSwwKgYDVQQDDCNTZWN0aWdvIFJTQSBUaW1lIFN0YW1waW5nIFNpZ25lciAj # MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMtRUP9W/vx4Y3ABk1qe # GPQ7U/YHryFs9aIPfR1wLYR0SIucipUFPVmE+ZGAeVEs2Yq3wQuaugqKzWZPA4sB # uzDKq73bwE8SXvwKzOJFsAE4irtN59QcVJjtOVjPW8IvRZgxCvk1OLgxLm20Hjly # 4bgqvp+MjBqlRq4LK0yZ/ixL/Ci5IjpmF9CqVoohwPOWJLTQhSZruvBvZJh5pq29 # XNhTaysK1nKKhUbjDRgG2sZ7QVY2mxU+8WoRoPdm9RjQgFVjh2hm6w55VYJco+1J # uHGGnpM3sGuj6mJso66W6Ln9i6vG9llbADxXIBgtcAOnnO+S63mhx13sfLSPS9/r # XfyjIN2SOOVqUTprhZxMoJgIaVsG5yoZ0JWTiztrigUJKdjW2tvjcvpcSi97FVaG # Mr9/BQmdLSrPUOHmYSDbxwaAXE4URr6uV3Giqmwwkxx+d8sG6VfNkfXVM3Ic4drK # buvzD+x5W7snnuge/i/yu3/p5dBn67gNfKQrWQOLle0iKM36LDvHFhGv49axUGdp # xY71edCt/4fM+H+q+aLtYfjIjWnasfRRketnV9FkEetkywO9SVU6RUMYLCVs0S8M # LW/1QTUkoPJjWRZf2aTpLE7buzESxm34W24D3MsVjxuNcuzbDxWQ1hJO7uIAMSWT # NW9qW6USY0ABirlpiDqIuA8ZAgMBAAGjggF4MIIBdDAfBgNVHSMEGDAWgBQaofhh # GSAPw0F3RSiO0TVfBhIEVTAdBgNVHQ4EFgQUb02GB9gyJ54sKdLQEwOAgd0Fgykw # DgYDVR0PAQH/BAQDAgbAMAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYB # BQUHAwgwQAYDVR0gBDkwNzA1BgwrBgEEAbIxAQIBAwgwJTAjBggrBgEFBQcCARYX # aHR0cHM6Ly9zZWN0aWdvLmNvbS9DUFMwRAYDVR0fBD0wOzA5oDegNYYzaHR0cDov # L2NybC5zZWN0aWdvLmNvbS9TZWN0aWdvUlNBVGltZVN0YW1waW5nQ0EuY3JsMHQG # CCsGAQUFBwEBBGgwZjA/BggrBgEFBQcwAoYzaHR0cDovL2NydC5zZWN0aWdvLmNv # bS9TZWN0aWdvUlNBVGltZVN0YW1waW5nQ0EuY3J0MCMGCCsGAQUFBzABhhdodHRw # Oi8vb2NzcC5zZWN0aWdvLmNvbTANBgkqhkiG9w0BAQwFAAOCAgEAwGjts9jUUJvv # 03XLDzv3JNN6N0WNLO8W+1GpLB+1JbWKn10LwhsgdI1mDzbLqvY2DQ9+j0tKdENl # rA0q9grta23FCTjtABv45dymCkAFR++Eygm8Q2aDv5/t24490UFksXACLQNXWxhv # HCzLHrIA6LoJL1uBBDW5qWNtjgjFGNHhIaz5EgoUwBLbfiWdrB0QwFqlg9IfGmZV # /Jsq4uw3V47l35Yw+MCTC0MY+QJvqVGvuFcK8xwHaTmPN5xt15GupS5J6Ures9CM # vzmQDcCBzvAqBzoMpi1R0nLzU8b5ve/vDGlJd58sVsTpoQg9B67FHtaEIse8fUMb # WDhiTtEFJYTFQvgfL/bb+quMVOxFimwSTTBaUuWkFwki5u9v9V+GQ9+hLb1KRpKg # gZYsYZd/QG/YP4w1WqvRxqA7hWZUgO8fGvXxm7ChJ32y5wvP9i2cWBOUqYb8RVKi # KG1/dA9SkUl66RL4qTuwkv19kRTpW21IlPLIlu4FOLPF7DA/4QcgBLHYi7z9sz5v # 8gJTBvSg7cmacqOXXwD7y2PQ6M10/XXJ1DZFunsSWXLt5/J6UAB4+EOaRtjfv1TU # XrHH0bwbg/Qr5wvoR8hTnswarPb6inVTbCCFqdW4arokjoorCJGfNwQc9m+i3TSq # kf/GFS4eQhoJKU/0xs3ikaLTQAyOeOMwggbsMIIE1KADAgECAhAwD2+s3WaYdHyp # RjaneC25MA0GCSqGSIb3DQEBDAUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK # TmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBV # U0VSVFJVU1QgTmV0d29yazEuMCwGA1UEAxMlVVNFUlRydXN0IFJTQSBDZXJ0aWZp # Y2F0aW9uIEF1dGhvcml0eTAeFw0xOTA1MDIwMDAwMDBaFw0zODAxMTgyMzU5NTla # MH0xCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO # BgNVBAcTB1NhbGZvcmQxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDElMCMGA1UE # AxMcU2VjdGlnbyBSU0EgVGltZSBTdGFtcGluZyBDQTCCAiIwDQYJKoZIhvcNAQEB # BQADggIPADCCAgoCggIBAMgbAa/ZLH6ImX0BmD8gkL2cgCFUk7nPoD5T77NawHbW # GgSlzkeDtevEzEk0y/NFZbn5p2QWJgn71TJSeS7JY8ITm7aGPwEFkmZvIavVcRB5 # h/RGKs3EWsnb111JTXJWD9zJ41OYOioe/M5YSdO/8zm7uaQjQqzQFcN/nqJc1zjx # FrJw06PE37PFcqwuCnf8DZRSt/wflXMkPQEovA8NT7ORAY5unSd1VdEXOzQhe5cB # lK9/gM/REQpXhMl/VuC9RpyCvpSdv7QgsGB+uE31DT/b0OqFjIpWcdEtlEzIjDzT # FKKcvSb/01Mgx2Bpm1gKVPQF5/0xrPnIhRfHuCkZpCkvRuPd25Ffnz82Pg4wZytG # tzWvlr7aTGDMqLufDRTUGMQwmHSCIc9iVrUhcxIe/arKCFiHd6QV6xlV/9A5VC0m # 7kUaOm/N14Tw1/AoxU9kgwLU++Le8bwCKPRt2ieKBtKWh97oaw7wW33pdmmTIBxK # lyx3GSuTlZicl57rjsF4VsZEJd8GEpoGLZ8DXv2DolNnyrH6jaFkyYiSWcuoRsDJ # 8qb/fVfbEnb6ikEk1Bv8cqUUotStQxykSYtBORQDHin6G6UirqXDTYLQjdprt9v3 # GEBXc/Bxo/tKfUU2wfeNgvq5yQ1TgH36tjlYMu9vGFCJ10+dM70atZ2h3pVBeqeD # AgMBAAGjggFaMIIBVjAfBgNVHSMEGDAWgBRTeb9aqitKz1SA4dibwJ3ysgNmyzAd # BgNVHQ4EFgQUGqH4YRkgD8NBd0UojtE1XwYSBFUwDgYDVR0PAQH/BAQDAgGGMBIG # A1UdEwEB/wQIMAYBAf8CAQAwEwYDVR0lBAwwCgYIKwYBBQUHAwgwEQYDVR0gBAow # CDAGBgRVHSAAMFAGA1UdHwRJMEcwRaBDoEGGP2h0dHA6Ly9jcmwudXNlcnRydXN0 # LmNvbS9VU0VSVHJ1c3RSU0FDZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDB2Bggr # BgEFBQcBAQRqMGgwPwYIKwYBBQUHMAKGM2h0dHA6Ly9jcnQudXNlcnRydXN0LmNv # bS9VU0VSVHJ1c3RSU0FBZGRUcnVzdENBLmNydDAlBggrBgEFBQcwAYYZaHR0cDov # L29jc3AudXNlcnRydXN0LmNvbTANBgkqhkiG9w0BAQwFAAOCAgEAbVSBpTNdFuG1 # U4GRdd8DejILLSWEEbKw2yp9KgX1vDsn9FqguUlZkClsYcu1UNviffmfAO9Aw63T # 4uRW+VhBz/FC5RB9/7B0H4/GXAn5M17qoBwmWFzztBEP1dXD4rzVWHi/SHbhRGdt # j7BDEA+N5Pk4Yr8TAcWFo0zFzLJTMJWk1vSWVgi4zVx/AZa+clJqO0I3fBZ4OZOT # lJux3LJtQW1nzclvkD1/RXLBGyPWwlWEZuSzxWYG9vPWS16toytCiiGS/qhvWiVw # YoFzY16gu9jc10rTPa+DBjgSHSSHLeT8AtY+dwS8BDa153fLnC6NIxi5o8JHHfBd # 1qFzVwVomqfJN2Udvuq82EKDQwWli6YJ/9GhlKZOqj0J9QVst9JkWtgqIsJLnfE5 # XkzeSD2bNJaaCV+O/fexUpHOP4n2HKG1qXUfcb9bQ11lPVCBbqvw0NP8srMftpmW # JvQ8eYtcZMzN7iea5aDADHKHwW5NWtMe6vBE5jJvHOsXTpTDeGUgOw9Bqh/poUGd # /rG4oGUqNODeqPk85sEwu8CgYyz8XBYAqNDEf+oRnR4GxqZtMl20OAkrSQeq/eww # 2vGnL8+3/frQo4TZJ577AWZ3uVYQ4SBuxq6x+ba6yDVdM3aO8XwgDCp3rrWiAoa6 # Ke60WgCxjKvj+QrJVF3UuWp0nr1IrpgxggQcMIIEGAIBATCBkTB9MQswCQYDVQQG # EwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxm # b3JkMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxJTAjBgNVBAMTHFNlY3RpZ28g # UlNBIFRpbWUgU3RhbXBpbmcgQ0ECED0aNXIwFYJjMNATcX6CQQgwDQYJYIZIAWUD # BAIBBQCgggFbMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAcBgkqhkiG9w0B # CQUxDxcNMTkwNTIzMTAzMDAyWjAvBgkqhkiG9w0BCQQxIgQg3ac2vX+m0F5QHUph # Tz5wr5G8phKnD36KcfT0F7BD0gYwge0GCyqGSIb3DQEJEAIMMYHdMIHaMIHXMBYE # FCXIrHNOSFC3+NkTkagbkkk2ZZ9hMIG8BBQC1luV4oNwwVcAlfqI+SPdk3+tjzCB # ozCBjqSBizCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDAS # BgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdv # cmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3Jp # dHkCEDAPb6zdZph0fKlGNqd4LbkwDQYJKoZIhvcNAQEBBQAEggIAFNTJXoyTg8jP # OgF2OK2jvc33XAHF3I+2nDVCd7hKIX8y+OpaZ4UPk7O2NDsBKlfEgu2XaQ/JS3IX # K0YW7kU2oyRNopUNYNSfsWcPmO4xAUDoWSljL1t75PlymfpfJQbBgNT7KJbHQtfR # Pkp67cLcX1ZNhSPuAu44UEsgc/tHrhiGXEqi7j9vv5BqF2lk1tT/iBtQtFyx9Flh # degOWLDAcLkiqihtePJzOJ8MUmwj/cA1vCwgJc/Nem6RFNUzNR/5qHr+PVs6Cn1d # 0iZlDWOGQuPtBoCjNvDBGQ/OuIcyOrf/IjyI0nwZCiamuDNJSNta32fXGVrYv8uH # zzEznAA4uOHmJqA8rTy0Rj06zhTEV4zEMm32c5DGye8Md4yfPEEDWIi4/UN4pE+P # L7QIUMappENSKwYOEuWSd6gMCVhQFpJeg5EU2G4pP8emAsRbvAF3Jw//R8We2WmK # 5hAc1evFLP3l9xBWeoHPBSH5A+INNookggwP7ef0QqvOuSrRTOXOX7lWnn83666b # 09bo4/0Aa5cNfKeE3HLulEcyWUNWgWil7Nkb10dhGHV1ihTkEKfn89UVLV4eLU5O # l7b9G6Eie3deLHVymSODNUjYHKRyYVKE0gGCJVRQ15U6+j39xoUF32hneI39ENAo # Iat0of6i8AC0zQT+vufKy2keD48EouE= # SIG # End signature block |