OZOTaskScheduler.psm1
|
#Requires -RunAsAdministrator # CLASSES # OZOOnceDateTime class Class OZOOnceDateTime { # PROPERTIES: Booleans [Boolean] $Valid = $false # PROPERTIES: Int32s [Int32] $RandomDelay = 0 [Int32] $RandomDelayMax = 3600 # PROPERTIES: Strings [String] $DateTime = $null # METHODS: Constructor method OZOOnceDateTime($OnceDateTime) { # Set properties $this.DateTime = $OnceDateTime.DateTime $this.RandomDelay = $OnceDateTime.RandomDelay # Call validates to set valid $this.Valid = $this.Validates() } # METHODS: Validates method [Boolean] Validates() { # Control variable [Boolean] $Return = $true # Determine if RandomDelay is outside of range If ($this.RandomDelay -lt 0 -Or $this.RandomDelay -gt $this.RandomDelayMax) { # RandomDelay is outside of range $Return = $false } # Determine if DateTime cannot be expressed as a DateTime If ([Boolean]($this.DateTime -As [DateTime]) -eq $false) { # DateTime cannot be expressed as a DateTime $Return = $false } Else { # DateTime can be expressed as a DateTime; determine if DateTime is in the past If ([DateTime]$this.DateTime -lt (Get-Date)) { # DateTime is in the past $Return = $false } } # Return return $Return } } # OZOSchedule class Class OZOSchedule { # PROPERTIES: Booleans [Boolean] $Valid = $false # PROPERTIES: Int32s [Int32] $RandomDelay = 0 [Int32] $RandomDelayMax = 3600 # PROPERTIES: Strings [String] $StartTime = $null [String] $WeekDay = $null # PROPERTIES: String Lists Hidden [System.Collections.Generic.List[String]] $Weekdays = @("Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday") # METHODS: Constructor method OZOSchedule($Schedule) { # Set properties $this.RandomDelay = $Schedule.RandomDelay $this.StartTime = $Schedule.StartTime $this.WeekDay = $Schedule.WeekDay # Call validates to set valid $this.Valid = $this.Validates() } # METHODS: Validates method [Boolean] Validates() { # Control variable [Boolean] $Return = $true # Determine if RandomDelay is outside of range If ($this.RandomDelay -lt 0 -Or $this.RandomDelay -gt $this.RandomDelayMax) { # RandomDelay is outside of range $Return = $false } # Determine if StartTime cannot be expressed as a DateTime If ([Boolean]($this.StartTime -As [DateTime]) -eq $false) { # StartTime cannot be expressed as a DateTime $Return = $false } # Determine if WeekDay is not found in WeekDays If ($this.WeekDays -NotContains $this.WeekDay) { # WeekDay is not found in WeekDays $Return = $false } # Return return $Return } } # OZOTask class Class OZOTask { # PROPERTIES: Booleans [Boolean] $Disabled = $false [Boolean] $Scheduled = $false [Boolean] $Once = $false [Boolean] $AtReboot = $false [Boolean] $AtLogon = $false # PROPERTIES: OZOOnceDateTimes [OZOOnceDateTime] $OnceDateTime = $null # PROPERTIES: PSCustomObjects Hidden [PSCustomObject] $ozoLogger = $null [PSCustomObject] $Settings = $null # PROPERTIES: OZOSchedule Lists [System.Collections.Generic.List[OZOSchedule]] $OZOSchedules = @() # PROPERTIES: Strings [String] $Name = $null [String] $Script = $null [String] $Parameters = $null [String] $Directory = $null [String] $User = $null # PROPERTIES: String Lists [System.Collections.Generic.List[String]] $Compatibilities = @("At","V1","Vista","Win7","Win8") [System.Collections.Generic.List[String]] $MultipleInstancesValues = @("IgnoreNew","Parallel","Queue") # METHODS: Constructor method - Disable, Enable, Export, Get, Remove OZOTask([String]$Name) { # Set Properties $this.Name = $Name # Create a logger object $this.ozoLogger = (New-OZOLogger) # Determine if task exists If ($this.Exists() -eq $true) { # Task exists; populate from existing task $this.GetExistingTask() } } # METHODS: Constructor method - full OZOTask([String]$Name,[String]$Script,[String]$Parameters,[String]$Directory,[Boolean]$Disabled,[PSCustomObject]$Settings,[String]$User,[Boolean]$AtLogon,[Boolean]$AtReboot,[Boolean]$Once,[PSCustomObject]$OnceDateTime,[Boolean]$Scheduled,[System.Collections.Generic.List[PSCustomObject]]$Schedules) { # Set Properties $this.Name = $Name $this.Script = $Script $this.Parameters = $Parameters $this.Directory = $Directory $this.Disabled = $Disabled $this.Settings = $Settings $this.User = $User $this.AtLogon = $AtLogon $this.AtReboot = $AtReboot $this.Once = $Once $this.Scheduled = $Scheduled # Create a logger object $this.ozoLogger = (New-OZOLogger) # Iterate over schedules ForEach ($Schedule in $Schedules) { # Instantiate an OZOSchedule object and add it to the schedules list #$this.OZOSchedules.Add(([OZOSchedule]::new([String]$Schedule.StartTime,[Int32]$Schedule.RandomDelay,[String]$Schedule.WeekDay))) $this.OZOSchedules.Add(([OZOSchedule]::new($Schedule))) } # Determine if Once is set If ($this.Once -eq $true) { # Once is set; instantiate the OnceDateTime object #$this.OnceDateTime = [OZOOnceDateTime]::new([String]$OnceDateTime.DateTime,[Int32]$OnceDateTime.RandomDelay) $this.OnceDateTime = [OZOOnceDateTime]::new($OnceDateTime) } } # METHODS: Validation method [Boolean] Validates() { # Control variable [Boolean] $Return = $true # Determine if the Name property is null or empty If ([String]::IsNullOrEmpty($this.Name) -eq $true) { # Name is null or empty $this.ozoLogger.Write("Missing value for Name.","Error") $Return = $false } Else { # Determine if the Script property is null or empty If ([String]::IsNullOrEmpty($this.Script) -eq $true) { # Compatibility is null or empty $this.ozoLogger.Write(($this.Name + " Script value is missing."),"Error") $Return = $false } Else { # Determine if script exists If ([Boolean](Test-Path -Path $this.Script) -eq $true) { # Determine if Directory is null or empty If ([String]::IsNullOrEmpty($this.Directory)) { # Directory is null or empty; set to parent of script $this.Directory = (Split-Path -Path $this.Script -Parent) } } Else { # Script does not exist $this.ozoLogger.Write(($this.Name + " script does not exist."),"Error") $Return = $false } } # Determine if Scheduled is set and there are no valid schedules If ($this.Scheduled -eq $true -And ($this.OZOSchedules | Where-Object {$_.Valid -eq $true}).Count -eq 0) { # Scheduled is set and there are no valid schedules $this.ozoLogger.Write(($this.Name + " Scheduled is enabled but no valid schedules were found."),"Error") $Return = $false } # Determine if Once is true and OnceDateTime is null If ($this.Once -eq $true -And $null -eq $this.OnceDateTime) { # Once is true and OnceDateTime is null $this.ozoLogger.Write(($this.Name + " Once is enabled but OnceDateTime is null."),"Error") $Return = $false } # Determine if Once is true and OnceDateTime is not null and OnceDateTime is not valid If ($this.Once -eq $true -And $null -ne $this.OnceDateTime -And $this.OnceDateTime.Valid -eq $false) { # Once is true and OnceDateTime not null and OnceDateTime is not valid $this.ozoLogger.Write(($this.Name + " Once is enabled but OnceDateTime is not valid."),"Error") $Return = $false } # Determine if no triggers are set If ($this.AtLogon -eq $false -And $this.Scheduled -eq $false -And $this.Once -eq $false -And $this.AtReboot -eq $false) { # No triggers are set $this.ozoLogger.Write(($this.Name + " No triggers are set."),"Error") $Return = $false } # Determine if AtLogon is set and any other trigger is set If ($this.AtLogon -eq $true -And ($this.Scheduled -eq $true -Or $this.Once -eq $true -Or $this.AtReboot -eq $true)) { # AtLogon is set and any other trigger is set $this.ozoLogger.Write(($this.Name + " AtLogon is enabled but other triggers are also set. AtLogon will be ignored."),"Warning") } # Determine if Settings is null If ($null -eq $this.Settings) { # Settings is null; set to default $this.Settings = [PSCustomObject]@{ AllowDemandStart = $true AllowHardTerminate = $true AllowStartOnRemoteAppSession = $true Compatibility = "Win8" DeleteExpiredTaskAfter = "PT0S" DisallowStartIfOnBatteries = $false DontStopIfGoingOnBatteries = $true ExecutionTimeLimit = "PT0S" Hidden = $false IdleSettings = [PSCustomObject]@{ StopOnIdleEnd = $false RestartOnIdle = $false } MultipleInstances = "IgnoreNew" Priority = "Normal" RunOnlyIfNetworkAvailable = $false WakeToRun = $false } } # Determine if Compatibility is not found in Compatibilities If ($this.Compatibilities -NotContains $this.Settings.Compatibility) { # Compatibility is not found in Compatibilities $this.Settings.Compatibility = "Win8" } # Determine if MultipleInstances is set and not found in MultipleInstancesValues If ([String]::IsNullOrEmpty($this.Settings.MultipleInstances) -eq $false -And $this.MultipleInstancesValues -NotContains $this.Settings.MultipleInstances) { # MultipleInstances is set to an unsupported value; log a warning and clear it so the Task Scheduler default is used $this.ozoLogger.Write(($this.Name + " Settings.MultipleInstances value '" + $this.Settings.MultipleInstances + "' is not supported and will be ignored."), "Warning") $this.Settings.MultipleInstances = $null } # Determine if Directory is null or empty If ([String]::IsNullOrEmpty($this.Directory)) { # Directory is null or empty $this.Directory = (Split-Path -Path $this.Script -Parent) } } # Return return $Return } # METHODS: TaskExists method [Boolean] Exists() { # Control variable [Boolean] $Return = $true # Determine if the task exists If ([Boolean](Get-ScheduledTask -TaskName $this.Name -ErrorAction SilentlyContinue) -eq $false) { # Task does not exist; set return $Return = $false } # Return Return $Return } # METHODS: Populate from existing task method Hidden [Void] GetExistingTask() { # Determine if the task exists If ([Boolean]($ScheduledTask = Get-ScheduledTask -TaskName $this.Name -ErrorAction SilentlyContinue) -eq $true) { # Task exists; populate properties from existing task $ScheduledTask = (Get-ScheduledTask -TaskName $this.Name) # Populate Disabled $this.Disabled = -Not [Boolean]$ScheduledTask.Settings.Enabled # Populate Settings $this.Settings = [PSCustomObject]@{ AllowDemandStart = [Boolean]$ScheduledTask.Settings.AllowDemandStart AllowHardTerminate = [Boolean]$ScheduledTask.Settings.AllowHardTerminate AllowStartOnRemoteAppSession = -Not [Boolean]$ScheduledTask.Settings.DisallowStartOnRemoteAppSession Compatibility = [String]$ScheduledTask.Settings.Compatibility DeleteExpiredTaskAfter = [String]$ScheduledTask.Settings.DeleteExpiredTaskAfter DisallowStartIfOnBatteries = [Boolean]$ScheduledTask.Settings.DisallowStartIfOnBatteries DontStopIfGoingOnBatteries = -Not [Boolean]$ScheduledTask.Settings.StopIfGoingOnBatteries ExecutionTimeLimit = [String]$ScheduledTask.Settings.ExecutionTimeLimit Hidden = [Boolean]$ScheduledTask.Settings.Hidden IdleSettings = [PSCustomObject]@{ StopOnIdleEnd = [Boolean]$ScheduledTask.Settings.IdleSettings.StopOnIdleEnd RestartOnIdle = [Boolean]$ScheduledTask.Settings.IdleSettings.RestartOnIdle } MultipleInstances = [String]$ScheduledTask.Settings.MultipleInstances Priority = [Int32]$ScheduledTask.Settings.Priority RunOnlyIfNetworkAvailable = [Boolean]$ScheduledTask.Settings.RunOnlyIfNetworkAvailable WakeToRun = [Boolean]$ScheduledTask.Settings.WakeToRun } # Populate User (handling user will be introduced in a future update) #$this.User = [String]$ScheduledTask.Principal.UserId # Populate Actions (including Directory, Script, and Parameters) $Action = $ScheduledTask.Actions | Select-Object -First 1 # Determine if Action is not null If ($null -ne $Action) { # Action is not null; set Directory $this.Directory = [String]$Action.WorkingDirectory # Determine if the action is a PowerShell If ($Action.Execute -match 'powershell\.exe$') { # Action is a PowerShell executable $ActionMatch = [Regex]::Match([String]$Action.Arguments, '-File\s+"(?<Script>[^"]+)"\s*(?<Parameters>.*)$') # Determine if the PowerShell action matches the expected format If ($ActionMatch.Success) { # PowerShell action matches the expected format $this.Script = $ActionMatch.Groups['Script'].Value $this.Parameters = $ActionMatch.Groups['Parameters'].Value } Else { # PowerShell action does not match the expected format $this.ozoLogger.Write(($this.Name + " uses an unsupported PowerShell action format."), "Warning") } # Determine if the action is a CMD executable } ElseIf ($Action.Execute -match 'cmd\.exe$') { # Action is a CMD executable $ActionMatch = [Regex]::Match([String]$Action.Arguments, '/Q\s+/C\s+"(?<Script>[^"]+)"\s*(?<Parameters>.*)$') # Determine if the CMD action matches the expected format If ($ActionMatch.Success) { # CMD action matches the expected format $this.Script = $ActionMatch.Groups['Script'].Value $this.Parameters = $ActionMatch.Groups['Parameters'].Value } Else { # CMD action does not match the expected format $this.ozoLogger.Write(($this.Name + " uses an unsupported CMD action format."), "Warning") } } Else { # Action is an unsupported executable $this.ozoLogger.Write(($this.Name + " uses an unsupported action executable."), "Warning") } } # Reset trigger state before mapping supported Task Scheduler triggers $this.AtLogon = $false $this.AtReboot = $false $this.Once = $false $this.OnceDateTime = $null $this.Scheduled = $false $this.OZOSchedules.Clear() # Iterate over the triggers and map to properties ForEach ($Trigger in $ScheduledTask.Triggers) { # Set default RandomDelay to 0 $RandomDelay = 0 # Determine if the trigger has a RandomDelay property and it is not null or empty If ([String]::IsNullOrEmpty([String]$Trigger.RandomDelay) -eq $false) { # Trigger has a RandomDelay property and it is not null or empty; convert to seconds $RandomDelay = [Int32][System.Xml.XmlConvert]::ToTimeSpan([String]$Trigger.RandomDelay).TotalSeconds } # Switch on the trigger's CimClassName to map to properties Switch ($Trigger.CimClass.CimClassName) { 'MSFT_TaskWeeklyTrigger' { # Trigger is MSFT_TaskWeeklyTrigger; set Scheduled to true $this.Scheduled = $true # Determine the StartTime for the trigger $StartTime = ([DateTime]$Trigger.StartBoundary).ToString("h:mm tt") # Define a list of weekdays with their corresponding values for bitwise comparison $Weekdays = @( [PSCustomObject]@{ Name = "Sunday"; Value = 1 }, [PSCustomObject]@{ Name = "Monday"; Value = 2 }, [PSCustomObject]@{ Name = "Tuesday"; Value = 4 }, [PSCustomObject]@{ Name = "Wednesday"; Value = 8 }, [PSCustomObject]@{ Name = "Thursday"; Value = 16 }, [PSCustomObject]@{ Name = "Friday"; Value = 32 }, [PSCustomObject]@{ Name = "Saturday"; Value = 64 } ) # Iterate on Weekdays ForEach ($Weekday in $Weekdays) { # Determine if the current weekday is included in the trigger's DaysOfWeek using bitwise AND If (([Int32]$Trigger.DaysOfWeek -band $Weekday.Value) -ne 0) { # Current weekday is included in the trigger's DaysOfWeek; create an OZOSchedule object and add it to the OZOSchedules list $this.OZOSchedules.Add([OZOSchedule]::new([PSCustomObject]@{ WeekDay = $Weekday.Name StartTime = $StartTime RandomDelay = $RandomDelay })) } } # Break break } 'MSFT_TaskTimeTrigger' { # Trigger is MSFT_TaskTimeTrigger; set Once to true $this.Once = $true # Create an OZOOnceDateTime object and set it to OnceDateTime $this.OnceDateTime = [OZOOnceDateTime]::new([PSCustomObject]@{ DateTime = ([DateTime]$Trigger.StartBoundary).ToString("o") RandomDelay = $RandomDelay }) # Break break } 'MSFT_TaskBootTrigger' { # Trigger is MSFT_TaskBootTrigger; set AtReboot to true $this.AtReboot = $true # Break break } 'MSFT_TaskLogonTrigger' { # Trigger is MSFT_TaskLogonTrigger; set AtLogon to true $this.AtLogon = $true # Break break } Default { $this.ozoLogger.Write(($this.Name + " uses an unsupported trigger type: " + $Trigger.CimClass.CimClassName + "."), "Warning") # Break break } } } } Else { # Task does not exist $this.ozoLogger.Write(("Failed to get the " + $this.Name + " task with error " + $_ + "."), "Error") } } # METHODS: Priority value method Hidden [Int32] GetPriorityValue() { # Local variables [Int32] $Priority = 7 [Int32] $ParsedPriority = 0 # Determine if Priority can be parsed as an integer If ([Int32]::TryParse([String]$this.Settings.Priority, [ref]$ParsedPriority) -eq $true) { # Priority is an integer; use it $Priority = $ParsedPriority # ElseIf determine if Priority is the friendly value "Normal" } ElseIf ([String]$this.Settings.Priority -eq "Normal") { # Priority is "Normal"; use the default value $Priority = 7 # ElseIf determine if Priority is set to an unrecognized value } ElseIf ($null -ne $this.Settings.Priority) { # Priority is set to an unrecognized value; log a warning and use the default value $this.ozoLogger.Write(($this.Name + " Settings.Priority value '" + $this.Settings.Priority + "' is not recognized; using 7."), "Warning") } # Determine if Priority is outside of the supported range If ($Priority -lt 0 -Or $Priority -gt 10) { # Priority is outside of the supported range; log a warning and use the default value $this.ozoLogger.Write(($this.Name + " Settings.Priority value " + $Priority + " is outside the supported range (0-10); using 7."), "Warning") $Priority = 7 } # Return return $Priority } # METHODS: Settings parameters method Hidden [Hashtable] GetSettingsParameters() { # Control variable [Hashtable] $SettingsParameters = @{ Compatibility = $this.Settings.Compatibility StartWhenAvailable = $true } # Determine if AllowDemandStart is false If ($this.Settings.AllowDemandStart -eq $false) { # AllowDemandStart is false $SettingsParameters.DisallowDemandStart = $true } # Determine if AllowHardTerminate is false If ($this.Settings.AllowHardTerminate -eq $false) { # AllowHardTerminate is false $SettingsParameters.DisallowHardTerminate = $true } # Determine if AllowStartOnRemoteAppSession is false If ($this.Settings.AllowStartOnRemoteAppSession -eq $false) { # AllowStartOnRemoteAppSession is false $SettingsParameters.DisallowStartOnRemoteAppSession = $true } # Determine if DeleteExpiredTaskAfter is set If ([String]::IsNullOrEmpty($this.Settings.DeleteExpiredTaskAfter) -eq $false) { # Determine if DeleteExpiredTaskAfter is not zero (PT0S) If ($this.Settings.DeleteExpiredTaskAfter -ne "PT0S") { # DeleteExpiredTaskAfter is set; Try to convert it to a TimeSpan Try { $SettingsParameters.DeleteExpiredTaskAfter = [System.Xml.XmlConvert]::ToTimeSpan([String]$this.Settings.DeleteExpiredTaskAfter) # Success } Catch { # Failure $this.ozoLogger.Write(($this.Name + " Settings.DeleteExpiredTaskAfter value '" + $this.Settings.DeleteExpiredTaskAfter + "' is not a valid duration and will be ignored."), "Warning") } } } # Determine if DisallowStartIfOnBatteries is false If ($this.Settings.DisallowStartIfOnBatteries -eq $false) { # DisallowStartIfOnBatteries is false $SettingsParameters.AllowStartIfOnBatteries = $true } # Determine if DontStopIfGoingOnBatteries is true If ($this.Settings.DontStopIfGoingOnBatteries -eq $true) { # DontStopIfGoingOnBatteries is true $SettingsParameters.DontStopIfGoingOnBatteries = $true } # Determine if ExecutionTimeLimit is set If ([String]::IsNullOrEmpty($this.Settings.ExecutionTimeLimit) -eq $false) { # ExecutionTimeLimit is set; Try to convert it to a TimeSpan Try { $SettingsParameters.ExecutionTimeLimit = [System.Xml.XmlConvert]::ToTimeSpan([String]$this.Settings.ExecutionTimeLimit) # Success } Catch { # Failure $this.ozoLogger.Write(($this.Name + " Settings.ExecutionTimeLimit value '" + $this.Settings.ExecutionTimeLimit + "' is not a valid duration and will be ignored."), "Warning") } } # Determine if Hidden is true If ($this.Settings.Hidden -eq $true) { # Hidden is true $SettingsParameters.Hidden = $true } # Determine if IdleSettings.StopOnIdleEnd is false If ($null -ne $this.Settings.IdleSettings -And $this.Settings.IdleSettings.StopOnIdleEnd -eq $false) { # IdleSettings.StopOnIdleEnd is false $SettingsParameters.DontStopOnIdleEnd = $true } # Determine if IdleSettings.RestartOnIdle is true If ($null -ne $this.Settings.IdleSettings -And $this.Settings.IdleSettings.RestartOnIdle -eq $true) { # IdleSettings.RestartOnIdle is true $SettingsParameters.RestartOnIdle = $true } # Determine if MultipleInstances is set If ([String]::IsNullOrEmpty($this.Settings.MultipleInstances) -eq $false) { # MultipleInstances is set $SettingsParameters.MultipleInstances = $this.Settings.MultipleInstances } # Determine if Priority is set If ($null -ne $this.Settings.Priority) { # Priority is set $SettingsParameters.Priority = $this.GetPriorityValue() } # Determine if RunOnlyIfNetworkAvailable is true If ($this.Settings.RunOnlyIfNetworkAvailable -eq $true) { # RunOnlyIfNetworkAvailable is true $SettingsParameters.RunOnlyIfNetworkAvailable = $true } # Determine if WakeToRun is true If ($this.Settings.WakeToRun -eq $true) { # WakeToRun is true $SettingsParameters.WakeToRun = $true } # Return return $SettingsParameters } # METHODS: AddTask method [Void] AddTask() { # Local variables [System.Collections.Generic.List[Microsoft.Management.Infrastructure.CimInstance]] $Triggers = @() $actionParameters = @{} $settingsParameters = @{} $scheduledTaskParameters = @{} # Determine if the task does not exist and is valid If ($this.Exists() -eq $false -And $this.Validates() -eq $true) { ## ACTION PARAMETERS # Determine if this script is a PowerShell script If ((Get-Item -Path $this.Script).Extension -eq ".ps1") { # Script is PowerShell; set paramters for New-ScheduledTaskAction with PowerShell executable and arguments $actionParameters = @{ Execute = 'powershell.exe' Argument = ('-NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy RemoteSigned -File "' + $this.Script + '" ' + $this.Parameters) WorkingDirectory = $this.Directory } } Else { # Script is not PowerShell; set executable and argument for CMD $actionParameters = @{ Execute = "cmd.exe" Argument = ('/Q /C "' + $this.Script + '" ' + $this.Parameters) WorkingDirectory = $this.Directory } } ## SETTINGS PARAMETERS # Build settings parameters from the Settings configuration $settingsParameters = $this.GetSettingsParameters() # Determine if Disabled is set If ($this.Disabled -eq $true) { # Disabled is set; add the Disable parameter $settingsParameters.Disable = $true } ## TRIGGERS AND SCHEDULED TASK PARAMETERS # Determine if at least one of Scheduled, Once, or AtReboot is true If ($this.Scheduled -eq $true -Or $this.Once -eq $true -Or $this.AtReboot -eq $true) { # AtLogon is false and one of Scheduled, Once, or AtReboot is true; determine if scheduled is true and at least one schedule is valid If ($this.Scheduled -eq $true -And ($this.OZOSchedules | Where-Object {$_.Valid -eq $true}).Count -gt 0) { # Schedules is not null and there is at least one valid schedule; iterate over the valid schedules and create triggers ForEach ($Schedule in ($this.OZOSchedules | Where-Object {$_.Valid -eq $true})) { # Try to create a weekly trigger for each schedule and add the trigger to the list of triggers Try { $Triggers.Add((New-ScheduledTaskTrigger -Weekly -DaysOfWeek $Schedule.Weekday -At $Schedule.StartTime -RandomDelay (New-TimeSpan -Start ([DateTime]$Schedule.StartTime) -End (([DateTime]$Schedule.StartTime).AddSeconds($Schedule.RandomDelay))))) # Success } Catch { # Failure $this.ozoLogger.Write(("Failed to add schedule for weekday " + $Schedule.Weekday + " with start time " + $Schedule.StartTime + " and random delay " + $Schedule.RandomDelay + " with error " + $_.Exception.Message + "."),"Error") } } } # Determine Once is true and OnceDateTime is not null and is valid If ($this.Once -eq $true -And $null -ne $this.OnceDateTime -And $this.OnceDateTime.Valid -eq $true) { # Once is true, and OnceDateTime is not null and is valid; try to create a one-time trigger and add it to the list of triggers Try { $Triggers.Add((New-ScheduledTaskTrigger -Once -At $this.OnceDateTime.DateTime -RandomDelay (New-TimeSpan -Start ([DateTime]$this.OnceDateTime.DateTime) -End (([DateTime]$this.OnceDateTime.DateTime).AddSeconds($this.OnceDateTime.RandomDelay))))) # Success } Catch { # Failure $this.ozoLogger.Write(("Failed to add once schedule with start time " + $this.OnceDateTime.DateTime + " and random delay " + $this.OnceDateTime.RandomDelay + " with error " + $_.Exception.Message + "."),"Error") } } # Determine AtReboot is true If ($this.AtLogon -eq $false -And $this.AtReboot -eq $true) { # AtLogon is false and AtReboot is true; create a boot trigger and it to the list of triggers $Triggers.Add((New-ScheduledTaskTrigger -AtStartup)) } # Set scheduled task parameters for Register-ScheduledTask with User parameter $scheduledTaskParameters = @{ TaskName = $this.Name User = $this.User Action = (New-ScheduledTaskAction @actionParameters) Trigger = $Triggers Settings = (New-ScheduledTaskSettingsSet @settingsParameters) } # ElseIf determine if AtLogon is true and all of Scheduled, Once, and AtReboot are false } ElseIf ($this.AtLogon -eq $true -And $this.Scheduled -eq $false -And $this.Once -eq $false -And $this.AtReboot -eq $false) { # AtLogon is true, Scheduled is false, and AtReboot is false; create a logon trigger and a it to the list of triggers $Triggers.Add((New-ScheduledTaskTrigger -AtLogOn)) # Set scheduled task parameters for Register-ScheduledTask without User parameter $scheduledTaskParameters = @{ TaskName = $this.Name Action = (New-ScheduledTaskAction @actionParameters) Trigger = $Triggers Settings = (New-ScheduledTaskSettingsSet @settingsParameters) } } # Determine if DeleteExpiredTaskAfter is set; Task Scheduler requires every trigger to have an EndBoundary or registration fails with a missing EndBoundary error If ($settingsParameters.ContainsKey("DeleteExpiredTaskAfter") -eq $true) { ForEach ($Trigger in $Triggers) { # Determine if the trigger does not already have an EndBoundary If ([String]::IsNullOrEmpty($Trigger.EndBoundary) -eq $true) { # EndBoundary is not set; set it far in the future so the task remains valid without altering its intended schedule $Trigger.EndBoundary = (Get-Date).AddYears(99).ToString("yyyy-MM-ddTHH:mm:ss") } } } # Determine that at least one trigger is defined If ($Triggers.Count -gt 0) { # At least one trigger is defined; try to register the task Try { Register-ScheduledTask @scheduledTaskParameters -ErrorAction Stop # Success } Catch { # Failure $this.ozoLogger.Write(("Failed to register the " + $this.Name + " task with error " + $_ + "."),"Error") } } Else { # Task exists or no triggers defined $this.ozoLogger.Write("No triggers were defined.","Error") } } } # METHODS: EnableTask method [Void] EnableTask() { # Determine if task exists If ($this.Exists() -eq $true) { # Task exists; try to enable it Try { Enable-ScheduledTask -TaskName $this.Name -ErrorAction Stop # Success } Catch { # Failure $this.ozoLogger.Write(("Failed to enable the " + $this.Name + " task with error " + $_ + "."),"Error") } } } # METHODS: DisableTask method [Void] DisableTask() { # Detemrine if the task exists If ($this.Exists() -eq $true) { # Task exists; try to disable Try { Disable-ScheduledTask -TaskName $this.Name -ErrorAction Stop # Success } Catch { # Failure $this.ozoLogger.Write(("Failed to disable the " + $this.Name + " task with error " + $_ + "."), "Error") } } } # METHODS: RemoveTask method [Void] RemoveTask() { # Detemrine if the task exists If ($this.Exists() -eq $true) { # Task exists; call disable task to disable $this.DisableTask() # Try to unregister Try { Unregister-ScheduledTask -TaskName $this.Name -Confirm:$false -ErrorAction Stop # Success } Catch { # Failure $this.ozoLogger.Write(("Failed to remove the " + $this.Name + " task with error " + $_ + "."), "Error") } } } # METHODS: UpdateTask method [Void] UpdateTask() { # Call RemoveTask to disable and remove the task $this.RemoveTask() # Detemine if the task does not exist If ($this.Exists() -eq $false) { # Call AddTask to add the task $this.AddTask() } } } # OZOScheduledTask class Class OZOJsonTask { # PROPERTIES: Hidden PSCustomObjects Hidden [PSCustomObject] $Json = $null Hidden [PSCustomObject] $ozoLogger = $null # PROPERTIES: PSCustomObjects [OZOTask] $Task = $null # METHODS: Constructor method OZOJsonTask([String]$JsonFile,[String]$JsonString) { # Create an OZOLogger object $this.ozoLogger = (New-OZOLogger) # Determine if the configuration and environment validate If ($this.ValidateConfiguration($JsonFile,$JsonString) -eq $true) { # Determine if JSON is not null If ($null -ne $this.Json) { # Instantiate an OZOTask object for this Task $this.Task = [OZOTask]::new( $this.Json.Name, $this.Json.Script, $this.Json.Parameters, $this.Json.Directory, $this.Json.Disabled, $this.Json.Settings, "SYSTEM", $this.Json.AtLogon, $this.Json.AtReboot, $this.Json.Once, $this.Json.OnceDateTime, $this.Json.Scheduled, $this.Json.Schedules ) } } } # METHODS: Configuration validation method Hidden [Boolean] ValidateConfiguration($JsonFile,$JsonString) { # Control variable [Boolean] $Return = $true # Determine if both JsonFile and JsonString are provided If ([String]::IsNullOrEmpty($JsonFile) -eq $false -And [String]::IsNullOrEmpty($JsonString) -eq $false) { # Both JsonFile and JsonString are provided; log error and return false $this.ozoLogger.Write("Specify either JsonFile or JsonString, not both.", "Error") return $false } # Determine if both JsonFile and JsonString are not provided If ([String]::IsNullOrEmpty($JsonFile) -eq $true -And [String]::IsNullOrEmpty($JsonString) -eq $true) { $this.ozoLogger.Write("Either JsonFile or JsonString is required.", "Error") return $false } # Try to get the JSON content from the provided file or string Try { # Determine if JsonFile is not null or empty If ([String]::IsNullOrEmpty($JsonFile) -eq $false) { $this.Json = (Get-Content -Path $JsonFile -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop) # Elseif determine if JsonString is not null or empty } ElseIf ([String]::IsNullOrEmpty($JsonString) -eq $false) { $this.Json = ($JsonString | ConvertFrom-Json -ErrorAction Stop) } # Success } Catch { # Failure $this.ozoLogger.Write(("Failed to import JSON."), "Error") $this.Json = $null return $false } # Determine if JSON is null If ($null -eq $this.Json) { $this.ozoLogger.Write("JSON content is null or empty.", "Error") return $false } # Return Return $Return } } # FUNCTIONS # Disable-OZOScheduledTask function Function Disable-OZOScheduledTask { <# .SYNOPSIS See description. .DESCRIPTION Disables a task, if found. .PARAMETER TaskName The name of the task to disable. .PARAMETER PassThru Return the disabled task. .EXAMPLE Disable-OZOScheduledTask -TaskName "Update OZO PowerShell Module" .LINK https://github.com/onezeroone-dev/OZOTaskScheduler-PowerShell-Repository/blob/main/Documentation/Disable-OZOScheduledTask.md #> # Parameters [CmdLetBinding(SupportsShouldProcess=$true)] Param ( [Parameter(Mandatory=$true,HelpMessage="The task to disable")][String]$TaskName, [Parameter(Mandatory=$false,HelpMessage="Return the disabled task")][Switch]$PassThru ) # Get the task [PSCustomObject] $ozoGetScheduledTask = (Get-OZOScheduledTask -TaskName $TaskName) # Determine if the task is not null If ($null -ne $ozoGetScheduledTask) { # Task is not null; call DisableTask to disable the task If ($PSCmdlet.ShouldProcess($TaskName, "Disable scheduled task")) { $ozoGetScheduledTask.DisableTask() # Determine if PassThru is set If ($PassThru.IsPresent -eq $true) { # PassThru is set; return the disabled task $PSCmdlet.WriteObject((Get-OZOScheduledTask -TaskName $TaskName)) } } } } # Enable-OZOScheduledTask function Function Enable-OZOScheduledTask { <# .SYNOPSIS See description. .DESCRIPTION Enable a task, if found. .PARAMETER TaskName The name of the task to enable. .PARAMETER PassThru Return the enabled task. .EXAMPLE Enable-OZOScheduledTask -TaskName "Update OZO PowerShell Module" .LINK https://github.com/onezeroone-dev/OZOTaskScheduler-PowerShell-Repository/blob/main/Documentation/Enable-OZOScheduledTask.md #> # Parameters [CmdLetBinding(SupportsShouldProcess=$true)] Param ( [Parameter(Mandatory=$true,HelpMessage="The task to enable")][String] $TaskName, [Parameter(Mandatory=$false,HelpMessage="Return the enabled task")][Switch]$PassThru ) # Get the task [PSCustomObject] $ozoGetScheduledTask = (Get-OZOScheduledTask -TaskName $TaskName) # Determine if the task is not null If ($null -ne $ozoGetScheduledTask) { # Task is not null; call EnableTask to enable the task If ($PSCmdlet.ShouldProcess($TaskName, "Enable scheduled task")) { $ozoGetScheduledTask.EnableTask() # Determine if PassThru is set If ($PassThru.IsPresent -eq $true) { # PassThru is set; return the enabled task $PSCmdlet.WriteObject((Get-OZOScheduledTask -TaskName $TaskName)) } } } } # Export-OZOScheduledTask function Function Export-OZOScheduledTask { <# .SYNOPSIS See description. .DESCRIPTION Exports a task to JSON, if found. .PARAMETER OutFile The path for the output JSON file. .PARAMETER TaskName The name of the task to export. .EXAMPLE Export-OZOScheduledTask -TaskName "Update OZO PowerShell Module" .LINK https://github.com/onezeroone-dev/OZOTaskScheduler-PowerShell-Repository/blob/main/Documentation/Export-OZOScheduledTask.md #> # Parameters [CmdLetBinding(SupportsShouldProcess=$true)] Param ( [Parameter(Mandatory=$true,HelpMessage="The path for the output JSON file")][String]$OutFile, [Parameter(Mandatory=$true,HelpMessage="The task to export")][String]$TaskName ) # Get the task [PSCustomObject] $ozoGetScheduledTask = (Get-OZOScheduledTask -TaskName $TaskName) # Determine if the task is not null If ($null -ne $ozoGetScheduledTask) { # Task is not null If ($PSCmdlet.ShouldProcess($TaskName, "Remove scheduled task")) { #Get the OneDateTime [PSCustomObject] $OnceDateTime = @{DateTime=$ozoGetScheduledTask.OnceDateTime.DateTime;RandomDelay=$ozoGetScheduledTask.OnceDateTime.RandomDelay} # Get the Schedules [System.Collections.Generic.List[PSCustomObject]] $Schedules = @($ozoGetScheduledTask.OZOSchedules | Select-Object -Property WeekDay,StartTime,RandomDelay) # Get the task details [PSCustomObject] $Task = [PSCustomObject]@{ Name = $ozoGetScheduledTask.Name Script = $ozoGetScheduledTask.Script Parameters = $ozoGetScheduledTask.Parameters Directory = $ozoGetScheduledTask.Directory Disabled = $ozoGetScheduledTask.Disabled Settings = $ozoGetScheduledTask.Settings AtLogon = $ozoGetScheduledTask.AtLogon AtReboot = $ozoGetScheduledTask.AtReboot Once = $ozoGetScheduledTask.Once OnceDateTime = $OnceDateTime Scheduled = $ozoGetScheduledTask.Scheduled Schedules = $Schedules } # Export to the specified JSON file $Task | ConvertTo-Json | Out-File -FilePath $OutFile } } } # Get-OZOScheduledTask function Function Get-OZOScheduledTask { <# .SYNOPSIS See description. .DESCRIPTION Get an object representing an existing task, if found. .PARAMETER TaskName The name of the task to get. .EXAMPLE $ozoGetScheduledTask = (Get-OZOScheduledTask -TaskName "Update OZO PowerShell Module") .LINK https://github.com/onezeroone-dev/OZOTaskScheduler-PowerShell-Repository/blob/main/Documentation/Get-OZOScheduledTask.md #> # Parameters [CmdLetBinding()] Param ( [Parameter(Mandatory=$true,HelpMessage="The task to get")][String] $TaskName ) # Return an OZOTask object $PSCmdlet.WriteObject(([OZOTask]::New($TaskName))) } # New-OZOScheduledTask function Function New-OZOScheduledTask { <# .SYNOPSIS See description. .DESCRIPTION Creates a scheduled task. Uses powershell.exe to run .ps1 scripts and cmd.exe to run everything else. .PARAMETER JsonFile A JSON file that defines a task to schedule. .PARAMETER JsonString A compressed JSON string that defines a task to schedule. .PARAMETER PassThru Return the created task. .EXAMPLE New-OZOScheduledTask -JsonFile "C:\Temp\OZOTaskScheduler-ScheduledTask-Example.json" .EXAMPLE New-OZOScheduledTask -JsonString '{"Name":"Example Scheduled Task","Script":"C:\\Temp\\OZOTaskScheduler-ScheduledTask-Example.ps1","Parameters":"","Directory":"C:\\Temp","Disabled":true,"Settings":{"AllowDemandStart":true,"AllowHardTerminate":true,"AllowStartOnRemoteAppSession":true,"Compatibility":"Win8","DeleteExpiredTaskAfter":"PT0S","DisallowStartIfOnBatteries":false,"DontStopIfGoingOnBatteries":true,"ExecutionTimeLimit":"PT0S","Hidden":false,"IdleSettings":{"StopOnIdleEnd":false,"RestartOnIdle":false},"MultipleInstances":"IgnoreNew","Priority":"Normal","RunOnlyIfNetworkAvailable":false,"WakeToRun":false},"AtLogon":false,"AtReboot":true,"Once":true,"OnceDateTime":{"DateTime":"2099-12-31T09:00:00","RandomDelay":0},"Scheduled":true,"Schedules":[{"WeekDay":"Monday","StartTime":"8:00 AM","RandomDelay":0},{"WeekDay":"Wednesday","StartTime":"8:00 AM","RandomDelay":0},{"WeekDay":"Friday","StartTime":"8:00 AM","RandomDelay":0}]}' .LINK https://github.com/onezeroone-dev/OZOTaskScheduler-PowerShell-Repository/blob/main/Documentation/New-OZOScheduledTask.md #> [CmdLetBinding(SupportsShouldProcess=$true)]Param ( [Parameter(Mandatory=$true,HelpMessage="A JSON file that defines a task to schedule",ParameterSetName="JsonFile")][String]$JsonFile, [Parameter(Mandatory=$true,HelpMessage="A compressed JSON string that defines a task to schedule",ParameterSetName="JsonString")][String]$JsonString, [Parameter(Mandatory=$false,HelpMessage="Return the created task")][Switch]$PassThru ) # Instantiate an OZOJsonTask object [PSCustomObject] $ozoJsonTask = ([OZOJsonTask]::new($JsonFile,$JsonString)) # Determine if the task does not exist and validates If ($null -ne $ozoJsonTask -And $null -ne $ozoJsonTask.Task -And $ozoJsonTask.Task.Exists() -eq $false -And $ozoJsonTask.Task.Validates() -eq $true) { # Task does not exiust and validates; add it If ($PSCmdlet.ShouldProcess($ozoJsonTask.Task.Name, "Create scheduled task")) { $ozoJsonTask.Task.AddTask() # Determine if PassThru is set If ($PassThru.IsPresent -eq $true) { # PassThru is set; return the created task $PSCmdlet.WriteObject((Get-OZOScheduledTask -TaskName $ozoJsonTask.Task.Name)) } } } } # Remove-OZOScheduledTask function Function Remove-OZOScheduledTask { <# .SYNOPSIS See description. .DESCRIPTION Disables and removes a scheduled task, if found. .PARAMETER TaskName The name of the task to remove. .PARAMETER Confirm Prompts for confirmation before removing the task. Use "-Confirm:$false" to remove the task without prompting. .EXAMPLE Remove-OZOScheduledTask -TaskName "Update OZO PowerShell Module" .LINK https://github.com/onezeroone-dev/OZOTaskScheduler-PowerShell-Repository/blob/main/Documentation/Remove-OZOScheduledTask.md #> # Parameters [CmdLetBinding(SupportsShouldProcess=$true,ConfirmImpact="High")]Param ( [Parameter(Mandatory=$true,HelpMessage="The name of the task to remove")][String]$TaskName ) # Get the task [PSCustomObject] $ozoGetScheduledTask = (Get-OZOScheduledTask -TaskName $TaskName) # Determine if the task is not null If ($null -ne $ozoGetScheduledTask) { # Task is not null; call RemoveTask to disable and remove the task If ($PSCmdlet.ShouldProcess($TaskName, "Remove scheduled task")) { $ozoGetScheduledTask.RemoveTask() } } } # Set-OZOScheduledTask function Function Set-OZOScheduledTask { <# .SYNOPSIS See description. .DESCRIPTION Updates a scheduled task. Uses PowerShell to run .ps1 scripts and CMD to run everything else. .PARAMETER JsonFile A JSON file that defines a task to schedule .PARAMETER JsonString A compressed JSON string that defines a task to schedule .PARAMETER PassThru Return the updated task. .EXAMPLE Set-OZOScheduledTask -JsonFile "C:\Temp\OZOTaskScheduler-ScheduledTask-Example.json" .EXAMPLE Set-OZOScheduledTask -JsonString '{"Name":"Example Scheduled Task","Script":"C:\\Temp\\OZOTaskScheduler-ScheduledTask-Example.ps1","Parameters":"","Directory":"C:\\Temp","Disabled":true,"Settings":{"AllowDemandStart":true,"AllowHardTerminate":true,"AllowStartOnRemoteAppSession":true,"Compatibility":"Win8","DeleteExpiredTaskAfter":"PT0S","DisallowStartIfOnBatteries":false,"DontStopIfGoingOnBatteries":true,"ExecutionTimeLimit":"PT0S","Hidden":false,"IdleSettings":{"StopOnIdleEnd":false,"RestartOnIdle":false},"MultipleInstances":"IgnoreNew","Priority":"Normal","RunOnlyIfNetworkAvailable":false,"WakeToRun":false},"AtLogon":false,"AtReboot":true,"Once":true,"OnceDateTime":{"DateTime":"2099-12-31T09:00:00","RandomDelay":0},"Scheduled":true,"Schedules":[{"WeekDay":"Monday","StartTime":"8:00 AM","RandomDelay":0},{"WeekDay":"Wednesday","StartTime":"8:00 AM","RandomDelay":0},{"WeekDay":"Friday","StartTime":"8:00 AM","RandomDelay":0}]}' .LINK https://github.com/onezeroone-dev/OZOTaskScheduler-PowerShell-Repository/blob/main/Documentation/Set-OZOScheduledTask.md #> [CmdLetBinding(SupportsShouldProcess=$true)]Param ( [Parameter(Mandatory=$true,HelpMessage="A JSON file that defines a task to schedule",ParameterSetName="JsonFile")][String]$JsonFile, [Parameter(Mandatory=$true,HelpMessage="A compressed JSON string that defines a task to schedule",ParameterSetName="JsonString")][String]$JsonString, [Parameter(Mandatory=$false,HelpMessage="Return the updated task")][Switch]$PassThru ) # Instantiate an OZOJsonTask object [PSCustomObject] $ozoJsonTask = ([OZOJsonTask]::new($JsonFile,$JsonString)) # Determine if the task exists and validates If ($null -ne $ozoJsonTask -And $null -ne $ozoJsonTask.Task -And $ozoJsonTask.Task.Exists() -eq $true -And $ozoJsonTask.Task.Validates() -eq $true) { # Task exists and validates; update it If ($PSCmdlet.ShouldProcess($ozoJsonTask.Task.Name, "Update scheduled task")) { $ozoJsonTask.Task.UpdateTask() # Determine if PassThru is set If ($PassThru.IsPresent -eq $true) { # PassThru is set; return the updated task $PSCmdlet.WriteObject((Get-OZOScheduledTask -TaskName $ozoJsonTask.Task.Name)) } } } } Export-ModuleMember -Function ` Disable-OZOScheduledTask, Enable-OZOScheduledTask, Export-OZOScheduledTask, Get-OZOScheduledTask, New-OZOScheduledTask, Remove-OZOScheduledTask, Set-OZOScheduledTask # SIG # Begin signature block # MIIvrwYJKoZIhvcNAQcCoIIvoDCCL5wCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCe23W/9Yrl5yFj # +3zmWQlnHwDzE2NTiyfvMESFlV4VG6CCEgUwggVvMIIEV6ADAgECAhBI/JO0YFWU # jTanyYqJ1pQWMA0GCSqGSIb3DQEBDAUAMHsxCzAJBgNVBAYTAkdCMRswGQYDVQQI # DBJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAOBgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoM # EUNvbW9kbyBDQSBMaW1pdGVkMSEwHwYDVQQDDBhBQUEgQ2VydGlmaWNhdGUgU2Vy # dmljZXMwHhcNMjEwNTI1MDAwMDAwWhcNMjgxMjMxMjM1OTU5WjBWMQswCQYDVQQG # EwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMS0wKwYDVQQDEyRTZWN0aWdv # IFB1YmxpYyBDb2RlIFNpZ25pbmcgUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEBAQUA # A4ICDwAwggIKAoICAQCN55QSIgQkdC7/FiMCkoq2rjaFrEfUI5ErPtx94jGgUW+s # hJHjUoq14pbe0IdjJImK/+8Skzt9u7aKvb0Ffyeba2XTpQxpsbxJOZrxbW6q5KCD # J9qaDStQ6Utbs7hkNqR+Sj2pcaths3OzPAsM79szV+W+NDfjlxtd/R8SPYIDdub7 # P2bSlDFp+m2zNKzBenjcklDyZMeqLQSrw2rq4C+np9xu1+j/2iGrQL+57g2extme # me/G3h+pDHazJyCh1rr9gOcB0u/rgimVcI3/uxXP/tEPNqIuTzKQdEZrRzUTdwUz # T2MuuC3hv2WnBGsY2HH6zAjybYmZELGt2z4s5KoYsMYHAXVn3m3pY2MeNn9pib6q # RT5uWl+PoVvLnTCGMOgDs0DGDQ84zWeoU4j6uDBl+m/H5x2xg3RpPqzEaDux5mcz # mrYI4IAFSEDu9oJkRqj1c7AGlfJsZZ+/VVscnFcax3hGfHCqlBuCF6yH6bbJDoEc # QNYWFyn8XJwYK+pF9e+91WdPKF4F7pBMeufG9ND8+s0+MkYTIDaKBOq3qgdGnA2T # OglmmVhcKaO5DKYwODzQRjY1fJy67sPV+Qp2+n4FG0DKkjXp1XrRtX8ArqmQqsV/ # AZwQsRb8zG4Y3G9i/qZQp7h7uJ0VP/4gDHXIIloTlRmQAOka1cKG8eOO7F/05QID # AQABo4IBEjCCAQ4wHwYDVR0jBBgwFoAUoBEKIz6W8Qfs4q8p74Klf9AwpLQwHQYD # VR0OBBYEFDLrkpr/NZZILyhAQnAgNpFcF4XmMA4GA1UdDwEB/wQEAwIBhjAPBgNV # HRMBAf8EBTADAQH/MBMGA1UdJQQMMAoGCCsGAQUFBwMDMBsGA1UdIAQUMBIwBgYE # VR0gADAIBgZngQwBBAEwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybC5jb21v # ZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNAYIKwYBBQUHAQEE # KDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21vZG9jYS5jb20wDQYJKoZI # hvcNAQEMBQADggEBABK/oe+LdJqYRLhpRrWrJAoMpIpnuDqBv0WKfVIHqI0fTiGF # OaNrXi0ghr8QuK55O1PNtPvYRL4G2VxjZ9RAFodEhnIq1jIV9RKDwvnhXRFAZ/ZC # J3LFI+ICOBpMIOLbAffNRk8monxmwFE2tokCVMf8WPtsAO7+mKYulaEMUykfb9gZ # pk+e96wJ6l2CxouvgKe9gUhShDHaMuwV5KZMPWw5c9QLhTkg4IUaaOGnSDip0TYl # d8GNGRbFiExmfS9jzpjoad+sPKhdnckcW67Y8y90z7h+9teDnRGWYpquRRPaf9xH # +9/DUp/mBlXpnYzyOmJRvOwkDynUWICE5EV7WtgwggYaMIIEAqADAgECAhBiHW0M # UgGeO5B5FSCJIRwKMA0GCSqGSIb3DQEBDAUAMFYxCzAJBgNVBAYTAkdCMRgwFgYD # VQQKEw9TZWN0aWdvIExpbWl0ZWQxLTArBgNVBAMTJFNlY3RpZ28gUHVibGljIENv # ZGUgU2lnbmluZyBSb290IFI0NjAeFw0yMTAzMjIwMDAwMDBaFw0zNjAzMjEyMzU5 # NTlaMFQxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxKzAp # BgNVBAMTIlNlY3RpZ28gUHVibGljIENvZGUgU2lnbmluZyBDQSBSMzYwggGiMA0G # CSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCbK51T+jU/jmAGQ2rAz/V/9shTUxjI # ztNsfvxYB5UXeWUzCxEeAEZGbEN4QMgCsJLZUKhWThj/yPqy0iSZhXkZ6Pg2A2NV # DgFigOMYzB2OKhdqfWGVoYW3haT29PSTahYkwmMv0b/83nbeECbiMXhSOtbam+/3 # 6F09fy1tsB8je/RV0mIk8XL/tfCK6cPuYHE215wzrK0h1SWHTxPbPuYkRdkP05Zw # mRmTnAO5/arnY83jeNzhP06ShdnRqtZlV59+8yv+KIhE5ILMqgOZYAENHNX9SJDm # +qxp4VqpB3MV/h53yl41aHU5pledi9lCBbH9JeIkNFICiVHNkRmq4TpxtwfvjsUe # dyz8rNyfQJy/aOs5b4s+ac7IH60B+Ja7TVM+EKv1WuTGwcLmoU3FpOFMbmPj8pz4 # 4MPZ1f9+YEQIQty/NQd/2yGgW+ufflcZ/ZE9o1M7a5Jnqf2i2/uMSWymR8r2oQBM # dlyh2n5HirY4jKnFH/9gRvd+QOfdRrJZb1sCAwEAAaOCAWQwggFgMB8GA1UdIwQY # MBaAFDLrkpr/NZZILyhAQnAgNpFcF4XmMB0GA1UdDgQWBBQPKssghyi47G9IritU # pimqF6TNDDAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADATBgNV # HSUEDDAKBggrBgEFBQcDAzAbBgNVHSAEFDASMAYGBFUdIAAwCAYGZ4EMAQQBMEsG # A1UdHwREMEIwQKA+oDyGOmh0dHA6Ly9jcmwuc2VjdGlnby5jb20vU2VjdGlnb1B1 # YmxpY0NvZGVTaWduaW5nUm9vdFI0Ni5jcmwwewYIKwYBBQUHAQEEbzBtMEYGCCsG # AQUFBzAChjpodHRwOi8vY3J0LnNlY3RpZ28uY29tL1NlY3RpZ29QdWJsaWNDb2Rl # U2lnbmluZ1Jvb3RSNDYucDdjMCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5zZWN0 # aWdvLmNvbTANBgkqhkiG9w0BAQwFAAOCAgEABv+C4XdjNm57oRUgmxP/BP6YdURh # w1aVcdGRP4Wh60BAscjW4HL9hcpkOTz5jUug2oeunbYAowbFC2AKK+cMcXIBD0Zd # OaWTsyNyBBsMLHqafvIhrCymlaS98+QpoBCyKppP0OcxYEdU0hpsaqBBIZOtBajj # cw5+w/KeFvPYfLF/ldYpmlG+vd0xqlqd099iChnyIMvY5HexjO2AmtsbpVn0OhNc # WbWDRF/3sBp6fWXhz7DcML4iTAWS+MVXeNLj1lJziVKEoroGs9Mlizg0bUMbOalO # hOfCipnx8CaLZeVme5yELg09Jlo8BMe80jO37PU8ejfkP9/uPak7VLwELKxAMcJs # zkyeiaerlphwoKx1uHRzNyE6bxuSKcutisqmKL5OTunAvtONEoteSiabkPVSZ2z7 # 6mKnzAfZxCl/3dq3dUNw4rg3sTCggkHSRqTqlLMS7gjrhTqBmzu1L90Y1KWN/Y5J # KdGvspbOrTfOXyXvmPL6E52z1NZJ6ctuMFBQZH3pwWvqURR8AgQdULUvrxjUYbHH # j95Ejza63zdrEcxWLDX6xWls/GDnVNueKjWUH3fTv1Y8Wdho698YADR7TNx8X8z2 # Bev6SivBBOHY+uqiirZtg0y9ShQoPzmCcn63Syatatvx157YK9hlcPmVoa1oDE5/ # L9Uo2bC5a4CH2RwwggZwMIIE2KADAgECAhAbV4ef7ZvUwZ6AKRT0hWSAMA0GCSqG # SIb3DQEBDAUAMFQxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0 # ZWQxKzApBgNVBAMTIlNlY3RpZ28gUHVibGljIENvZGUgU2lnbmluZyBDQSBSMzYw # HhcNMjYwOTE0MDAwMDAwWhcNMjcwOTE0MjM1OTU5WjBiMQswCQYDVQQGEwJVUzER # MA8GA1UECAwIQ29sb3JhZG8xHzAdBgNVBAoMFkxpZXZlcnR6IEFuZHJldyBIdWJl # cnQxHzAdBgNVBAMMFkxpZXZlcnR6IEFuZHJldyBIdWJlcnQwggIiMA0GCSqGSIb3 # DQEBAQUAA4ICDwAwggIKAoICAQCZl/Kqmb1yMMbn0868VlBqxo7LGu/SDy7bO3wz # D6jMoxzh2UT4iFxNnIEpHJq9SzxkhNgy60Lhzr0kGxBXypLJVGKP3JpS9inmF3zn # e/DWJcOFkQPkwYAsqEoJ6xt/klQlJqnwaN7AJUwUIgZ/72j37//UcWxltCMxnwT2 # wVUQ357c3GOafCKfBMUN9dEgUhwxt4x5GZ6Re/WSGKlh+eFtP1wNBsBXoT6Hme5E # u2L5vTYl0fmKVAsTGJ9k7eHvLE7IUWXOD6NI548uxFQ2FeISOdCRt0WmYmpWgi6S # j1Aoh1zLhsMaLgUPP+BXcOy0cO3zDJ+SZky+S4AB37Y0UBqy9hNvX0WbfQh3BF+2 # uWYGxHYc3FtefKKAbhQGNo9EO4pB0SBh/19a/EbbUpv67CRlO8PmGf8hEk6ic8O6 # 28OjkFi5ByIB7y/XKPKCZmeS/TWDrkyMpu8h9NGqHyWONE5oxpqCYMm3a0L92kfd # zY5e6vSLDuZZjlANRUm3VLnbxZcD8znUvaDbR+ALdJeSZryev8NVMnfOzwZM6vKz # fTPbYpFRBo0owFluKK85CKeZ8z+NyKQfvWh5gYXZw0JFZwdE4SE46fnNLJ3z9loF # Qly+7Dn2zUd/+CxwlRgkuEO1ng6EJ1OAAeA7PixyZob0gjg7ob7YzSK2v4AV/QdQ # oXa04QIDAQABo4IBrjCCAaowHwYDVR0jBBgwFoAUDyrLIIcouOxvSK4rVKYpqhek # zQwwHQYDVR0OBBYEFMiE5GpeP6V7hsDSdHBbSjojZscBMA4GA1UdDwEB/wQEAwIH # gDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMEoGA1UdIARDMEEw # NQYMKwYBBAGyMQECAQMCMCUwIwYIKwYBBQUHAgEWF2h0dHBzOi8vc2VjdGlnby5j # b20vQ1BTMAgGBmeBDAEEATBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLnNl # Y3RpZ28uY29tL1NlY3RpZ29QdWJsaWNDb2RlU2lnbmluZ0NBUjM2LmNybDB5Bggr # BgEFBQcBAQRtMGswRAYIKwYBBQUHMAKGOGh0dHA6Ly9jcnQuc2VjdGlnby5jb20v # U2VjdGlnb1B1YmxpY0NvZGVTaWduaW5nQ0FSMzYuY3J0MCMGCCsGAQUFBzABhhdo # dHRwOi8vb2NzcC5zZWN0aWdvLmNvbTAjBgNVHREEHDAagRhhbGlldmVydHpAb25l # emVyb29uZS5kZXYwDQYJKoZIhvcNAQEMBQADggGBACvVVz+HNOH8p+CBcwmrYBVx # mlWNkTz84gmJZ/57FF5XK4kHeo6+QFalQkBSkiDheZa13+FdwUYf1zcJu5hfymkZ # 9zmBhLlIkdluT1llv9GKr/tQaJFd2NXoLiJ7ysqRDbu47U1RR7XGU42HZHLUHa00 # Ha8KDHKYeRcCBMhltjpBK4Hw5daTzT+5fLhMHaOy3ZuWn4Fgx8zEcH1/cdEqnWNn # NxxCWbKcp4sRI0v9uStoUYGpvX8LkYtMUUuWPr7tZAAZiPws6ctOrdROmdEwfyah # IMt2drMfTXZVPpL/5okl37qOYhzV+u6vEvbc3xeAicAgsgOYkvvx5L2iUyFfGdLb # JN4ioPTCKcvN7oXFPwHfyGqwBxARyJy3so2OAFvY4AGY/9DwQVE8KZuplrkktC7Y # MzjB9SjEtvqnyW+h0fHd3H85B8GJiL8slRmIb5f+jJDvmu5RgoaoaMAYeX/V4HXY # gpSD92Hq5AwUjjUvj1WPk+AagPBupA+J4OM2S6eO0DGCHQAwghz8AgEBMGgwVDEL # MAkGA1UEBhMCR0IxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDErMCkGA1UEAxMi # U2VjdGlnbyBQdWJsaWMgQ29kZSBTaWduaW5nIENBIFIzNgIQG1eHn+2b1MGegCkU # 9IVkgDANBglghkgBZQMEAgEFAKB8MBAGCisGAQQBgjcCAQwxAjAAMBkGCSqGSIb3 # DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEV # MC8GCSqGSIb3DQEJBDEiBCAd+vPE5kc6ZJzmdEXs/AjuCm0ipMH0XxyZ3YqewlRT # bjANBgkqhkiG9w0BAQEFAASCAgBcCjoP46Jqm6B7ZklTjb9HUV1jxmu5pqS13AK5 # 6ccB0s0Y+8X0aAY+KPZVLvCgYV386cgoRbqN6uaXvnLzohfdaXu6babRqUmYhNP3 # XqN/yNy5FmQ1ZSzUJWoNt11mdY+bLL/Gya6IdZ1nqOqSEnhlrY3iMDQAlzunVrwr # PjL9jUjzPC/vmXtHt9bCyhwoNC2ajKJYUg0cGQr0E+JLs3RC0icBWKkSo+IbBbj9 # po3/nFlqBeHZMxdvxSMj/BMw1ZcYZPtpQjRPYsf00ZogGy22VKSgk6uWTfdE5hqm # C61LGXic4720T8uVW3eOliCc6V3P6A8tIUsWsQjPEGbTMmmb3eWpWruE7gwlBsZ3 # g9kx+EstQndVTc96VJxPPOG5xHyv8AtJ4gJRBilwU/SYSyJ2vyapuFQCGp/dhffK # 7Pr3Wu7U8zQGXuqViBnmcapvuCl3s2658qDGroQGGT2HDfEEZ957qfNnUngy71cn # wjKoKGV5V7+TVW7ri0wNZf2ndEobkHXC/1DcZA5DhQB1C1ty76Ot27FUc58pCUuN # Csu46fr81eEfgmep4lORZsxonF10czshvo+W+ztXGEfDk0zUTosYlwsWVZexHRcf # yNDIxVtHWt7KsaWaokU50eewH9MAPWqpe4ecR199YyNHAwtlK4bEl2bRph9ajZ9C # wr73+qGCGeswghnnBgorBgEEAYI3AwMBMYIZ1zCCGdMGCSqGSIb3DQEHAqCCGcQw # ghnAAgEDMQ8wDQYJYIZIAWUDBAICBQAwgfcGCyqGSIb3DQEJEAEEoIHnBIHkMIHh # AgEBBgorBgEEAbIxAgEBMDEwDQYJYIZIAWUDBAIBBQAEIKwMOjNcc9OUUMMyDJvM # 3VjgbdsH1JLMyzCfs4t3PWhXAhQ02C8HFlUjHxNKsd8uDZyrARrLTRgPMjAyNjA5 # MTcyMzAwMjRaoHakdDByMQswCQYDVQQGEwJHQjEXMBUGA1UECBMOR3JlYXRlciBM # b25kb24xGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDEwMC4GA1UEAxMnU2VjdGln # byBQdWJsaWMgVGltZSBTdGFtcGluZyBTaWduZXIgUjM3oIIUFzCCBuIwggTKoAMC # AQICEQDnTvJVsFBP+tum3/f8i6MVMA0GCSqGSIb3DQEBDAUAMFUxCzAJBgNVBAYT # AkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxLDAqBgNVBAMTI1NlY3RpZ28g # UHVibGljIFRpbWUgU3RhbXBpbmcgQ0EgUjQxMB4XDTI2MDMyNTAwMDAwMFoXDTM3 # MDYyNDIzNTk1OVowcjELMAkGA1UEBhMCR0IxFzAVBgNVBAgTDkdyZWF0ZXIgTG9u # ZG9uMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxMDAuBgNVBAMTJ1NlY3RpZ28g # UHVibGljIFRpbWUgU3RhbXBpbmcgU2lnbmVyIFIzNzCCAiIwDQYJKoZIhvcNAQEB # BQADggIPADCCAgoCggIBALL/w21L3FDZRS0FEXfZuPtUrefibnRSqOT/NNyJLOJh # XjQfUspqHT+gSSVgbjYThUI/cO+wFQHoOakKQNnSMKdkE8gR69ofXlkk5DAVY/Zl # evliOUmlvrw2Vuz4SU28rHfb/Vgd17eqpRIvJuO6XE8vPpPzn4c4iorszUF6nwuy # nKEQ/+rqfDmQbFNKsa+5+Z4f4kXwKdUFxUwUDjQWUhiHRwMlUWGF9N91aAvL+9a4 # sxCgqR/ez8W8HJ/XqvSu1vIeb+J6bDFKKgkv3PJkMMpQ0BsdeXR2FejZXFRXY1w9 # dZe6gqyMv7px+TpWbYMefECUV0WxoEMgXUk6RKcLo94uUHOdmfZu4Xe8ghglyro3 # /N4VEKTj8dcPPvOBGxFEx1QH6uHKTkWhloGPDScurcZnd8KUtTHl6zmlQDHM04Mw # GfsmQViKnYEAYE8RHl5XRE6GTq0ZMb59SIyJX6+CODVic/kW+dhbIS1Z5AP8HaGn # e/PRG+12QzSneKDJp3Ot+k4GrmmlWT9iy6FNCQ/32K+d4cAZ+Ll7uWbEn6Z6gE+t # Eu7MyZvzWvPNsRKMkcyyflFW1zpRyzutwypALXc9Qg7sFsYERNXa58KZXqU9Onc/ # tck6+adQJFM9tW8xOnE//P5I4eDj84IGGKqzgUD37ihC+WST3DfY0YBKWL0Zaubn # AgMBAAGjggGOMIIBijAfBgNVHSMEGDAWgBQ6dKUMZ8ZCUML9tfzHuyk0gvR6uTAd # BgNVHQ4EFgQUYRDpehKvUcSF1PLPpHQPUM0gr/gwDgYDVR0PAQH/BAQDAgbAMAwG # A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwSgYDVR0gBEMwQTAI # BgZngQwBBAIwNQYMKwYBBAGyMQECAQMIMCUwIwYIKwYBBQUHAgEWF2h0dHBzOi8v # c2VjdGlnby5jb20vQ1BTMEoGA1UdHwRDMEEwP6A9oDuGOWh0dHA6Ly9jcmwuc2Vj # dGlnby5jb20vU2VjdGlnb1B1YmxpY1RpbWVTdGFtcGluZ0NBUjQxLmNybDB6Bggr # BgEFBQcBAQRuMGwwRQYIKwYBBQUHMAKGOWh0dHA6Ly9jcnQuc2VjdGlnby5jb20v # U2VjdGlnb1B1YmxpY1RpbWVTdGFtcGluZ0NBUjQxLmNydDAjBggrBgEFBQcwAYYX # aHR0cDovL29jc3Auc2VjdGlnby5jb20wDQYJKoZIhvcNAQEMBQADggIBAAPqPY3R # rM36GXqTpsoHn9TpW5I6z3dkFvc9zPL1W0Egq7j3jtnkbAvRoWeAjGX4ZK4sWsmA # +u4EJG8okQmybuS/4tDUI5UIQb21n4hG2vihxShrneWB0VoQ2VLQ3jCCRmRtAQ+/ # 7H7WVKNiH5Pgl4v2ZTOdPsStzpKnl1YuRrmww/+bcZmLqgk909ywIpZqAfubYfbE # MYjIckLk90f2mG+L8qaGSS2JJVM02pV5XltZ1fbOFETpRN/PQhwygIv33qUUjJ1f # E4ITgw0McMzRqziWdOJP8ocxxw7qXxz1OdRWCalyL1qvUgAFnZTVdSRiMYZKf0wL # cQcM/1Xf1W4FW9nff8ERX8RZJGt/TtPuMWmUpf6BCv9Q6o8YyUTtknvZRpSQ0nLt # tWXdtwsrN2mMgfMuR//gxVrVXvDzCoK/lbiA6dEZOW53lQwBFtEzwE/FH8Jdhegy # Yg4PymZOTZrGBEvgsbxe25yEhJ0IdGa1pwCYsarldJhJVMdNcAOU7jyIMqHcczav # 3wtIXp/SwbXZ3xX0mfsLfANSJ47G4qPgx1atb6GIlTaQXzu/p4fTQeAIUVzZXT4K # 984IyfuO7NLjWMtog1wGUpZD98pv+4Mt9Y5bvfPUjaUVjtePy1DVdi0rl5ESNYi0 # zyOmXVxtA5zzxu1H7RdLZOZugT/XjX69rY9bMIIGpzCCBI+gAwIBAgIRAJCsCHIg # /cWnxGtcxw33PQYwDQYJKoZIhvcNAQEMBQAwVzELMAkGA1UEBhMCR0IxGDAWBgNV # BAoTD1NlY3RpZ28gTGltaXRlZDEuMCwGA1UEAxMlU2VjdGlnbyBQdWJsaWMgVGlt # ZSBTdGFtcGluZyBSb290IFI0NjAeFw0yNjAzMjUwMDAwMDBaFw00MTAzMjQyMzU5 # NTlaMFUxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxLDAq # BgNVBAMTI1NlY3RpZ28gUHVibGljIFRpbWUgU3RhbXBpbmcgQ0EgUjQxMIICIjAN # BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAruRKogGtghxi+WYtW5oDzPDVF8GG # SfgbUKh6bONxi0wvrI1S8qbAYfvLr/ky5ILVRg//70pgNKq8xC3/WEQodjEwAP2h # mkGShoNAUQps4kd6Wwp74Fo7RlwQ1Mp949ytpWQDvCsYBbZccDmBAJC/ggqiuL/c # 805fGcMw6TzIgyBWuUx5PGp9YnheSNPXFzaz0MPtREdZYk4WhtM+hazqasMWVpj0 # WUAcNhN9vO/FAdWy9Gafdb7lmYLDKTTYjwqAY9P9RfixPPjUaJH6mnBSNBdrX7a0 # Qdlux0ApS0fc48RW1m+W3tq3HiHzch1FHyhiLzCNjc6MUpcV5xalBvPOw/FtQo/A # xaJOvPCSsVrx0f/WkMpEm3fvVbrY9+oo9rIKv9ducE6VGfwIAtKYedG0bO4Ba1Mm # lxPcErDqjLwggvrBJu73fwXpkhtE0hzV0psgm2vhQs3pHll9N00SHBdy2qndEcNu # Dh+46XouM2hoXCO533YQQOHPEUnMTWOo3hyxx5kjDE5PVqp+x+HS4VAT+WBMG4Gz # eLr9YvZbU5x5YvLdcR1dErV/QRYK55rp019fZFF2NR+TkSW0WcmQ3b5taGcrXg49 # EpzKM6/mEpnSJXg1E13X6GO29rWs/LNvkGzsS8XGoRCGBls6ruofeebSsHADR3Ge # IE5gIU927bjokLECAwEAAaOCAW4wggFqMB8GA1UdIwQYMBaAFPZ3at0//QET/xah # bIICL9AKPRQlMB0GA1UdDgQWBBQ6dKUMZ8ZCUML9tfzHuyk0gvR6uTAOBgNVHQ8B # Af8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADATBgNVHSUEDDAKBggrBgEFBQcD # CDAjBgNVHSAEHDAaMAgGBmeBDAEEAjAOBgwrBgEEAbIxAQIBAwgwTAYDVR0fBEUw # QzBBoD+gPYY7aHR0cDovL2NybC5zZWN0aWdvLmNvbS9TZWN0aWdvUHVibGljVGlt # ZVN0YW1waW5nUm9vdFI0Ni5jcmwwfAYIKwYBBQUHAQEEcDBuMEcGCCsGAQUFBzAC # hjtodHRwOi8vY3J0LnNlY3RpZ28uY29tL1NlY3RpZ29QdWJsaWNUaW1lU3RhbXBp # bmdSb290UjQ2LnA3YzAjBggrBgEFBQcwAYYXaHR0cDovL29jc3Auc2VjdGlnby5j # b20wDQYJKoZIhvcNAQEMBQADggIBADLeUkdm8Z4DZvjfKHOhqu+hsdXNt+X5F+48 # PB6PTAJCRgA3qxxO3YbAV7baps4K/+2WWoWUspBkT4T2NXK49NvJAfCsztHSgtqk # AQMLX4KjCypJF/+m2Ktrk993g+gcgKdv1yg9C3JmYdJCnnL0ga+pZ/Wo1+rtXZ8d # nwO8RCstTN6gYX0ElFi7Y7NpxbdBC1S6bc05V/SA9HC/ojj33W6GdwnpU/iVylSk # dkoHtHeGIhQLT2ZH0qPM9Wdce8v2fZsDCJQQJ8rll7OGLDbsXa2CLf0MRN9Twzif # Q3rEuAXOx/TkzkZRFfwL34hf1XqSmaYq2tTMy2LgsPrqC2Z/6ZKb3fgrzU0vphB4 # wSTWulitY/KlxbvoyKvrBvUCCx4sgeqf8aR65CbvM5MN/d/lahfXipU2NlY0cXcn # GS61XpmeGKd8It92/lufApZR9x6o5qMJWe0jq4JsfGMGDpIKx7FzkB8gaejuBUW/ # CJ9Phc40+xJRonvVewn4S9yJVRWeM47irGbR9YlN3xruM/yZzhk+rAm9AW06nv7o # b6RQkAXR+cTxiAPy620FF41NrViYB4UyKpzfx7x8jh4ubTOMz954YIdqyeiqqtsb # BwXjWLP0dfMUPA3iIPnPdBKGnodGJTdSlPAMmKJdyvTPqmOXs/LMnf+2Za0Z6FXs # IB9z9aXLMIIGgjCCBGqgAwIBAgIQNsKwvXwbOuejs902y8l1aDANBgkqhkiG9w0B # AQwFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNV # BAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsx # LjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw # HhcNMjEwMzIyMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjBXMQswCQYDVQQGEwJHQjEY # MBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMS4wLAYDVQQDEyVTZWN0aWdvIFB1Ymxp # YyBUaW1lIFN0YW1waW5nIFJvb3QgUjQ2MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A # MIICCgKCAgEAiJ3YuUVnnR3d6LkmgZpUVMB8SQWbzFoVD9mUEES0QUCBdxSZqdTk # dizICFNeINCSJS+lV1ipnW5ihkQyC0cRLWXUJzodqpnMRs46npiJPHrfLBOifjfh # pdXJ2aHHsPHggGsCi7uE0awqKggE/LkYw3sqaBia67h/3awoqNvGqiFRJ+OTWYmU # CO2GAXsePHi+/JUNAax3kpqstbl3vcTdOGhtKShvZIvjwulRH87rbukNyHGWX5tN # K/WABKf+Gnoi4cmisS7oSimgHUI0Wn/4elNd40BFdSZ1EwpuddZ+Wr7+Dfo0lcHf # lm/FDDrOJ3rWqauUP8hsokDoI7D/yUVI9DAE/WK3Jl3C4LKwIpn1mNzMyptRwsXK # rop06m7NUNHdlTDEMovXAIDGAvYynPt5lutv8lZeI5w3MOlCybAZDpK3Dy1MKo+6 # aEtE9vtiTMzz/o2dYfdP0KWZwZIXbYsTIlg1YIetCpi5s14qiXOpRsKqFKqav9R1 # R5vj3NgevsAsvxsAnI8Oa5s2oy25qhsoBIGo/zi6GpxFj+mOdh35Xn91y72J4RGO # JEoqzEIbW3q0b2iPuWLA911cRxgY5SJYubvjay3nSMbBPPFsyl6mY4/WYucmyS9l # o3l7jk27MAe145GWxK4O3m3gEFEIkv7kRmefDR7Oe2T1HxAnICQvr9sCAwEAAaOC # ARYwggESMB8GA1UdIwQYMBaAFFN5v1qqK0rPVIDh2JvAnfKyA2bLMB0GA1UdDgQW # BBT2d2rdP/0BE/8WoWyCAi/QCj0UJTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/ # BAUwAwEB/zATBgNVHSUEDDAKBggrBgEFBQcDCDARBgNVHSAECjAIMAYGBFUdIAAw # UAYDVR0fBEkwRzBFoEOgQYY/aHR0cDovL2NybC51c2VydHJ1c3QuY29tL1VTRVJU # cnVzdFJTQUNlcnRpZmljYXRpb25BdXRob3JpdHkuY3JsMDUGCCsGAQUFBwEBBCkw # JzAlBggrBgEFBQcwAYYZaHR0cDovL29jc3AudXNlcnRydXN0LmNvbTANBgkqhkiG # 9w0BAQwFAAOCAgEADr5lQe1oRLjlocXUEYfktzsljOt+2sgXke3Y8UPEooU5y39r # AARaAdAxUeiX1ktLJ3+lgxtoLQhn5cFb3GF2SSZRX8ptQ6IvuD3wz/LNHKpQ5nX8 # hjsDLRhsyeIiJsms9yAWnvdYOdEMq1W61KE9JlBkB20XBee6JaXx4UBErc+YuoSb # 1SxVf7nkNtUjPfcxuFtrQdRMRi/fInV/AobE8Gw/8yBMQKKaHt5eia8ybT8Y/Ffa # 6HAJyz9gvEOcF1VWXG8OMeM7Vy7Bs6mSIkYeYtddU1ux1dQLbEGur18ut97wgGwD # iGinCwKPyFO7ApcmVJOtlw9FVJxw/mL1TbyBns4zOgkaXFnnfzg4qbSvnrwyj1Ni # urMp4pmAWjR+Pb/SIduPnmFzbSN/G8reZCL4fvGlvPFk4Uab/JVCSmj59+/mB2Gn # 6G/UYOy8k60mKcmaAZsEVkhOFuoj4we8CYyaR9vd9PGZKSinaZIkvVjbH/3nlLb0 # a7SBIkiRzfPfS9T+JesylbHa1LtRV9U/7m0q7Ma2CQ/t392ioOssXW7oKLdOmMBl # 14suVFBmbzrt5V5cQPnwtd3UOTpS9oCG+ZZheiIvPgkDmA8FzPsnfXW5qHELB43E # T7HHFHeRPRYrMBKjkb8/IN7Po0d0hQoF4TeMM+zYAJzoKQnVKOLg8pZVPT8xggST # MIIEjwIBATBqMFUxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0 # ZWQxLDAqBgNVBAMTI1NlY3RpZ28gUHVibGljIFRpbWUgU3RhbXBpbmcgQ0EgUjQx # AhEA507yVbBQT/rbpt/3/IujFTANBglghkgBZQMEAgIFAKCCAfowGgYJKoZIhvcN # AQkDMQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNjA5MTcyMzAwMjRa # MD8GCSqGSIb3DQEJBDEyBDB9XNSay0wvngTsZq6oil3Vgu+xPHi4NxEeqViU/P5C # scO3C+Ixx5lfmzhPfMGgceYwggF7BgsqhkiG9w0BCRACDDGCAWowggFmMIIBYjAW # BBTpeBipKNoVCp/hv5zMequ5oA7urDCBiAQUZcMoaW99TlAs/QPHwgaXGMr7908w # cDBbpFkwVzELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDEu # MCwGA1UEAxMlU2VjdGlnbyBQdWJsaWMgVGltZSBTdGFtcGluZyBSb290IFI0NgIR # AJCsCHIg/cWnxGtcxw33PQYwgbwEFIU9Yy2TgoJhfNCQNcSR3pLBQtrHMIGjMIGO # pIGLMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNleTEUMBIGA1UE # BxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEu # MCwGA1UEAxMlVVNFUlRydXN0IFJTQSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQIQ # NsKwvXwbOuejs902y8l1aDANBgkqhkiG9w0BAQEFAASCAgA244snRw9a871LFOCq # +9DULFTambY07bPad52VAuHUfjVNFMrqBmcNQwyGDqOPjpZrPyqrKZcH0VQpkCDj # OEkpekczloFsAAx10FQLAnTsio/9aYoeDXRDCQN4BYzeC0RWd4rKnMO3gUONMlce # /BvYpmDLuyKaQVS91vUq9nxL/XjnF2D8XnJ44gnrayhWpVs8RYfyLSVEetyohPOU # 4pIdzP1d0t/mSL/2qedhGp4QNmKSMuo+N17O5bVbn7h5Cx5fOnAtZ/JCfvZEv1XY # qRiy/MPLLcQwNVvcKSklSyRuF2Xhm5t01A5iOQNeEh3FvqUtFI7c0kfgtec54jUp # aYOpKi56BuZO+MbUm4pKjEQqzf0cKIKrflXsWLfBvhpiN0fdZskk9LgbH0/f9Bmy # Xxgwft6LL3FW/cgunJvVxojXxekAMvgjFv7od24EHwy+quedTDSNh8nEmSFiHCn8 # zhJOJEK1gbXlRQi4Og/VwEIlsCqxUcpRWpAF42H8h+V82O9VHBDcllKZc+HC39au # d1q3EJNSVBHZqjsH22l9bvkxxRv80uJaBT8E+xiEDYhJwTYr2ffg1jNjPgBRRzZ7 # jP2Ft8E3y4+aNznr7kHjutZxcNWWkL+Qk+HNLT11Wmte3WWm/JGsdDIlbNn7yLIa # LNINM8jDNF76unzmt27bmPUXMA== # SIG # End signature block |