Archive for May, 2022

PowerShell ScheduledTasks

ScheduledTasks allows you to automate repetitive and predictable tasks. One of the more useful use case is to allow tasks to be achieved during less hectic times or allow simple repetitive tasks to be executed without much user involvement.

I have recently found myself in need of scheduling routine shutdown of a computer. In itself, it is not a difficult task but the fact that it needs to be done repeatedly, makes it tedious and easily forgotten (only to be remembered in an inopportune time).

The Scenario

The shutdown should occur under the following conditions:

  • at a specific time, daily
  • apply any pending windows updates
  • provide a chance to cancel the shutdown

As a beginner in scheduling tasks, a mental model is required to appreciate what needs to be done. At this point in my learning curve, it occurred to me that there is a distinction between the scheduling itself as well as the task that is being carried out.

Not all tasks need to be scheduled. A task that is not self contained would make a poor candidate for automation. Similarly, the conditions that trigger the need for the task to be executed should be predictable and fairly consistent.

When executing tasks from a GUI, it is fairly hard to think of it being automated. This is a result of trying to breakdown the process into simple steps. Consider shutting down your typical Windows machine; you have to click on the Windows Icon (Start button … notice that you want to shutdown a computer but why go to “start”), find the power button then choose whether to shutdown, sleep or restart the computer.

The simple truth is that the GUI requires a lot of ceremony in order to achieve something. The aforementioned shutdown process, is the long way of going about finding the appropriate program/command to initiate the shutdown process. From the look of it, a user is intimately involved in the process.

Achieving shutdown from the CLI is straight forward as  it involves invoking a program and/or running a command that will initiate the process. Once the command or program is invoked, there is minimal user input required thereafter.

A common aspect of shutting down a Windows machine is that there may instances when opened programs may prevent the shut down from proceeding. In the GUI approach, this invites user input and it is not possible to decide before hand which programs may trigger this behavior. With the CLI, it is possible to anticipate this and just force the shutdown to proceed.

The Task

Windows contains the program called shutdown.exe. As the name suggests, it shuts down a computer. However, it is the same program that also restarts a computer. Yes the quirks of Windows are sometimes interesting to contemplate. It is such idiosyncrasies that makes a Windows CLI experience seems bolted on.

To shutdown type the command below in a command prompt or in the running box.

shutdown /s

My preference is shutdown /s /f. The /f switch forces the shutdown, regardless of any potential lost of data. In most cases, some programs running are quite capable of storing any unsaved data; if the programs in question are browsers, then browsing history to the rescue. Any other interfering program has probably crashed and is hanging, thus forcing the shutdown is the correct option.


$pendingRebootKey = "HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired"
$seconds = 120
# Check if there is a restarted pending because of Windows Update
# TODO take into account if Windows Update is in progress
if ((Test-Path $pendingRebootKey -ErrorAction SilentlyContinue)) {
Write-Host "Checking Windows Update pending restart …"
$result = (Get-Item $pendingRebootKey -ErrorAction SilentlyContinue).Property
if ($null -ne $result) {
$seconds = 240
Write-Host "Windows Update pending Restart found!"
Write-Host "Restarting in $seconds seconds"
shutdown /r /f /t $seconds
}
else {
Write-Host "Daily Auto Shutdown"
Write-Host "Shutdown starting in $seconds seconds"
shutdown /s /f /t $seconds
}
}
else {
Write-Host "Daily Auto Shutdown"
Write-Host "No Pending Windows Update restart pending …"
Write-Host "Shutdown starting in $seconds seconds"
shutdown /s /f /t $seconds
}

The Schedule

With the task defined and sketched out, exploring scheduling options and approaches is next. In normal usage, a schedule implies time and possibly duration. However, within the context of a computer, other considerations may come into play such as events and the state of the computer.

For example, it would need to only initiate the shutdown, only if the computer is idle; at the scheduled time, only initiate the shutdown if the computer is idle. This way, a computer that is not idle would suggest active use.

There are two methods to schedule tasks on Windows:

Method 1: Task Scheduler

The Task Scheduler provides a GUI (Graphical User Interface) for managing tasks aspects of tasks including creating, editing, listing and deleting tasks. It is most suited to simple tasks that can be achieved by a single command and/or running a single program.

Task Scheduler

Method 2: PowerShell ScheduledTasks Cmdlet

This is the most appropriate option, since at the very least it can allow full programming flexibility. For example, the shutdown needs to take into account any pending restarts due to Windows Update. In case there are pending restarts, then the computer should restart in order to complete installing Windows updates.

ScheduledTaskAction

>$action = New-ScheduledTaskAction -Execute pwsh -Argument '-File [Path-To-PS1-File] -NoExit -NoProfile'

ScheduledTaskTrigger

>$trigger = New-ScheduledTaskTrigger -Daily -At 1am

Register ScheduledTask

>Register-ScheduledTask -TaskName "myTask" -Action $action -Trigger $trigger

Some Further Considerations

The currently configured task has the limitation of being executed only once. However, usage pattern suggests the computer may be in use at that time, though not always. Hence, the task need to attempt execution after about every hour but not during the daytime.

So infinitely executing throughout the day is out of the question. With that in mind, the task need to attempt repetition between midnight and 6am.

Update Task

Though New-ScheduledTaskTrigger allows for options to create a repetition duration and interval, these are paradoxically only available for triggers that run once.

The scheduled task we have defined above, needs its trigger to be changed.

>$interval = New-TimeSpan -Hours 1
>$duration = New-TimeSpan -Hours 6 
#Create a temporary trigger with required repetition patterns
>$tempTrigger = New-ScheduledTaskTrigger -Once -At 12am -RepetitionInterval $interval -RepetitionDuration $duration 
>$taskTrigger = New-ScheduledTaskTrigger -Daily -At 12am 
>$taskTrigger.Triggers[0].Repetition = $tempTrigger.Repetition 
#Update ScheduledTask
>Set-ScheduledTask -TaskName "myTask" -Trigger $taskTrigger

Future Improvements

The following are potential future improvements:

  • In case of a restart due to Windows Update pending restarts, find a way to ensure the explicit outcome of shutting down the computer is achieved.
  • Consider running the tasks only when the computer is idle within the specified period of time. This will ensure that the computer is not in active use.Note: this may cause trouble in a situation where an application prevents the computer from becoming idle; for example, playing YouTube videos on a browser would prevent any idle time.

References

Leave a comment