Public/New-SpecScheduledTask.ps1

Function New-SpecScheduledTask {
    <#
    .SYNOPSIS
    This function creates a new scheduled task with specified settings and options.
 
    .DESCRIPTION
    The New-SpecScheduledTask function creates a new scheduled task based on the provided parameters. It supports creating various types of tasks with different triggers, actions, principals, settings, and options.
 
    .PARAMETER Taskname
    Specifies the name of the scheduled task. If not provided, the leaf name of the specified path will be used.
 
    .PARAMETER Description
    Specifies the description of the scheduled task.
 
    .PARAMETER TaskFolder
    Specifies the folder in which the task will be created. Default is 'Specsavers'.
 
    .PARAMETER Path
    Specifies the path to the executable or script for the task action.
 
    .PARAMETER Arguments
    Specifies the arguments to be passed to the task action. (Do not use if it is a .ps1 file)
 
    .PARAMETER StartIn
    Specifies the working directory for the task action. (Do not use if it is a .ps1 file)
 
    .PARAMETER Trigger
    Specifies the trigger type for the task. Valid values are "AtLogon," "AtStartup," and "Daily."
 
    .PARAMETER RunAs
    Specifies the user account or security group to run the task as. Valid values are 'NT AUTHORITY\SYSTEM' and 'BUILTIN\Users'. Default is 'NT AUTHORITY\SYSTEM''.
 
    .PARAMETER DelayTaskByXMinutes
    Specifies the number of minutes to delay the task execution. e.g. 15 If you need to use over an hour, continue to use minutes, eg 68 (equivalent to 1 hour 8 minutes)
 
    .PARAMETER RunWithHighestPrivilege
    Indicates whether the task should be run with the highest privileges.
 
    .PARAMETER StartTaskImmediately
    Indicates whether to start the task immediately after creating it.
 
    .PARAMETER IgnoreTestPath
    Allows ignoring the path verification step. If the path does not exist then the scheduled task will still be created.
 
    .PARAMETER RandomiseTaskUpToXMinutes
    Specifies the number of minutes to randomly delay the task execution (used with the "Daily" trigger type). eg 30 If you need to use over an hour, continue to use minutes, eg 68 (equivalent to 1 hour 8 minutes)
 
    .PARAMETER Time
    Specifies the time for the trigger (used with the "Daily" trigger type). '04:00pm', '16:00', '3:12am' and '03:12' are all valid time values.
 
    .EXAMPLE
    $taskStatus = New-SpecScheduledTask -Description "My Task" -Path "C:\Scripts\MyScript.ps1" -Trigger "Daily" -Time "02:00" -RunWithHighestPrivilege -StartTaskImmediately
    Creates a new scheduled task named "My Task" that runs a PowerShell script daily at 2:00 AM with highest privileges and starts the task immediately.
 
    .EXAMPLE
    $taskStatus = New-SpecScheduledTask -Taskname "MyTask" -Description "My Task" -Path "C:\Program Files\MyApp.exe" -RunAs "NT AUTHORITY\SYSTEM" -DelayTaskByXMinutes 15 -IgnoreTestPath
    Creates a new scheduled task named "MyTask" that runs an executable with a 15-minute delay and ignores path verification.
 
    .NOTES
    Author: owen.heaume
    Date: August 10, 2023
    Version:
        2.0 - initial function
        2.1 - remove check to see if startin or args used on .ps1 script as it is now allowed
        2.2 - add parameter $RepeatEveryXMinutes to allow for repeating tasks and relevant code updates
        2.3 - Added TurnOffExecutionTimeLimit parameter to turn off the execution time limit.
 
    .OUTPUTS
    Status Codes:
    - 901: Path does not exist and -IgnoreTestPath not used.
    - 900: Task already exists.
    - 910: An error occurred assigning task action configuration.
    - 911: An error occurred assigning task trigger configuration.
    - 912: An error occurred assigning task principal configuration.
    - 913: An error occurred assigning task settings configuration.
    - 914: An error occurred creating the task using Register-ScheduledTask.
    - 915: An error occurred starting the task using Start-ScheduledTask.
    - 916: Incorrect parameter combination ( -arguments or -Startin with .ps1 extension n path)
    - 917: Incorrect parameter combination (Do not use '-DelayTaskByXMinutes' with '-randomiseTaskUpToXminutes')
 
    - 0: Successful task creation.
    #>


    [cmdletbinding()]

    Param (
        [parameter (mandatory = $false)]
        [string]$Taskname,

        [parameter (mandatory = $true)]
        [string]$Description,

        [parameter (mandatory = $false)]
        [string]$TaskFolder = 'Specsavers',

        [parameter (mandatory = $true)]
        [string]$Path,

        [parameter (mandatory = $false)]
        [string]$Arguments,

        [parameter (mandatory = $false)]
        [string]$StartIn,

        [parameter (mandatory = $false)]
        [ValidateSet('AtLogon', 'AtStartup', 'Daily')]
        [string]$Trigger = 'AtStartUp',

        [parameter (mandatory = $false)]
        [ValidateSet('BUILTIN\Users', 'NT AUTHORITY\SYSTEM', 'BUILTIN\Administrators')]
        [string]$RunAs = 'NT AUTHORITY\SYSTEM',

        [parameter (mandatory = $false)]
        [int]$DelayTaskByXMinutes,

        [parameter (mandatory = $false)]
        $RepeatEveryXMinutes,

        [switch]$RunWithHighestPrivilege,

        [switch]$StartTaskImmediately,

        [switch]$IgnoreTestPath,

        [switch]$TurnOffExecutionTimeLimit
    )

    DynamicParam {
        if ($Trigger -eq 'Daily') {
            # Define parameter attributes
            $paramAttributes = New-Object -Type System.Management.Automation.ParameterAttribute
            $paramAttributes.Mandatory = $true

            # Create collection of the attributes
            $paramAttributesCollect = New-Object -Type `
                System.Collections.ObjectModel.Collection[System.Attribute]
            $paramAttributesCollect.Add($paramAttributes)

            # Create parameter with name, type, and attributes
            $dynParam1 = New-Object -Type `
                System.Management.Automation.RuntimeDefinedParameter("RandomiseTaskUpToXMinutes", [int], $paramAttributesCollect)
            $dynParam2 = New-Object -Type `
                System.Management.Automation.RuntimeDefinedParameter("Time", [string], $paramAttributesCollect)

            # Add parameter to parameter dictionary and return the object
            $paramDictionary = New-Object -Type System.Management.Automation.RuntimeDefinedParameterDictionary
            $paramDictionary.Add("RandomiseTaskUpToXMinutes", $dynParam1)
            $paramDictionary.Add("Time", $dynParam2)
            return $paramDictionary
        }
    }

    Begin {
        # Assign dynamic parameters to variables so they can be tested if they were used later in the script.
        $RandomiseTaskUpToXMinutes = $PSBoundParameters['RandomiseTaskUpToXMinutes']
        $Time = $PSBoundParameters['Time']

    }

    Process {
        # check parameters combinations are valid
        # if ((Test-SpecPS1Extension -filePath $path -verbose:$false) -and ($Arguments -or $StartIn)) {
        # Write-warning "Please do not use '-Arguments' or '-StartIn' when the path contains a PowerShell (.ps1) script"
        # return 916
        # }

        if ($Trigger -eq 'Daily' -and $RandomiseTaskUpToXMinutes -and $DelayTaskByXMinutes) {
            Write-Warning "Please do not use '-DelayTaskByXMinutes' with '-randomiseTaskUpToXminutes'"
            return 917
        }

        # Path check
        $pathExists = Test-Path $Path -PathType Leaf

        if ($IgnoreTestPath) {
            Write-Verbose "Skipping path verification due to -IgnoreTestPath switch"
        }

        if ($pathExists -eq $false -and $IgnoreTestPath -eq $false) {
            Write-Warning "The path $path does not exist. Please use the -IgnoreTestPath switch to override this error."
            return 901
        }

        # If -TaskName was not used, then get the leaf name of the path to use instead
        if ([string]::IsNullOrEmpty($taskname)) {
            $TaskName = Get-SpecLeafName -Path $Path
        }

        # Check if the task already exists
        $TaskExists = Get-SpecScheduledTask -TaskName $TaskName

        if ($TaskExists -eq 900) {
            # 900 is the error code for the Get-SpecScheduledTask function
            Write-Host "The task $TaskName already exists. Please use a different name." -ForegroundColor DarkYellow
            return 900
        }

        # Create the scheduled task action
        $taskAction = New-SpecScheduledTaskAction -IsPs1Script (Test-SpecPS1Extension -filePath $Path) -path $Path -arguments $Arguments -startin $StartIn

        if ($TaskAction -eq 910) {
            # 910 is the error code for the New-SpecScheduledTaskAction function
            Write-Warning "New-SpecScheduledTaskAction: Task Action configuration could not be assigned."
            return 910
        }

        # Create the task trigger
        if ($Trigger -eq 'daily') {
            Write-Verbose "The '-daily' trigger has been selected"
            $taskTrigger = New-SpecScheduledTaskTrigger -trigger $Trigger -time $Time -RandomiseTaskUpTo $RandomiseTaskUpToXMinutes -RepeatEveryXMinutes $RepeatEveryXMinutes
        } else {
            $taskTrigger = New-SpecScheduledTaskTrigger -trigger $Trigger -DelayTaskUpToXMinutes $DelayTaskByXMinutes -RepeatEveryXMinutes $RepeatEveryXMinutes
        }

        if ($taskTrigger -eq 911) {
            # 911 is the error code for the New-SpecScheduledTaskTrigger function
            Write-Warning "New-SpecScheduledTaskTrigger: Trigger configuration could not be assigned."
            return 911
        }

        # Set the task run level
        if ($RunWithHighestPrivilege.IsPresent) {
            Write-Verbose "The '-RunWithHighestPrivilege' switch has been selected"
            $taskPrincipal = New-SpecScheduledTaskPrincipal -RunAs $RunAs -RunWithHighestPrivilege
        } else {
            $taskPrincipal = New-SpecScheduledTaskPrincipal -RunAs $RunAs
        }

        if ($taskPrincipal -eq 912) {
            # 912 is the error code for the New-SpecScheduledTaskPrincipal function
            Write-Warning "New-SpecScheduledTaskPrincipal: Task principal could not be assigned."
            return 912
        }

        # Set the task settings
        if ($TurnOffExecutionTimeLimit.IsPresent) {
            $taskSettings = New-SpecScheduledTaskSettingsSet -TurnOffExecutionTimeLimit
        } else {
            $taskSettings = New-SpecScheduledTaskSettingsSet
        }

        if ($taskSettings -eq 913) {
            # 913 is the error code for the New-SpecScheduledTaskSettingsSet function
            Write-Warning "New-SpecScheduledTaskSettingsSet: Task settings could not be assigned."
            return 913
        }

        # Create the scheduled task
        $params = @{
            TaskName    = $TaskName
            Description = $Description
            TaskPath    = "\$TaskFolder"
            Action      = $taskAction
            Trigger     = $taskTrigger
            Principal   = $taskPrincipal
            Settings    = $taskSettings
        }

        $result = Create-SpecScheduledTask @params

        if ($result -eq 914) {
            Write-Warning "Create-SpecScheduledTask: An error occured creating the task."
            return 914
        }

        # Start the task immediately if the -StartTaskImmediately switch was used
        if ($StartTaskImmediately.IsPresent) {
            try {
                Write-Verbose "Starting task $TaskName"
                Start-ScheduledTask -TaskName $TaskName -TaskPath "\$TaskFolder" -ea stop -ev x
                Write-Verbose "Task $TaskName started successfully."
            } catch {
                Write-Warning "Start-ScheduledTask: An error occured starting the task: $x"
                return 915
            }
        }

        #write-verbose "Task $TaskName created successfully."
        return 0
    }
}