BW.Utils.PSCron.psm1
using namespace System.Management.Automation using namespace System.Collections using namespace Microsoft.PowerShell $__ScriptPath = Split-Path (Get-Variable MyInvocation -Scope Script).Value.Mycommand.Definition -Parent Add-Type -Path "$__ScriptPath\lib\Cronos-0.7.0\netstandard2.0\Cronos.dll" # .ExternalHelp BW.Utils.PSCron-help.xml function Get-PSCronDate { [OutputType( [PSCronDateTime] )] param( [Parameter(Position=1)] [datetime] $Date = ( Get-Date ), [Parameter(Position=2)] [PSCronTicks] $Resolution = [PSCronTicks]::Minute ) return [PSCronDateTime]::new( $Date, $Resolution ) } # .ExternalHelp BW.Utils.PSCron-help.xml function Test-PSCronShouldRun { [OutputType( [bool] )] [CmdletBinding()] param( [Parameter( Mandatory, Position=1 )] [string] $Schedule, [Parameter( Position=2 )] [PSCronDateTime] $ReferenceDate = ( Get-PSCronDate ), [Parameter( ValueFromRemainingArguments, DontShow )] $IgnoredArguments ) $ThisRun = Get-PSCronNextRun -Schedule $Schedule -ReferenceDate $ReferenceDate -Inclusive return $ThisRun -eq $ReferenceDate } # .ExternalHelp BW.Utils.PSCron-help.xml function Get-PSCronNextRun { [OutputType( [PSCronDateTime] )] [CmdletBinding()] param( [Parameter(Mandatory, Position=1)] [string] $Schedule, [Parameter( Position=2 )] [PSCronDateTime] $ReferenceDate = ( Get-PSCronDate ), [switch] $Inclusive, [Parameter( ValueFromRemainingArguments, DontShow )] $IgnoredArguments ) $Offset = $ReferenceDate.Local - $ReferenceDate.Utc $ReferenceDate = $ReferenceDate + $Offset $CronSchedule = [Cronos.CronExpression]::Parse( $Schedule ) [PSCronDateTime]$NextRun = $CronSchedule.GetNextOccurrence( $ReferenceDate.Utc, [System.TimeZoneInfo]::Utc, $Inclusive ) return ( $NextRun - $Offset ) } # .ExternalHelp BW.Utils.PSCron-help.xml function Get-PSCronSchedule { [OutputType( [PSCronDateTime[]] )] param( [Parameter(Mandatory, Position=1)] [string] $Schedule, [Parameter(Position=2)] [PSCronDateTime] $Start = ( Get-PSCronDate -Resolution Day ), [Parameter(Position=3)] [PSCronDateTime] $End = ( Get-PSCronDate -Date (Get-Date).AddDays( 1 ) -Resolution Day ), [switch] $IncludeStart, [switch] $IncludeEnd, [Parameter( ValueFromRemainingArguments, DontShow )] $IgnoredArguments ) $CronSchedule = [Cronos.CronExpression]::Parse( $Schedule ) return [PSCronDateTime[]]$CronSchedule.GetOccurrences( $Start.Utc, $End.Utc, $IncludeStart, $IncludeEnd ) } # .ExternalHelp BW.Utils.PSCron-help.xml function Invoke-PSCronJob { [CmdletBinding( DefaultParameterSetName='ScriptBlock' )] param( [Parameter( Mandatory, Position=1 )] [string] $Schedule, [Parameter( Mandatory, Position=2 )] [string] $Name, [Parameter( Mandatory, Position=3, ParameterSetName='ScriptBlock' )] [scriptblock] $Definition, [Parameter( Mandatory, ParameterSetName='File' )] [string] $FilePath, [string] $Description, [string] $WorkingDirectory, [string] $LogPath, [switch] $Append, [int] $TimeOut, [ActionPreference] $JobInformationPreference, [ActionPreference] $JobDebugPreference, [ActionPreference] $JobWarningPreference, [ActionPreference] $JobErrorActionPreference, [PSCronDateTime] $ReferenceDate, [switch] $PassThru ) if ( -not( Test-PSCronShouldRun @PSBoundParameters ) ) { Write-Verbose ( 'SKIPPING: ' + $Name ) return } # resolve -FilePath to a full path if ( $PSBoundParameters.ContainsKey( 'FilePath' ) ) { $PSBoundParameters['FilePath'] = $FilePath = Resolve-Path $FilePath -ErrorAction Stop | Select-Object -ExpandProperty Path } # resolve -WorkingDirectory to a full path if ( $PSBoundParameters.ContainsKey( 'WorkingDirectory' ) ) { $PSBoundParameters['WorkingDirectory'] = $WorkingDirectory = Resolve-Path $WorkingDirectory -ErrorAction Stop | Select-Object -ExpandProperty Path } # resolve -LogPath to a full path if ( $PSBoundParameters.ContainsKey( 'LogPath' ) ) { $LogFile = Split-Path $LogPath -Leaf $LogDirectory = Split-Path $LogPath -Parent $PSBoundParameters['LogPath'] = $LogPath = Resolve-Path $LogDirectory -ErrorAction Stop | Select-Object -ExpandProperty Path | ForEach-Object { $LogDirectory = $_ Join-Path $LogDirectory $LogFile } } # initialize a cron result object $CronJob = [PSCronJobObject]::new( $PSBoundParameters ) $CronJob.Source = $PSCmdlet.ParameterSetName # create a powershell runspace $PowerShell = [PowerShell]::Create( [RunspaceMode]::NewRunspace ) # create events for logging streams Register-ObjectEvent -InputObject $PowerShell.Streams.Information -EventName DataAdded -Action { New-Event -SourceIdentifier 'PSCronLog:Info' -MessageData $Event.Sender[-1].MessageData } > $null Register-ObjectEvent -InputObject $PowerShell.Streams.Verbose -EventName DataAdded -Action { New-Event -SourceIdentifier 'PSCronLog:Verbose' -MessageData $Event.Sender[-1].Message } > $null Register-ObjectEvent -InputObject $PowerShell.Streams.Debug -EventName DataAdded -Action { New-Event -SourceIdentifier 'PSCronLog:Debug' -MessageData $Event.Sender[-1].Message } > $null Register-ObjectEvent -InputObject $PowerShell.Streams.Warning -EventName DataAdded -Action { New-Event -SourceIdentifier 'PSCronLog:Warning' -MessageData $Event.Sender[-1].Message } > $null Register-ObjectEvent -InputObject $PowerShell.Streams.Error -EventName DataAdded -Action { New-Event -SourceIdentifier 'PSCronLog:Error' -MessageData ( '{0}: {1}' -f $Event.Sender[-1].FullyQualifiedErrorId, $Event.Sender[-1].Exception.Message ) } > $null # create an init script for default output settings [ArrayList]$StreamPreferences = @( "`$Global:ProgressPreference = 'SilentlyContinue'" "`$Global:InformationPreference = '$($CronJob.JobInformationPreference)'" "`$Global:DebugPreference = '$($CronJob.JobDebugPreference)'" "`$Global:WarningPreference = '$($CronJob.JobWarningPreference)'" "`$Global:ErrorActionPreference = '$($CronJob.JobErrorActionPreference)'" ) # if a working directory is provided we switch to that location in the init script if ( $CronJob.WorkingDirectory ) { $StreamPreferences.Add( "Set-Location -Path '$($CronJob.WorkingDirectory)' -ErrorAction Stop" ) > $null } # we add a variable to the $StreamPreferences with the $File name if ( $CronJob.FilePath ) { $StreamPreferences.Add( "`$Global:PSCronFile = '$($CronJob.FilePath)'" ) > $null } # add the init script $InitScript = [scriptblock]::Create( ( $StreamPreferences | Out-String ) ) $PowerShell.AddScript( $InitScript, $true ) > $null # add the script $PowerShell.AddScript( $CronJob.Definition, $true ) > $null # collection for output # note: input cannot be assigned directly to the PSCronJobObject.Output property # because of the variable reference scope $Output = New-Object 'System.Management.Automation.PSDataCollection[psobject]' # run the script $Handle = $PowerShell.BeginInvoke( $Output, $Output ) # wait for completion while ( -not $Handle.IsCompleted ) { Start-Sleep -Milliseconds 500 # kill the job? if ( ( (Get-Date) - ([datetime]$CronJob.StartDate) ).TotalSeconds -gt $CronJob.TimeOut ) { Write-Warning ( '{0} has timed out, the job was stopped after {1} seconds' -f $CronJob.Name, $CronJob.TimeOut ) $PowerShell.RunSpace.Dispose() > $null $PowerShell.Stop() > $null } } # record the results $CronJob.Output = $Output $CronJob.State = $PowerShell.InvocationStateInfo.State $CronJob.HadErrors = $PowerShell.HadErrors # end timestamp $CronJob.EndDate = Get-PSCronDate -Resolution Millisecond # calculate the job runtime $CronJob.RunTime = $CronJob.EndDate - $CronJob.StartDate # write status to the screen in case job is run interactively ''.PadRight( 80, '-' ), ( 'Name: ' + $CronJob.Name ), ( 'Description: ' + $CronJob.Description ), ( 'Schedule: ' + $CronJob.Schedule ), ( 'Reference Date: ' + $CronJob.ReferenceDate ), ( 'Started: ' + $CronJob.StartDate ), ( 'Finished: ' + $CronJob.EndDate ), ( 'Elapsed: {0} seconds' -f $CronJob.RunTime.TotalSeconds ), ( 'Result: ' + $PowerShell.InvocationStateInfo.State ), ( 'Errors: ' + $PowerShell.HadErrors ), ''.PadRight( 80, '-' ) | ForEach-Object { $CronJob.LogRaw( $_ ) } # dump the job information streams collected by the events above $InfoStreamIndex = 0 Get-Event -SourceIdentifier 'PSCronLog:*' | Select-Object TimeGenerated, @{N='OutputStream';E={$_.SourceIdentifier.Split(':')[1].ToUpper()}}, MessageData | ForEach-Object { # do some hacky shit since the info events contain all the output # for some reason if ( $_.OutputStream -eq 'INFO' ) { # get the corresponding info object from the RunSpace $RunSpaceInfo = $PowerShell.Streams.Information[ $InfoStreamIndex ] # replace the MessageData $_.MessageData = $RunSpaceInfo.MessageData # if the $RunSpaceInfo has the 'PSHOST' tag it's from Write-Host, # change the OutputStream to 'HOST' if ( $RunSpaceInfo.Tags -contains 'PSHOST' ) { $_.OutputStream = 'HOST' } # increment the counter $InfoStreamIndex ++ } # send to JobLog $CronJob.LogMessage( $_.TimeGenerated, $_.OutputStream, $_.MessageData ) } # if there are non-terminating errors attach them if ( $PowerShell.Streams.Error.Count -gt 0 ) { $CronJob.Errors = $PowerShell.Streams.Error | ForEach-Object { $_ } } # if there is a TerminatingError attach to the log file if ( $PowerShell.InvocationStateInfo.Reason -is [Exception] ) { $CronJob.LogMessage( $CronJob.EndDate, 'ERROR', $PowerShell.InvocationStateInfo.Reason.ToString() ) $CronJob.TerminatingError = $PowerShell.InvocationStateInfo.Reason } # clean up events Get-Event -SourceIdentifier 'PSCronLog:*' | Remove-Event # clean up the runspace $PowerShell.RunSpace.Dispose() > $null $PowerShell.Dispose() > $null # pass through the job? if ( $PassThru ) { $CronJob } } # .ExternalHelp BW.Utils.PSCron-help.xml function Send-PSCronNotification { param( [Parameter( Mandatory, ValueFromPipeline )] [PSCronJobObject[]] $CronJob, [Parameter( Mandatory )] [ValidateNotNullOrEmpty()] [string[]] $To, [ValidateNotNullOrEmpty()] [string[]] $Cc, [ValidateNotNullOrEmpty()] [string[]] $Bcc, [Parameter( Mandatory )] [ValidateNotNullOrEmpty()] [string] $From, [Alias( 'ComputerName' )] [ValidateNotNullOrEmpty()] [string] $SmtpServer, [ValidateNotNullOrEmpty()] [System.Net.Mail.MailPriority] $Priority, [Alias( 'DNO' )] [ValidateNotNullOrEmpty()] [System.Net.Mail.DeliveryNotificationOptions] $DeliveryNotificationOption, [Alias('sub')] [ValidateNotNullOrEmpty()] [string] $Subject = '[CRON] {0}', [ValidateNotNullOrEmpty()] [pscredential] [System.Management.Automation.CredentialAttribute()] $Credential, [switch] $UseSsl, [ValidateRange(0, 2147483647)] [int] $Port, [switch] $PassThru ) begin { $SendOptions = @{ BodyAsHtml = $true } 'To', 'Cc', 'Bcc', 'From', 'SmtpServer', 'Priority', 'DeliveryNotificationOption', 'Credential', 'Port' | Where-Object { $_ -in $PSBoundParameters.Keys } | ForEach-Object { $SendOptions.$_ = $PSBoundParameters.$_ } } process { $CronJob | ForEach-Object { $_.LogMessage( (Get-Date), 'INFO', 'Send-PSCronNotification - Sending notifications...' ) Send-MailMessage ` -Subject ( $Subject -f $_.Name ) ` -Body ( '<pre>{0}</pre>' -f ( $_.Log ) ) ` @SendOptions if ( $PassThru ) { $_ } } } } # SIG # Begin signature block # MIIfWwYJKoZIhvcNAQcCoIIfTDCCH0gCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUhVMUNe3MQBe4X5orJEDQVlD0 # GPSgghlDMIIFTDCCBDSgAwIBAgIRAJXsrVtF2nXJk88FisVsbxUwDQYJKoZIhvcN # AQELBQAwfDELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3Rl # cjEQMA4GA1UEBxMHU2FsZm9yZDEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMSQw # IgYDVQQDExtTZWN0aWdvIFJTQSBDb2RlIFNpZ25pbmcgQ0EwHhcNMTkxMDE1MDAw # MDAwWhcNMjAxMDA3MjM1OTU5WjCBlDELMAkGA1UEBhMCVVMxDjAMBgNVBBEMBTYw # MTIwMREwDwYDVQQIDAhJbGxpbm9pczEOMAwGA1UEBwwFRWxnaW4xGjAYBgNVBAkM # ETEyODcgQmxhY2toYXdrIERyMRowGAYDVQQKDBFTaGFubm9uIEdyYXlicm9vazEa # MBgGA1UEAwwRU2hhbm5vbiBHcmF5YnJvb2swggEiMA0GCSqGSIb3DQEBAQUAA4IB # DwAwggEKAoIBAQDUDfCIlFqVcaV4Jg4top2UPmJmuzCm7gKFKcLHxe+RZ4tH9cFB # K95slekX32x1XDBR6i4STWLkQDYAhTzwOgTDT7VfELoMBTBgBVeajcz52S87A1ce # tbBJGo3CaEAEdV6MY4icqyLWNhrcM8jZ0UEvX1OuDR7PSheQxXBsoYOnC31TtZtu # O94twA+em9Hrl2TcmFv1uYIyKsKcAuNo46cXOUJSJqDH2cOzPPDt9hECV/oQk2eJ # zDAG6M6YDn2r26vAC4QDv9VdMAykTbCaFWwzjGqxxvMA+zs9JWCQ5OtlHprnWvUd # ILOtQshJVkrEXSYPj/qo4SWoz9f+d3urt2ZJAgMBAAGjggGuMIIBqjAfBgNVHSME # GDAWgBQO4TqoUzox1Yq+wbutZxoDha00DjAdBgNVHQ4EFgQUkeJMtbtdIm9ef+Gl # DOHtvFIhRnYwDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwEwYDVR0lBAww # CgYIKwYBBQUHAwMwEQYJYIZIAYb4QgEBBAQDAgQQMEAGA1UdIAQ5MDcwNQYMKwYB # BAGyMQECAQMCMCUwIwYIKwYBBQUHAgEWF2h0dHBzOi8vc2VjdGlnby5jb20vQ1BT # MEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly9jcmwuc2VjdGlnby5jb20vU2VjdGln # b1JTQUNvZGVTaWduaW5nQ0EuY3JsMHMGCCsGAQUFBwEBBGcwZTA+BggrBgEFBQcw # AoYyaHR0cDovL2NydC5zZWN0aWdvLmNvbS9TZWN0aWdvUlNBQ29kZVNpZ25pbmdD # QS5jcnQwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLnNlY3RpZ28uY29tMCYGA1Ud # EQQfMB2BG3NoYW5ub24uZ3JheWJyb29rQGdtYWlsLmNvbTANBgkqhkiG9w0BAQsF # AAOCAQEAI2bbcfka5PxMeiZ9rEEN2A3/iDgWCwKUuzGEDJHJbGqcTi53Gt7HUOrc # VC11eh5cU9poVhUf/O+YQW6H6jPrkSOiajZY9I5UqIXo1sunFJwdbZzX7I40cOqf # 9Ma3HT5Gt9SA4WBqj8DO5GrF6L3QKi2BR7vdymYbQk92ZXX6GcwCGiOg7iBuF91v # r3Lx8JoPMuhObJvPUk59yKdHXZMue01X4N5/YJdo2g5XZU0qlq4CIiHYGQrsuhpv # IG0w1Bsw0Jjfd3I2r1Xu29XF1g6U9GgA0HdJ8BM+0pAKuLHtPzmqsWhEcEJLFN9g # UezIZBWejqWYx9Cy0nqr5NAnPWAVxTCCBfUwggPdoAMCAQICEB2iSDBvmyYY0ILg # ln0z02owDQYJKoZIhvcNAQEMBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpO # ZXcgSmVyc2V5MRQwEgYDVQQHEwtKZXJzZXkgQ2l0eTEeMBwGA1UEChMVVGhlIFVT # RVJUUlVTVCBOZXR3b3JrMS4wLAYDVQQDEyVVU0VSVHJ1c3QgUlNBIENlcnRpZmlj # YXRpb24gQXV0aG9yaXR5MB4XDTE4MTEwMjAwMDAwMFoXDTMwMTIzMTIzNTk1OVow # fDELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G # A1UEBxMHU2FsZm9yZDEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMSQwIgYDVQQD # ExtTZWN0aWdvIFJTQSBDb2RlIFNpZ25pbmcgQ0EwggEiMA0GCSqGSIb3DQEBAQUA # A4IBDwAwggEKAoIBAQCGIo0yhXoYn0nwli9jCB4t3HyfFM/jJrYlZilAhlRGdDFi # xRDtsocnppnLlTDAVvWkdcapDlBipVGREGrgS2Ku/fD4GKyn/+4uMyD6DBmJqGx7 # rQDDYaHcaWVtH24nlteXUYam9CflfGqLlR5bYNV+1xaSnAAvaPeX7Wpyvjg7Y96P # v25MQV0SIAhZ6DnNj9LWzwa0VwW2TqE+V2sfmLzEYtYbC43HZhtKn52BxHJAteJf # 7wtF/6POF6YtVbC3sLxUap28jVZTxvC6eVBJLPcDuf4vZTXyIuosB69G2flGHNyM # fHEo8/6nxhTdVZFuihEN3wYklX0Pp6F8OtqGNWHTAgMBAAGjggFkMIIBYDAfBgNV # HSMEGDAWgBRTeb9aqitKz1SA4dibwJ3ysgNmyzAdBgNVHQ4EFgQUDuE6qFM6MdWK # vsG7rWcaA4WtNA4wDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAw # HQYDVR0lBBYwFAYIKwYBBQUHAwMGCCsGAQUFBwMIMBEGA1UdIAQKMAgwBgYEVR0g # ADBQBgNVHR8ESTBHMEWgQ6BBhj9odHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vVVNF # UlRydXN0UlNBQ2VydGlmaWNhdGlvbkF1dGhvcml0eS5jcmwwdgYIKwYBBQUHAQEE # ajBoMD8GCCsGAQUFBzAChjNodHRwOi8vY3J0LnVzZXJ0cnVzdC5jb20vVVNFUlRy # dXN0UlNBQWRkVHJ1c3RDQS5jcnQwJQYIKwYBBQUHMAGGGWh0dHA6Ly9vY3NwLnVz # ZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEMBQADggIBAE1jUO1HNEphpNveaiqMm/EA # AB4dYns61zLC9rPgY7P7YQCImhttEAcET7646ol4IusPRuzzRl5ARokS9At3Wpwq # QTr81vTr5/cVlTPDoYMot94v5JT3hTODLUpASL+awk9KsY8k9LOBN9O3ZLCmI2pZ # aFJCX/8E6+F0ZXkI9amT3mtxQJmWunjxucjiwwgWsatjWsgVgG10Xkp1fqW4w2y1 # z99KeYdcx0BNYzX2MNPPtQoOCwR/oEuuu6Ol0IQAkz5TXTSlADVpbL6fICUQDRn7 # UJBhvjmPeo5N9p8OHv4HURJmgyYZSJXOSsnBf/M6BZv5b9+If8AjntIeQ3pFMcGc # TanwWbJZGehqjSkEAnd8S0vNcL46slVaeD68u28DECV3FTSK+TbMQ5Lkuk/xYpMo # JVcp+1EZx6ElQGqEV8aynbG8HArafGd+fS7pKEwYfsR7MUFxmksp7As9V1DSyt39 # ngVR5UR43QHesXWYDVQk/fBO4+L4g71yuss9Ou7wXheSaG3IYfmm8SoKC6W59J7u # mDIFhZ7r+YMp08Ysfb06dy6LN0KgaoLtO0qqlBCk4Q34F8W2WnkzGJLjtXX4oemO # CiUe5B7xn1qHI/+fpFGe+zmAEc3btcSnqIBv5VPU4OOiwtJbGvoyJi1qV3AcPKRY # LqPzW0sH3DJZ84enGm1YMIIG7DCCBNSgAwIBAgIQMA9vrN1mmHR8qUY2p3gtuTAN # BgkqhkiG9w0BAQwFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJz # ZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNU # IE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBB # dXRob3JpdHkwHhcNMTkwNTAyMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjB9MQswCQYD # VQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdT # YWxmb3JkMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxJTAjBgNVBAMTHFNlY3Rp # Z28gUlNBIFRpbWUgU3RhbXBpbmcgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw # ggIKAoICAQDIGwGv2Sx+iJl9AZg/IJC9nIAhVJO5z6A+U++zWsB21hoEpc5Hg7Xr # xMxJNMvzRWW5+adkFiYJ+9UyUnkuyWPCE5u2hj8BBZJmbyGr1XEQeYf0RirNxFrJ # 29ddSU1yVg/cyeNTmDoqHvzOWEnTv/M5u7mkI0Ks0BXDf56iXNc48RaycNOjxN+z # xXKsLgp3/A2UUrf8H5VzJD0BKLwPDU+zkQGObp0ndVXRFzs0IXuXAZSvf4DP0REK # V4TJf1bgvUacgr6Unb+0ILBgfrhN9Q0/29DqhYyKVnHRLZRMyIw80xSinL0m/9NT # IMdgaZtYClT0Bef9Maz5yIUXx7gpGaQpL0bj3duRX58/Nj4OMGcrRrc1r5a+2kxg # zKi7nw0U1BjEMJh0giHPYla1IXMSHv2qyghYh3ekFesZVf/QOVQtJu5FGjpvzdeE # 8NfwKMVPZIMC1Pvi3vG8Aij0bdonigbSlofe6GsO8Ft96XZpkyAcSpcsdxkrk5WY # nJee647BeFbGRCXfBhKaBi2fA179g6JTZ8qx+o2hZMmIklnLqEbAyfKm/31X2xJ2 # +opBJNQb/HKlFKLUrUMcpEmLQTkUAx4p+hulIq6lw02C0I3aa7fb9xhAV3PwcaP7 # Sn1FNsH3jYL6uckNU4B9+rY5WDLvbxhQiddPnTO9GrWdod6VQXqngwIDAQABo4IB # WjCCAVYwHwYDVR0jBBgwFoAUU3m/WqorSs9UgOHYm8Cd8rIDZsswHQYDVR0OBBYE # FBqh+GEZIA/DQXdFKI7RNV8GEgRVMA4GA1UdDwEB/wQEAwIBhjASBgNVHRMBAf8E # CDAGAQH/AgEAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMBEGA1UdIAQKMAgwBgYEVR0g # ADBQBgNVHR8ESTBHMEWgQ6BBhj9odHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vVVNF # UlRydXN0UlNBQ2VydGlmaWNhdGlvbkF1dGhvcml0eS5jcmwwdgYIKwYBBQUHAQEE # ajBoMD8GCCsGAQUFBzAChjNodHRwOi8vY3J0LnVzZXJ0cnVzdC5jb20vVVNFUlRy # dXN0UlNBQWRkVHJ1c3RDQS5jcnQwJQYIKwYBBQUHMAGGGWh0dHA6Ly9vY3NwLnVz # ZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEMBQADggIBAG1UgaUzXRbhtVOBkXXfA3oy # Cy0lhBGysNsqfSoF9bw7J/RaoLlJWZApbGHLtVDb4n35nwDvQMOt0+LkVvlYQc/x # QuUQff+wdB+PxlwJ+TNe6qAcJlhc87QRD9XVw+K81Vh4v0h24URnbY+wQxAPjeT5 # OGK/EwHFhaNMxcyyUzCVpNb0llYIuM1cfwGWvnJSajtCN3wWeDmTk5SbsdyybUFt # Z83Jb5A9f0VywRsj1sJVhGbks8VmBvbz1kteraMrQoohkv6ob1olcGKBc2NeoLvY # 3NdK0z2vgwY4Eh0khy3k/ALWPncEvAQ2ted3y5wujSMYuaPCRx3wXdahc1cFaJqn # yTdlHb7qvNhCg0MFpYumCf/RoZSmTqo9CfUFbLfSZFrYKiLCS53xOV5M3kg9mzSW # mglfjv33sVKRzj+J9hyhtal1H3G/W0NdZT1QgW6r8NDT/LKzH7aZlib0PHmLXGTM # ze4nmuWgwAxyh8FuTVrTHurwROYybxzrF06Uw3hlIDsPQaof6aFBnf6xuKBlKjTg # 3qj5PObBMLvAoGMs/FwWAKjQxH/qEZ0eBsambTJdtDgJK0kHqv3sMNrxpy/Pt/36 # 0KOE2See+wFmd7lWEOEgbsausfm2usg1XTN2jvF8IAwqd661ogKGuinutFoAsYyr # 4/kKyVRd1LlqdJ69SK6YMIIHBjCCBO6gAwIBAgIQPRo1cjAVgmMw0BNxfoJBCDAN # BgkqhkiG9w0BAQwFADB9MQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBN # YW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRgwFgYDVQQKEw9TZWN0aWdvIExp # bWl0ZWQxJTAjBgNVBAMTHFNlY3RpZ28gUlNBIFRpbWUgU3RhbXBpbmcgQ0EwHhcN # MTkwNTAyMDAwMDAwWhcNMzAwODAxMjM1OTU5WjCBhDELMAkGA1UEBhMCR0IxGzAZ # BgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEYMBYG # A1UECgwPU2VjdGlnbyBMaW1pdGVkMSwwKgYDVQQDDCNTZWN0aWdvIFJTQSBUaW1l # IFN0YW1waW5nIFNpZ25lciAjMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC # ggIBAMtRUP9W/vx4Y3ABk1qeGPQ7U/YHryFs9aIPfR1wLYR0SIucipUFPVmE+ZGA # eVEs2Yq3wQuaugqKzWZPA4sBuzDKq73bwE8SXvwKzOJFsAE4irtN59QcVJjtOVjP # W8IvRZgxCvk1OLgxLm20Hjly4bgqvp+MjBqlRq4LK0yZ/ixL/Ci5IjpmF9CqVooh # wPOWJLTQhSZruvBvZJh5pq29XNhTaysK1nKKhUbjDRgG2sZ7QVY2mxU+8WoRoPdm # 9RjQgFVjh2hm6w55VYJco+1JuHGGnpM3sGuj6mJso66W6Ln9i6vG9llbADxXIBgt # cAOnnO+S63mhx13sfLSPS9/rXfyjIN2SOOVqUTprhZxMoJgIaVsG5yoZ0JWTiztr # igUJKdjW2tvjcvpcSi97FVaGMr9/BQmdLSrPUOHmYSDbxwaAXE4URr6uV3Giqmww # kxx+d8sG6VfNkfXVM3Ic4drKbuvzD+x5W7snnuge/i/yu3/p5dBn67gNfKQrWQOL # le0iKM36LDvHFhGv49axUGdpxY71edCt/4fM+H+q+aLtYfjIjWnasfRRketnV9Fk # EetkywO9SVU6RUMYLCVs0S8MLW/1QTUkoPJjWRZf2aTpLE7buzESxm34W24D3MsV # jxuNcuzbDxWQ1hJO7uIAMSWTNW9qW6USY0ABirlpiDqIuA8ZAgMBAAGjggF4MIIB # dDAfBgNVHSMEGDAWgBQaofhhGSAPw0F3RSiO0TVfBhIEVTAdBgNVHQ4EFgQUb02G # B9gyJ54sKdLQEwOAgd0FgykwDgYDVR0PAQH/BAQDAgbAMAwGA1UdEwEB/wQCMAAw # FgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwQAYDVR0gBDkwNzA1BgwrBgEEAbIxAQIB # AwgwJTAjBggrBgEFBQcCARYXaHR0cHM6Ly9zZWN0aWdvLmNvbS9DUFMwRAYDVR0f # BD0wOzA5oDegNYYzaHR0cDovL2NybC5zZWN0aWdvLmNvbS9TZWN0aWdvUlNBVGlt # ZVN0YW1waW5nQ0EuY3JsMHQGCCsGAQUFBwEBBGgwZjA/BggrBgEFBQcwAoYzaHR0 # cDovL2NydC5zZWN0aWdvLmNvbS9TZWN0aWdvUlNBVGltZVN0YW1waW5nQ0EuY3J0 # MCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5zZWN0aWdvLmNvbTANBgkqhkiG9w0B # AQwFAAOCAgEAwGjts9jUUJvv03XLDzv3JNN6N0WNLO8W+1GpLB+1JbWKn10Lwhsg # dI1mDzbLqvY2DQ9+j0tKdENlrA0q9grta23FCTjtABv45dymCkAFR++Eygm8Q2aD # v5/t24490UFksXACLQNXWxhvHCzLHrIA6LoJL1uBBDW5qWNtjgjFGNHhIaz5EgoU # wBLbfiWdrB0QwFqlg9IfGmZV/Jsq4uw3V47l35Yw+MCTC0MY+QJvqVGvuFcK8xwH # aTmPN5xt15GupS5J6Ures9CMvzmQDcCBzvAqBzoMpi1R0nLzU8b5ve/vDGlJd58s # VsTpoQg9B67FHtaEIse8fUMbWDhiTtEFJYTFQvgfL/bb+quMVOxFimwSTTBaUuWk # Fwki5u9v9V+GQ9+hLb1KRpKggZYsYZd/QG/YP4w1WqvRxqA7hWZUgO8fGvXxm7Ch # J32y5wvP9i2cWBOUqYb8RVKiKG1/dA9SkUl66RL4qTuwkv19kRTpW21IlPLIlu4F # OLPF7DA/4QcgBLHYi7z9sz5v8gJTBvSg7cmacqOXXwD7y2PQ6M10/XXJ1DZFunsS # WXLt5/J6UAB4+EOaRtjfv1TUXrHH0bwbg/Qr5wvoR8hTnswarPb6inVTbCCFqdW4 # arokjoorCJGfNwQc9m+i3TSqkf/GFS4eQhoJKU/0xs3ikaLTQAyOeOMxggWCMIIF # fgIBATCBkTB8MQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVz # dGVyMRAwDgYDVQQHEwdTYWxmb3JkMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQx # JDAiBgNVBAMTG1NlY3RpZ28gUlNBIENvZGUgU2lnbmluZyBDQQIRAJXsrVtF2nXJ # k88FisVsbxUwCQYFKw4DAhoFAKB4MBgGCisGAQQBgjcCAQwxCjAIoAKAAKECgAAw # GQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisG # AQQBgjcCARUwIwYJKoZIhvcNAQkEMRYEFIBI17QeseMX0I4zpf0vbylLiB2zMA0G # CSqGSIb3DQEBAQUABIIBANKQbgJbEAqLEsDlGJijlbnAqiz/APUyFg4hAO7rI+JO # aJyIEsHg4GY2+Y+TGZs66MdlrGv2RsQ0r77q6OrYhngd1QJLYCP4Qb9cBMbO9eHq # wbbs7bshWRkZoYj5WSapqCh90Ufe9PW6bzuRTEjmNNe6+nXr5Uy7he6TQUCz5XIY # oqJPwBHf+S9vDE53pa8Bvgbu8wxFPrlITAakRTF/ktciy4APKsOCDndy21b3tZB9 # HiDMG6G6xG1TjYmovpQkfVlvYxP100CmeHqhW0rGWtXdaZdCrgcW7qXJ0b3m8pvx # LwwtsQ0HeASUK60f1JhnvVnCO8b48t7hFk+A+U9ICEKhggNLMIIDRwYJKoZIhvcN # AQkGMYIDODCCAzQCAQEwgZEwfTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0 # ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEYMBYGA1UEChMPU2VjdGln # byBMaW1pdGVkMSUwIwYDVQQDExxTZWN0aWdvIFJTQSBUaW1lIFN0YW1waW5nIENB # AhA9GjVyMBWCYzDQE3F+gkEIMA0GCWCGSAFlAwQCAgUAoHkwGAYJKoZIhvcNAQkD # MQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMjAwNjEzMjAzODE0WjA/Bgkq # hkiG9w0BCQQxMgQwlbpFwLmhUIyHXfI4VW38kQnie/mPlJmzCapX5mjfArkCMxL9 # auN3dy9ZbAUadM3RMA0GCSqGSIb3DQEBAQUABIICAGEKIVskhz7jzxECqRRkGyul # RJGIEblUP5Vh4GTRlXc3EHfkBwy4+acb3RcHCq9r5UM05/JbvptC4zPReK5CZig7 # Y1i6HXryQuC69uXbmFWmcSJFLpjTSApQA/B4KfmFgF6+h08PVqdLodKDfWF79E4l # HV8iHJgBU1UGaGCRVvzuKNFugBl/+kPU23Dd2tB3ppkS5HQG4RRkrQag1jPxXfmK # 3oMTCaTFvH1P4obkKnKkIYwkfGVAyuEtNdeov1iEc91rc2kZPxv6sLBKTiLf2Ye8 # YemlyKqJd4WTtH7mhkM1LBWAOc11lTr6RQsdB4KYymGOr/YoNEoTDFiGb87nodxE # 1cMnlD1XoQSTSQj08xdCEEhbcvKb6oIL18lq2+eo27DQy+HG44HSf+r9VjktdRQp # FNLT0ZpiUZNkLhnhUFaN316qUjZt5ELLoy47/wby8fIYKRvQ7hADH1w6C8/KT1Zo # AljwFSDvQorQ9/HosdiBUE4gOglimwG5Zvy5NPI3rWbn2aFSxGV9C2aQaX2D7bxp # QHOcqnOTyy/ae+E+YUCo8PIZEkqEt8iRP1VUjaHd+l3gl3CLxlZeZfDroeZVL0xC # SFf8qjRSiJPDCfDgobuaT6NB3BOsEbRzi/8ubEpVWO0OfoH22HKrdctZYaOm2txr # c1zqjDNAGUu8Zed7hUbh # SIG # End signature block |