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