Technology Solutions for Everyday Folks
Mechanical Mathematical Calculator

Windows Scheduled Tasks with Powershell

As I struggled to find looked for inspiration for this week's post, I wound up looking within...oddly enough as I was reconfiguring my email out of office response (the below is what I had for my time at MMS):

PS C:\> Get-Service MattZaske -RequiredServices | Stop-Service -Verbose
VERBOSE: Performing the operation "Stop-Service" on target "Email".
VERBOSE: Performing the operation "Stop-Service" on target "Phone".
VERBOSE: Performing the operation "Stop-Service" on target "In-Office".

PS C:\> Stop-Service MattZaske

PS C:\> $trigger = New-ScheduledTaskTrigger -Once -At '05/14/2019 01:00 PM'
PS C:\> $taskAction = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument 'Start-Service MattZaske'
PS C:\> Register-ScheduledTask -TaskName 'Matt Returns' -Trigger $trigger -Action $taskAction | Get-ScheduledTaskInfo | Select -Property TaskName, NextRunTime

TaskName      NextRunTime
--------      -----------
Matt Returns  5/14/2019 01:00:00 PM

PS C:\>

When I'm gone for an extended period (more than a couple days), I try to bake in a bit of defensive calendaring to help set reasonable expectations for folks who may have contacted me during my time away. This is ultimately why, though I returned from MMS on Friday, May 10, my autoresponder told folks I'd be back in on Tuesday the 14th.

The inspiration for this particular out of office autoresponder comes via Twitter by way of Ned Pyle, who wrote his in a series of commands. A few days later, I wrote my first version of the same thing in Powershell. I've used it many times since then, and while I think it's often received well over folks' heads, it also ends with the succinct details from Get-ScheduledTaskInfo, to summarize.

What I personally love about my autoresponder is that it's 100% legitimate and correct Powershell, and assuming the services in question (MattZaske, Email, Phone, In-Office) actually existed, would actually produce this exact output at the command line.

What's this to do with Windows Scheduled Tasks, again?


But in a more serious sense, my autoresponder reminded me that I've a GitHub repo to show some basics to manage a scheduled task, which you might find useful. The example provided solves for a specific use case: automatically logging off folks after a period of inactivity. Despite reiterating to folks to log off of common workstations when done (what data security/privacy, again?), many continue to simply walk away.

As oddly specific as the use case is, I like it since it uses several practical (or necessary) components of a scheduled task: action, trigger, user, and additional settings.

Registering/Updating a Task with Powershell

The actual action of creating or updating a task with Powershell is pretty straightforward. To create a new task:

Register-ScheduledTask -Action $action -Trigger $trigger -Principal $user -TaskName $taskName -Description $taskDescription -Settings $settings

Or, to modify:

Set-ScheduledTask -Action $action -Trigger $trigger -Principal $user -TaskName $taskName

Let's start with some variables!

The details are easily 'parameterized' with some variables:

$taskName = "Idle Time Log Off - 5 Minute"
$taskDescription = "Automatic Log Off of user after 5 minutes of inactivity."
$action = New-ScheduledTaskAction -Execute "C:\Path\To\idlelogoff.exe" -Argument "300 LOGOFF"
$trigger =  New-ScheduledTaskTrigger -AtLogOn
$user = New-ScheduledTaskPrincipal -GroupId "BUILTIN\Users"
$settings = New-ScheduledTaskSettingsSet -MultipleInstances IgnoreNew -Disable -Priority 7 -Compatibility Win8
$logPath = "C:\Windows\Temp\$taskName.log"

The Microsoft docs for scheduled tasks go into great detail about the nuances and requirements for all of the variables, especially those task-related items starting with the New- verb. The docs and examples will be your friend if this is your first, second, or fiftieth rodeo. I find that especially true for the $trigger and $settings options.

Stitch it together, already!

The most important thing to understand is that you'll have to edit (or remove and replace) the "old" task of the same name should it exist, so I simply rolled the detection into an if/else:

$existingTask = Get-ScheduledTask | Where-Object {$_.TaskName -eq "$taskName"}
if ($existingTask) { # UPDATE THE EXISTING TASK
    if (Set-ScheduledTask -Action $action -Trigger $trigger -Principal $user -TaskName $taskName) {
        $logText = "Successfully updated " + $taskName
        Out-File -FilePath $logPath -InputObject $logText
} else { # CREATE A NEW TASK
    if (Register-ScheduledTask -Action $action -Trigger $trigger -Principal $user -TaskName $taskName -Description $taskDescription -Settings $settings) {
        $logText = "Successfully created " + $taskName
        Out-File -FilePath $logPath -InputObject $logText

It's Really Simple

What I really like about managing scheduled tasks on client machines is that they're so simple. Fifteen lines of nicely-formatted and clean code gets you some pretty powerful stuff. I've created all sorts of useful scheduled tasks for servers, clients, and so forth.

I've discovered that, especially at first, being able to craft and test (or dev/test concept) on a workstation via the GUI is super helpful. Using the Export-ScheduledTask cmdlet on a "working" GUI-built task will produce most, if not all, of the details you'll need to bolt into the Powershell version. The Microsoft docs are also your friend in this process. You can then test the scripted version, and assuming all is well, roll to production!

Rockstar or Wizardry status among your colleagues may vary, but I think there are aspects of using scheduled tasks in the Windows ecosystem that are vastly underrated. I use cron in Linux environments all the time, so maybe I'm more accustomed to thinking in that manner, but TL;DR: scripted, scheduled tasks can save a boatload of time and effort. Use them.

Headline image (calculator) courtesy of Samuel Mann (Flickr/Arithmuseum collection)