Public/New-SpecScheduledTask.ps1
Function New-SpecScheduledTask { <# .SYNOPSIS Creates a new scheduled task. .DESCRIPTION The New-SpecScheduledTask function creates a new scheduled task based on the provided parameters. .PARAMETER TaskName Specifies the name of the task. .PARAMETER TaskDescription Specifies the description of the task. .PARAMETER Trigger Specifies the trigger type for the task. Valid options are 'AtLogon', 'AtStartup', and 'Daily'. .PARAMETER Time Specifies the time at which the task should run. Applicable only if the trigger is set to 'Daily'. .PARAMETER RandomiseTaskUpTo Specifies the random delay duration for the task. Valid options are '15m', '30m', and '1h'. Applicable only if the trigger is set to 'Daily'. .PARAMETER AllowedUser Specifies the user account under which the task should run. Valid options are 'BUILTIN\Users' and 'NT AUTHORITY\SYSTEM'. .PARAMETER ScriptPath Specifies the path to the PowerShell script to be executed by the task. .PARAMETER Program Specifies the path to the program to be executed by the task. .PARAMETER Arguments Specifies the arguments to be passed to the program. Applicable only if the 'Program' parameter is used. .PARAMETER StartIn Specifies the working directory for the program. Applicable only if the 'Program' parameter is used. .PARAMETER DelayTask Specifies the delay before the task starts. Valid options are '30s', '1m', '30m', and '1h'. .PARAMETER TaskFolder Specifies the folder path where the task should be created. Default is 'Specsavers'. .PARAMETER RunWithHighestPrivilege Indicates whether the task should run with the highest privilege. .PARAMETER StartTaskImmediately Indicates whether the task should be started immediately after registration. .PARAMETER IgnoreTestPath Indicates whether the task should be created even if the script path, program path, or startin directory does not exist. .EXAMPLE New-SpecScheduledTask -TaskName "MyTask" -TaskDescription "Description of my task" -Trigger "AtLogon" -AllowedUser "BUILTIN\Users" -ScriptPath "C:\Scripts\MyScript.ps1" -StartTaskImmediately Creates a scheduled task that runs the specified PowerShell script at user logon, allowing only users in the 'BUILTIN\Users' group to run the task. The task will start immediately after registration. .EXAMPLE New-SpecScheduledTask -TaskName 'System Restart' -TaskDescription 'Scheduled task to restart the system' -Trigger Daily -Time '1am' -RandomiseTaskUpTo 30m -AllowedUser 'NT AUTHORITY\SYSTEM' -Program 'Shutdown.exe' -StartIn 'C:\Windows\System32' -Arguments '-r -f -t 10' -RunWithHighestPrivilege In this example, the function creates a scheduled task named "System Restart" with the description "Scheduled task to restart the system". The trigger is set to 'Daily', indicating that the task should run daily. The Time parameter is set to "01:00", specifying that the task should run at 1:00 AM each day. Additionally, the RandomiseTaskUpTo parameter is set to "30m", indicating that the task will be randomized for up to 30 minutes from 1:00 AM. This means that the task will have a random delay of up to 30 minutes before it executes, helping to distribute the task execution across systems and avoid simultaneous execution. .NOTES Author: owen.heaume Date: 26-May-2023 Version: 1.0 - Initial Script 1.1 - Add -NoLogo -NonInteractive switches to $taskAction for security of PS Scripts 1.2 - Add ability to set daily schedule at a predefined time with new 'Daily' trigger 1.3 - Add ability to randomise task start when using 'Daily' trigger 1.4 - Split out 'Daily' to it's own parameter so parameter sets work more effectively 1.5 - Parameter sets not working effectively so coded logic instead and moved 'Daily' back to $Trigger parameter - Rewrite Comment-based help 1.6 - Change task action to: $taskAction = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument "-NoLogo -NonInteractive -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$ScriptPath`"" (includes -executionPolicy Bypass switches otherwise doesn't work on devices) 1.7 - Assign $taskSettings var to Register-Scheduled task as this had been missed. - Added compatibility = win8 to settings - Fixed -ExecutionTimeLimit as a setting as for some reason it only worked if time was converted 1.8 - Added to NewTaskScheduleSettingsSet: -RunOnlyIfNetworkAvailable Cleaned up new-taskscheduledsettingsset so it is easier to see what settings have are being applied. (All settings now applied under the cmdlet definition) 1.9 - Add Switch 'IgnoreTestPath' and refactored code to use it. If used and the script path, program path or startin dir does not exist then it will continue to create the task anyway. #> [CmdletBinding(SupportsShouldProcess = $true, DefaultParameterSetName = "Script")] param( [Parameter()] [string]$TaskName, [Parameter(Mandatory = $true)] [string]$TaskDescription, [Parameter(Mandatory = $true)] [ValidateSet('AtLogon', 'AtStartup', 'Daily')] [string]$Trigger = "AtStartup", [Parameter()] [string]$Time, [Parameter()] [ValidateSet('15m', '30m', '1h')] [string]$RandomiseTaskUpTo, [Parameter()] [ValidateSet('BUILTIN\Users', 'NT AUTHORITY\SYSTEM')] [string]$AllowedUser, [Parameter()] [ValidateScript({ $_ -match '\.ps1$' })] [string]$ScriptPath, [Parameter()] [ValidateScript({ $_ -match '\.(exe|bat|cmd)$' })] [string]$Program, [Parameter()] [string]$Arguments, [Parameter()] [string]$StartIn, [Parameter()] [ValidateSet('30s', '1m', '30m', '1h')] [string]$DelayTask, [Parameter()] [string]$TaskFolder = "Specsavers", [Parameter()] [switch]$RunWithHighestPrivilege, [Parameter()] [switch]$StartTaskImmediately, [parameter()] [switch]$IgnoreTestPath ) #region Parameter logic #Parameter sets were not flexible enough to meet the goal so code logic introduced instead to ensure correct params used together if ($Program -and $ScriptPath) { throw "Invalid combination of parameters. 'ScriptPath' and 'Program' cannot be selected together." } if ($ScriptPath -and ($Arguments -or $StartIn)) { throw "Invalid combination of parameters. When using 'ScriptPath', 'Arguments', and 'StartIn' should not be selected." } if ($trigger -eq 'AtLogon' -or $trigger -eq 'AtStartup' -and ($Time -or $RandomiseTaskUpTo)) { throw "Invalid combination of parameters. When using 'AtLogon' or 'AtStartup' as a trigger, 'Time' or 'RandomiseTaskUpTo' should not be selected." } #endregion parameter logic if ($ScriptPath) { try { $result = Test-Path $ScriptPath -ea Stop if ($result -eq $false -and $IgnoreTestPath) { write-verbose "Ignoring test-path checks..." } else { Write-Warning "Script path not found: $ScriptPath" return } } catch { if ($IgnoreTestPath) { write-verbose "Ignoring test-path checks..." } else { Write-Warning "Script path not found: $ScriptPath" return } } # If a -TaskName was not used, then get the leaf name of the script path to use instead if ($TaskName -eq "") { $TaskName = $(Split-Path $ScriptPath -Leaf -Resolve).Replace('.ps1', "") } } elseif ($program) { try { Test-Path (Join-Path $StartIn $Program -ea stop) -ea stop } catch { if ($IgnoreTestPath) { write-verbose "Ignoring test-path checks..." } else { Write-Warning "Program path not found: $Program" return } } # If a -TaskName was not used, then get the leaf name of the program path to use instead if ($TaskName -eq "") { $TaskName = $(Split-Path $Program -Leaf -Resolve) } } if (!(Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue)) { # Task action if ($ScriptPath) { $taskAction = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument "-NoLogo -NonInteractive -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$ScriptPath`"" } elseif ($Program) { $taskAction = New-ScheduledTaskAction -Execute $Program if ($Arguments -ne $null -and $Arguments -ne '') { #$taskAction.Argument = $Arguments <<<--- For some reason, this doesn't work $taskAction = New-ScheduledTaskAction -Execute $Program -Argument $Arguments } if ($StartIn -ne $null -and $StartIn -ne '') { $taskAction.WorkingDirectory = $StartIn #<<<--- Yet this does! } } # Task Trigger switch ($trigger) { 'AtStartup' { $taskTrigger = New-ScheduledTaskTrigger -AtStartup } 'AtLogon' { $taskTrigger = New-ScheduledTaskTrigger -AtLogOn } 'Daily' { $taskTrigger = New-ScheduledTaskTrigger -Daily -At $time if ($RandomiseTaskUpTo) { $randomDelay = New-TimeSpan -Minutes 0 switch ($RandomiseTaskUpTo) { '15m' { $randomDelay = New-TimeSpan -Minutes 15 } '30m' { $randomDelay = New-TimeSpan -Minutes 30 } '1h' { $randomDelay = New-TimeSpan -Hours 1 } } $randomDelayString = 'PT' + $randomDelay.Hours.ToString('00') + 'H' + $randomDelay.Minutes.ToString('00') + 'M' + $randomDelay.Seconds.ToString('00') + 'S' $taskTrigger.RandomDelay = $randomDelayString } } } # Task principal $taskPrincipal = New-ScheduledTaskPrincipal -GroupId $AllowedUser # task settings # $taskSettings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -WakeToRun -Compatibility Win8 $taskSettings = New-ScheduledTaskSettingsSet $taskSettings.Compatibility = 'Win8' $taskSettings.WakeToRun = $true $taskSettings.DisallowStartIfOnBatteries = $false $taskSettings.StopIfGoingOnBatteries = $false $taskSettings.ExecutionTimeLimit = 'PT0S' $taskSettings.RunOnlyIfNetworkAvailable = $true $taskSettings.Hidden = $false $taskSettings.Priority = 7 # For some reason, adding -ExecutionTimeLimit (New-TimeSpan - Minutes 30) to the New-ScheduledTaskSettingsSet doesn't work # and I have to convert it first. $eTime = New-TimeSpan -Minutes 30 $MaxTime = "PT" + $eTime.ToString('hh') + "H" + $eTime.ToString('mm') + "M" + $eTime.ToString('ss') + "S" $taskSettings.executiontimelimit = $MaxTime # Add a task delay if selected if ($DelayTask) { $taskDelay = New-TimeSpan -Seconds 1 switch ($DelayTask) { '30s' { $taskDelay = New-TimeSpan -Seconds 30 } '1m' { $taskDelay = New-TimeSpan -Minutes 1 } '30m' { $taskDelay = New-TimeSpan -Minutes 30 } '1h' { $taskDelay = New-TimeSpan -Hours 1 } } $delayTime = "PT" + $taskDelay.ToString('hh') + "H" + $taskDelay.ToString('mm') + "M" + $taskDelay.ToString('ss') + "S" $taskTrigger.Delay = $delayTime } # Run with highest privileges if selected if ($RunWithHighestPrivilege) { $taskPrincipal.RunLevel = "Highest" } else { $taskPrincipal.RunLevel = "Limited" } # Register the new PowerShell scheduled task Register-ScheduledTask ` -TaskName $TaskName ` -Action $taskAction ` -Trigger $taskTrigger ` -Principal $taskPrincipal ` -Description $TaskDescription ` -TaskPath "\$taskFolder" ` -Settings $taskSettings # Verify the task has been registered and if so start immediately if the switch was try { if ($(Get-ScheduledTask -TaskName $TaskName -ErrorAction stop -ev x).TaskName -eq $TaskName) { write-verbose "Task registered successfully!" # If the switch has been used, then start the task straight away if ($StartTaskImmediately) { Write-Verbose "Starting the task immediately." Start-ScheduledTask -TaskName $TaskName -TaskPath "\$taskFolder" } } } catch { Write-Warning "The task was not registered" $x #exit } } else { write-verbose "Scheduled Task $taskname already exists so taking no action" } } |