Windows Task Scheduler monitoring

Task Scheduler keeps each task's last run time and last run result but never alerts on them, so monitor it with a small PowerShell watcher that pings an external monitor every 10 seconds while the last run is recent and clean.

Task Scheduler runs the job and keeps the receipt. Last Run Time and Last Run Result sit in two columns of the console, and that is all you get. The Send an e-mail action has been deprecated since Windows 8 and Server 2012, task history is off until someone clicks Enable All Tasks History, and nobody opens the console on a server that has worked for two years. That is how a backup task returns 0x1 for a month.

Task Scheduler last run result

Get-ScheduledTaskInfo returns what the console shows: LastRunTime, LastTaskResult, NextRunTime and NumberOfMissedRuns. LastTaskResult is the exit code of the action, so 0 is success and 1 is the generic failure most scripts return. A few values are states, not errors: 267009, or 0x41301, means the task is running right now, and 267011, or 0x41303, means it has never run. A check that treats every non-zero value as failure pages you every time the backup is mid-run.

Windows Task Scheduler monitoring with a heartbeat

Logdash push monitors are on Pro, $15 a month, and Pro checks each one every 15 seconds: no ping in that window, the monitor goes down and the alert goes out. A nightly task cannot ping that often, so a second task pings for it. The watcher below starts at boot, reads the task every 10 seconds, and posts to Logdash only while the last run started within 25 hours and either ended clean or is still going.

C:\ops\watch-task.ps1
$task   = 'Nightly Backup'
$maxAge = New-TimeSpan -Hours 25
$ping   = 'https://api.logdash.io/ping/68b4c1f0e3a2d5c7b9f01234'
[Net.ServicePointManager]::SecurityProtocol = 'Tls12'

while ($true) {
  $info  = Get-ScheduledTaskInfo -TaskName $task
  $fresh = ((Get-Date) - $info.LastRunTime) -lt $maxAge
  # 0 = last run succeeded, 267009 = running right now
  if ($fresh -and $info.LastTaskResult -in 0, 267009) {
    try { Invoke-RestMethod -Method Post -Uri $ping -TimeoutSec 5 | Out-Null } catch {}
  }
  Start-Sleep -Seconds 10
}
Run once in an elevated PowerShell
$action   = New-ScheduledTaskAction -Execute 'powershell.exe' `
  -Argument '-NoProfile -ExecutionPolicy Bypass -File C:\ops\watch-task.ps1'
$trigger  = New-ScheduledTaskTrigger -AtStartup
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit ([TimeSpan]::Zero)
Register-ScheduledTask -TaskName 'Logdash watch' -Action $action `
  -Trigger $trigger -Settings $settings -User 'SYSTEM' -RunLevel Highest
Start-ScheduledTask -TaskName 'Logdash watch'
  • The zero time limit matters. The default is 3 days, so without it Task Scheduler kills the watcher on day three and you get an alert for nothing.
  • Tasks inside a folder need -TaskPath next to -TaskName, for example -TaskPath '\Backups\'.
  • The TLS line is for Windows PowerShell 5.1 on older servers, which can still default to protocols the API refuses.
  • A reboot stops the watcher, so patch night reads as a short outage with a down and an up message.
  1. Create a push monitor Add a service in Logdash on Pro, set the monitor to push and copy the monitor id from the ping URL. Name it after the task, since the alert shows the name.
  2. Install the watcher Save the script with your task name, window and monitor id, then run the registration block as administrator. If the last run was clean, the monitor goes green within 15 seconds.
  3. Break it on purpose Run Stop-ScheduledTask -TaskName 'Logdash watch'. Within 30 seconds a Telegram message lands saying the monitor is down, status code 0, did not receive call for this time range. Start it again and the up message follows.

Logdash vs Cronitor for Windows tasks

FeatureLogdashCronitor
SetupOne watcher script and monitor per taskcronitor sync scans Task Scheduler and wraps the tasks you pick
What it alerts onLast run stale or not cleanLate start, failure and long runtime, separately
Your task definitionUntouched, the watcher only reads itAction rewritten to run through cronitor.exe
Server downAlert within 30 secondsAlert at the next missed run
Alert channelsTelegram and webhookEmail, Slack and more
Free planNone for push monitors, Pro is $15 a month5 monitors free

When Cronitor is the better pick

  • You have more than a few tasks. cronitor sync finds them all and wraps the ones you choose in one pass.
  • You want did not start, failed and ran too long as three different alerts, not one freshness window.
  • Your servers reboot for patches every month and a short down and up message each time would be noise.
  • Alerts must land in email or Slack, and 5 monitors on a free plan covers you.
What is a good Windows Task Scheduler monitoring tool?
For many tasks, Cronitor, whose CLI discovers and wraps them. For a few important tasks on servers you also want uptime checks for, a PowerShell watcher pinging a Logdash push monitor. Task Scheduler itself has no alerting.
How do I get a scheduled task failed alert on Windows?
Read LastTaskResult with Get-ScheduledTaskInfo and treat anything other than 0 or 267009 as a failure. The watcher above stops pinging on a failure, and Logdash sends a Telegram alert within 30 seconds.
What does Task Scheduler last run result 0x1 mean?
The action exited with code 1. That is the script or program failing, not Task Scheduler. Common causes are a wrong working directory, a missing path or a credential the task account does not have.
Does Windows Task Scheduler monitoring need an agent?
Something on the machine has to read the task state. Here it is a PowerShell script registered as a startup task, with nothing to install.

Point it at your own URL and watch it for real.