# Windows Task Scheduler Monitoring: Result Codes, History, Alerts

By Dan Ochoa · Published 2026-10-25 · Updated 2026-10-25
Canonical: https://cronradar.com/blog/windows-task-scheduler-monitoring

> Monitor Windows Task Scheduler: the settings that skip runs, what Last Run Result codes mean, how to enable task history, PowerShell checks, and alerts.

**To monitor Windows Task Scheduler, read each task's Last Run Result and event history for failures, and have the task check in with an external monitor so that a run that never starts also raises an alert.** Task Scheduler records results but notifies no one: its email action was removed in Windows 8 and Windows Server 2012 ([IEmailAction](https://learn.microsoft.com/en-us/windows/win32/api/taskschd/nn-taskschd-iemailaction)). Several defaults skip or stop runs without an error, and a PowerShell script that fails can still report success. This guide covers Windows Server 2016 through 2025 and Windows 10 and 11, with Windows PowerShell 5.1 and PowerShell 7; it is the Windows counterpart of [how to know if a cron job ran](https://cronradar.com/blog/how-to-know-if-cron-job-ran).

## Task Settings That Skip or Stop Runs Without an Error

These settings skip or end runs by design. The history event in the last column is written only if task history is enabled, which a later section covers.

| Setting (console wording)                                                     | Default                                                              | Effect on a run                                                                                               | History event |
| ----------------------------------------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | ------------- |
| **Run only when user is logged on** (General)                                 | Selected: the task runs as its creator, only while they're signed in | Nothing runs on a server where nobody is signed in                                                            | 332           |
| **Start the task only if the computer is on AC power** (Conditions)           | On                                                                   | No start on battery; **Stop if the computer switches to battery power** ends a run in progress                | 326, 327      |
| **Run task as soon as possible after a scheduled start is missed** (Settings) | Off                                                                  | A start missed while the machine was off or the service was busy is never made up                             | 114 if on     |
| **Stop the task if it runs longer than** (Settings)                           | 72 hours                                                             | The run is terminated. Runs started on demand are exempt, so clicking **Run** to test never reaches the limit | 111, 329      |
| **If the task is already running** (Settings)                                 | Do not start a new instance                                          | A slow or hung run silently blocks the next starts                                                            | 322           |
| **Expire** (trigger, Advanced settings)                                       | Not set                                                              | After that date the trigger never fires again                                                                 | 128           |

Defaults: [Task Security Context](<https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/cc722152(v=ws.10)>), [DisallowStartIfOnBatteries](https://learn.microsoft.com/en-us/windows/win32/taskschd/taskschedulerschema-disallowstartifonbatteries-settingstype-element), [StartWhenAvailable](https://learn.microsoft.com/en-us/windows/win32/taskschd/taskschedulerschema-startwhenavailable-settingstype-element), [ExecutionTimeLimit](https://learn.microsoft.com/en-us/windows/win32/api/taskschd/nf-taskschd-itasksettings-get_executiontimelimit), [MultipleInstancesPolicy](https://learn.microsoft.com/en-us/windows/win32/taskschd/taskschedulerschema-multipleinstancespolicy-settingstype-element). Events: Microsoft's [Task Properties](<https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-r2-and-2008/dd363625(v=ws.10)>) and [Task Monitoring and Control](<https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-r2-and-2008/dd315533(v=ws.10)>) lists.

**Run whether user is logged on or not** runs the task non-interactively with a stored password. If you also check **Do not store password**, Task Scheduler uses S4U logon, which can reach only local resources ([Task Security Context](<https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/cc722152(v=ws.10)>)). A stored password doesn't follow a password change; Microsoft says the credential store "requires that passwords be updated once per computer" ([Help! My Scheduled Task does not run](https://learn.microsoft.com/en-us/archive/blogs/supportingwindows/help-my-scheduled-task-does-not-run)). Until someone re-enters it on each server, the task fails to log on (event 104). A group managed service account removes the stored password; Microsoft's overview names scheduled tasks among the workloads that can run under one ([gMSA overview](https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/group-managed-service-accounts/group-managed-service-accounts/group-managed-service-accounts-overview)).

One more trap: when **Start in** is blank, a script's relative paths resolve against `%SystemRoot%\System32` (the same support article).

## What Last Run Result Actually Holds

Last Run Result, `LastTaskResult` in PowerShell, is the result returned by the task's last run ([IRegisteredTask](https://learn.microsoft.com/en-us/windows/win32/api/taskschd/nf-taskschd-iregisteredtask-get_lasttaskresult)). For a **Start a program** action that is the program's exit code in hex, so a script that exits with 1 shows `0x1`. When Task Scheduler itself couldn't run the task, the field holds one of its own [status constants](https://learn.microsoft.com/en-us/windows/win32/taskschd/task-scheduler-error-and-success-constants) (`0x0004xxxx` for success, `0x8004xxxx` for errors) or a Windows error wrapped as an HRESULT: `0x8007` followed by the Win32 error number ([HRESULT structure](https://learn.microsoft.com/en-us/windows/win32/com/structure-of-com-error-codes)).

So `0x2` is the Win32 code for "file not found", but as a Last Run Result it only says the program returned 2. What 2 means is up to that program.

| Code         | Meaning                                                                                  | What to check                                                     |
| ------------ | ---------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
| `0x0`        | The action exited with 0                                                                 | Whether the job did its work (next section)                       |
| `0x1`        | The action exited with 1                                                                 | Run the exact command line as the task's account                  |
| `0x2`        | The action exited with 2                                                                 | The program's own exit codes                                      |
| `0x41301`    | `SCHED_S_TASK_RUNNING`: the task is currently running                                    | A run still going long after its usual duration is hung           |
| `0x41303`    | `SCHED_S_TASK_HAS_NOT_RUN`: the task has not yet run                                     | The trigger's start date, and whether it ever fired               |
| `0x41306`    | `SCHED_S_TASK_TERMINATED`: the last run was terminated by the user                       | History: 111 or 329 (time limit), 330 (stopped by a user)         |
| `0x8004131F` | `SCHED_E_ALREADY_RUNNING`: an instance of this task is already running                   | The **If the task is already running** rule                       |
| `0x80041326` | `SCHED_E_TASK_DISABLED`: the task is disabled                                            | Who disabled it: event 142                                        |
| `0x80070002` | `ERROR_FILE_NOT_FOUND`: the system cannot find the file specified                        | The program path in the action and the **Start in** folder        |
| `0x8007052E` | `ERROR_LOGON_FAILURE`: the user name or password is incorrect                            | The stored password after a rotation                              |
| `0x800710E0` | `ERROR_REQUEST_REFUSED`: the operator or administrator has refused the request           | Conditions and the already-running rule; history around that time |
| `0xE0434352` | The exception code of a .NET (CLR) exception: the program died of an unhandled exception | Catch exceptions in `Main` and return an exit code                |

Win32 meanings: Microsoft's system error code lists ([0-499](https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-), [1300-1699](https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--1300-1699-), [4000-5999](https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--4000-5999-)). A process ended by an unhandled exception exits with the exception's code ([GetExitCodeProcess](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getexitcodeprocess)), and `0xE0434352` is the CLR's ([Microsoft Learn](https://learn.microsoft.com/en-us/shows/inside/e0434352)).

## Why a Failed Script Can Still Report 0x0

Task Scheduler only sees the exit code of the process it started. When that's PowerShell, the code follows the rules in [about_PowerShell_exe](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_powershell_exe?view=powershell-5.1) for Windows PowerShell 5.1, and the same rules in [about_Pwsh](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_pwsh?view=powershell-7.5) for `pwsh`:

| How the task starts it          | Process exit code                                                                                                                                                                       |
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `powershell.exe -File job.ps1`  | The number given to `exit`; 1 if a script-terminating error (`throw`, `-ErrorAction Stop`) ends the script; otherwise 0                                                                 |
| `powershell.exe -Command "..."` | 0 or 1, from `$?` after the last command. A called script's `exit 3` becomes 1 unless the command ends with `exit $LASTEXITCODE`                                                        |
| A `.bat` or `.cmd` file         | `exit N` sets the process exit code. `exit /b N` is documented only to set `ERRORLEVEL` ([exit](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/exit)) |

The `0x0` trap is that last case under `-File`, and [`$LASTEXITCODE` in about_Automatic_Variables](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_automatic_variables?view=powershell-5.1) lists the same three outcomes. `Write-Error` and most cmdlet failures are non-terminating, and under the default `$ErrorActionPreference` of `Continue`, PowerShell prints the error and keeps going ([about_Preference_Variables](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_preference_variables?view=powershell-7.5)). The script reaches its last line and exits 0. A native program that fails inside the script doesn't stop it either; its code waits in `$LASTEXITCODE`. Start scheduled scripts like this:

```powershell
$ErrorActionPreference = 'Stop'   # cmdlet errors now end the script with exit code 1

& 'C:\Tools\export-orders.exe' --since yesterday
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
```

In PowerShell 7.4 and later ([what's new](https://learn.microsoft.com/en-us/powershell/scripting/whats-new/what-s-new-in-powershell-74)), `$PSNativeCommandUseErrorActionPreference = $true` makes a non-zero exit from a native program raise an error too. In a batch file the task runs directly, end with `exit %RC%`, not `exit /b %RC%`.

## Turn On Task History Before You Need It

The **History** tab and the queries below read the `Microsoft-Windows-TaskScheduler/Operational` event log. History is a machine-wide switch, and when it's off the tab is empty, so turn it on before the next failure. In the console, select **Task Scheduler Library** and click **Enable All Tasks History** in the **Actions** pane ([View Task Properties and History](<https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/cc722006(v=ws.11)>)). From an elevated prompt ([wevtutil](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/wevtutil)):

```powershell
wevtutil sl Microsoft-Windows-TaskScheduler/Operational /e:true
wevtutil gl Microsoft-Windows-TaskScheduler/Operational   # check "enabled: true" and maxSize
```

`/ms:` raises the log's maximum size in bytes if you need longer history. The events that matter, from Microsoft's event lists linked above:

| Event ID | Logged when                                                         |
| -------- | ------------------------------------------------------------------- |
| 100, 102 | A task instance started; it finished                                |
| 200, 201 | An action launched; an action completed, whatever its exit code     |
| 101, 103 | Task Scheduler failed to start the task (the message has the error) |
| 104      | Logon failed for the task's account                                 |
| 203      | The action failed to launch                                         |
| 111, 329 | The run was stopped for exceeding its time limit                    |
| 114      | A missed start launched late because of the catch-up setting        |
| 322      | Not launched: an instance is already running                        |
| 326, 332 | Not launched: on batteries; the user wasn't logged on               |

Event 201 is written whether the program succeeded or not. In one Windows Server 2016 report, a PowerShell action with Last Run Result `0x1` logged "return code 2147942401", which is `0x80070001`: the same 1, wrapped as an HRESULT ([Microsoft Q&A](https://learn.microsoft.com/en-us/answers/a/1143811)). Query the problem events from the last day with [Get-WinEvent](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.diagnostics/get-winevent?view=powershell-5.1):

```powershell
$ids = 101, 103, 104, 111, 203, 322, 326, 329, 332
Get-WinEvent -ErrorAction SilentlyContinue -FilterHashtable @{
    LogName   = 'Microsoft-Windows-TaskScheduler/Operational'
    Id        = $ids
    StartTime = (Get-Date).AddDays(-1)
} | Select-Object TimeCreated, Id, Message | Format-List
```

`-ErrorAction SilentlyContinue` hides the error `Get-WinEvent` raises when nothing matches. Add `Where-Object Message -like '*\Nightly Export*'` to narrow it to one task.

## Check Every Task's Last Result With PowerShell

`Get-ScheduledTaskInfo` returns `LastRunTime`, `LastTaskResult`, `NextRunTime`, and `NumberOfMissedRuns` for each task ([Get-ScheduledTaskInfo](https://learn.microsoft.com/en-us/powershell/module/scheduledtasks/get-scheduledtaskinfo); [example output](https://devblogs.microsoft.com/scripting/powertip-find-missed-runs-on-scheduled-task-or-job/)). This lists enabled tasks in the root folder whose last result is anything but success or "running":

```powershell
$okResults = 0, 0x41301
Get-ScheduledTask -TaskPath '\' |
    Where-Object { $_.State -ne 'Disabled' } |
    Get-ScheduledTaskInfo |
    Where-Object { $okResults -notcontains $_.LastTaskResult } |
    Select-Object TaskName, LastRunTime, NextRunTime, NumberOfMissedRuns,
        @{ Name = 'LastResult'; Expression = { '0x{0:X}' -f $_.LastTaskResult } } |
    Format-Table -AutoSize
```

Point `-TaskPath` at the folder that holds your jobs, which keeps Microsoft's own tasks under `\Microsoft\` out of the report. The check has two limits: running on the same server, it dies with the server, and it can't see a run that should have happened and didn't. Both need a signal from outside the machine.

## Add an External Check-In to Each Task

An external monitor expects a check-in on the task's schedule and alerts when it doesn't arrive: the [dead man's switch](https://cronradar.com/blog/cron-dead-mans-switch) pattern. CronRadar works this way. It doesn't discover Task Scheduler tasks, so each task reports for itself, over HTTP from a wrapper or through the .NET SDK.

### A PowerShell Wrapper

This wrapper runs under Windows PowerShell 5.1 and PowerShell 7. It reports `start`, runs the real program, reports `complete` or `fail`, and exits with the program's own code, so Last Run Result keeps showing the real result:

```powershell
# C:\Jobs\nightly-export-monitored.ps1
$monitorUrl = 'https://cron.life/api/v1/ping/nightly-export'
$schedule   = '0 2 * * *'   # five-field cron, UTC
$grace      = 900           # seconds
$keyFile    = 'C:\ProgramData\CronRadar\api-key.txt'
$runId      = 'nightly-export-{0:yyyyMMddHHmmss}-{1}' -f (Get-Date).ToUniversalTime(), $PID

$apiKey = $env:CRONRADAR_API_KEY
if (-not $apiKey -and (Test-Path -LiteralPath $keyFile)) {
    $apiKey = "$(Get-Content -LiteralPath $keyFile -Raw)".Trim()
}

function Send-CheckIn([string] $Stage, [string] $Message) {
    if (-not $apiKey) { return }
    try {
        $query = 'schedule=' + [uri]::EscapeDataString($schedule) +
            "&gracePeriod=$grace&run_id=" + [uri]::EscapeDataString($runId)
        if ($Message) { $query += '&message=' + [uri]::EscapeDataString($Message) }
        $token = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("${apiKey}:"))
        Invoke-RestMethod -Method Post -Uri ('{0}/{1}?{2}' -f $monitorUrl, $Stage, $query) `
            -Headers @{ Authorization = "Basic $token" } -TimeoutSec 5 -UseBasicParsing | Out-Null
    } catch {
        # A monitoring failure must never change the job's result.
    }
}

Send-CheckIn 'start'
$exitCode = 1
try {
    & 'C:\Jobs\NightlyExport\NightlyExport.exe' --output 'D:\exports'
    if ($null -ne $LASTEXITCODE) { $exitCode = $LASTEXITCODE }
} catch {
    Write-Error $_   # for example, the program file is missing
}

if ($exitCode -eq 0) {
    Send-CheckIn 'complete'
} else {
    Send-CheckIn 'fail' ('NightlyExport.exe exited with code 0x{0:X}' -f $exitCode)
}
exit $exitCode
```

Set the task's action to program `powershell.exe`, arguments `-NoProfile -NonInteractive -File C:\Jobs\nightly-export-monitored.ps1`, and **Start in** `C:\Jobs\NightlyExport`. `-NonInteractive` turns any prompt into an error instead of a hang ([about_PowerShell_exe](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_powershell_exe?view=powershell-5.1)). Windows 10 and 11 default to the `Restricted` execution policy, which blocks scripts, and Windows Server to `RemoteSigned` ([about_Execution_Policies](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_execution_policies?view=powershell-5.1)). On a client, sign the script or add `-ExecutionPolicy RemoteSigned`. To wrap a PowerShell job, call `powershell.exe -File` on it in place of the `.exe`, so its exit code follows the rules above.

What else the wrapper does:

- **The key** goes in the Basic auth username, with an empty password. A machine-wide environment variable is visible to every process on the server, so on shared machines use the file, readable only by the task's account and Administrators.
- **Monitoring errors are ignored.** Each request has a 5-second timeout, which a slow DNS lookup can stretch to 15 seconds or more ([Invoke-RestMethod](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-restmethod?view=powershell-5.1)). A blocked proxy or TLS failure therefore shows up only as a monitor that never appears, so run the task once with **Run** and confirm it does.
- **Every check-in carries the schedule**, so the first run creates the monitor, and a deleted monitor comes back on the next run.
- **Alerts:** a `fail` alerts immediately. A check-in that never arrives turns the monitor `warning`, then `critical` once the grace period passes. A `start` with no finish is flagged as stuck when the next expected run plus grace passes with no new check-in.

That last rule interacts with Task Scheduler's defaults. A hung run blocks later starts (event 322), so the job goes silent and CronRadar flags it. But if a time limit shorter than the schedule's interval kills the wrapper, no `fail` is sent, and the next run's `start` hides the lost one. Give the job its own timeout, which turns a hang into a failure.

### curl.exe From a Batch File

Windows 10 build 17063 and later include `curl.exe` ([Microsoft](https://devblogs.microsoft.com/commandline/tar-and-curl-come-to-windows/)). That covers Windows 10 version 1803 (build 17134) and Windows Server 2019 (build 17763) onward, not Windows Server 2016 (build 14393) ([Windows 10](https://learn.microsoft.com/en-us/windows/release-health/release-information) and [Windows Server](https://learn.microsoft.com/en-us/windows/release-health/windows-server-release-info) release information). In Windows PowerShell 5.1, `curl` is an alias for `Invoke-WebRequest`, so type `curl.exe` ([curl on Windows](https://learn.microsoft.com/en-us/windows/curl/)). A batch job can send one heartbeat on success:

```text
@echo off
rem C:\Jobs\nightly-export.cmd
"C:\Jobs\NightlyExport\NightlyExport.exe" --output D:\exports
set "RC=%ERRORLEVEL%"
if "%RC%"=="0" curl.exe -fsS -m 5 -o NUL -X POST -G -u "%CRONRADAR_API_KEY%:" --data-urlencode "schedule=0 2 * * *" --data-urlencode "gracePeriod=900" https://cron.life/api/v1/ping/nightly-export
exit %RC%
```

A failed run sends nothing, so it surfaces as a missed check-in after the grace period rather than as an immediate failure. Plain `exit` also closes an interactive console, which doesn't matter for a file only the task runs.

### A .NET Console App

If the task runs your own .NET console app, the [CronRadar .NET SDK](https://docs.cronradar.com/integrations/dotnet) reports for it. NuGet installs `CronRadar` 1.0.13 (checked September 20, 2026), added with `dotnet add package CronRadar`; it reads `CRONRADAR_API_KEY`:

```csharp
// Program.cs
try
{
    // The class shares its namespace's name, so qualify it fully.
    await CronRadar.CronRadar.WrapAsync("nightly-export", async () =>
    {
        await ExportOrdersAsync();
    }, schedule: "0 2 * * *");
    return 0;
}
catch (Exception ex)
{
    Console.Error.WriteLine(ex);
    return 1;
}

static async Task ExportOrdersAsync()
{
    // The job's real work; an exception here is reported as "fail" and rethrown.
    await File.WriteAllTextAsync(@"D:\exports\orders.csv", "id,total\n");
}
```

`WrapAsync` reports `start`, then `complete` or `fail`, and rethrows the job's exception; the `catch` turns it into exit code 1 instead of a `0xE0434352` crash. The SDK's own network errors never reach your code, and each request gives up after 5 seconds. [.NET monitoring](https://cronradar.com/dotnet) covers the rest of the SDK.

## Translate the Trigger to a UTC Cron Schedule

CronRadar reads every schedule as five-field cron in UTC ([schedule syntax](https://docs.cronradar.com/schedules)). Task Scheduler triggers run in the computer's time zone unless **Synchronize across time zones** is checked; Microsoft's console documentation calls that box **Universal** and says it makes the time relative to UTC ([Triggers](<https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/cc748841(v=ws.11)>)). The trigger's start boundary shows which you have:

```powershell
(Get-ScheduledTask -TaskName 'Nightly Export').Triggers | Select-Object StartBoundary
```

A value with no offset, such as `2026-01-05T02:00:00`, follows the machine's time zone and daylight saving rules. A value with an offset or `Z`, such as `2026-01-05T02:00:00-05:00`, is used as written regardless of daylight saving ([ITrigger::StartBoundary](https://learn.microsoft.com/en-us/windows/win32/api/taskschd/nf-taskschd-itrigger-get_startboundary)), so that task runs at 07:00 UTC all year: `0 7 * * *`.

With no offset on a server that observes daylight saving, the task's UTC time moves by an hour twice a year. Run the server in UTC if you can. Otherwise use the earlier of the two UTC times and a grace period of at least an hour plus the job's usual duration: a 02:00 New York task becomes `0 6 * * *` with `gracePeriod` of 3600 or more.

**Repeat task every** runs the task, waits the interval, and runs again from the trigger's start time ([Triggers](<https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/cc748841(v=ws.11)>)). A 15-minute repetition that started at 09:07 runs at :07, :22, :37, and :52, while `*/15 * * * *` expects :00, :15, :30, and :45. Write the real minutes as `7/15 * * * *`, or move the trigger's start to a whole hour. A grace period long enough to cover the offset also works but delays every missed-run alert. A **Delay task for up to (random delay)** setting needs a grace period that covers the full delay. If the repetition has a duration, restrict the hour field to that window.

| Trigger (server on UTC)                                   | CronRadar schedule                                                                          |
| --------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| Daily at 02:00                                            | `0 2 * * *`                                                                                 |
| Weekly on Monday and Thursday at 06:30                    | `30 6 * * 1,4`                                                                              |
| Monthly on day 1 at 00:00                                 | `0 0 1 * *`                                                                                 |
| Monthly on the first Monday at 09:00                      | `0 9 1-7 * 1` (CronRadar matches both day fields)                                           |
| Daily from 00:07, repeating every 15 minutes indefinitely | `7/15 * * * *`                                                                              |
| Every 2 days, every 2 weeks, or the last day of the month | No exact equivalent; run on every candidate date, let the job skip, and check in every time |
| At startup, at log on, or on an event                     | No clock schedule to check against                                                          |

Once the task has run, compare CronRadar's next expected run with `(Get-ScheduledTaskInfo -TaskName 'Nightly Export').NextRunTime.ToUniversalTime()`, and check again after the next daylight saving change. If the job belongs to a SQL Server instance, [SQL Server Agent job monitoring](https://cronradar.com/blog/sql-server-agent-job-monitoring) covers that scheduler.

## Frequently Asked Questions

### What does Last Run Result 0x1 mean in Task Scheduler?

The task's program exited with code 1. For a PowerShell script started with `-File`, either the script called `exit 1` or a script-terminating error such as `throw` ended it; with `-Command`, the last command failed. Run the action's exact command line as the task's account, and log the script's output to a file to see the error.

### Why didn't my scheduled task run?

Check that the task is enabled and its trigger hasn't expired, then look at `NextRunTime` and the history around the expected time. Event 332 means the account wasn't logged on, 326 means the computer was on battery, 322 means an earlier run was still going, and 104 means the stored password no longer works. These events appear only if task history is enabled.

### How do I enable Task Scheduler history?

In the console, click **Enable All Tasks History** in the **Actions** pane, or run `wevtutil sl Microsoft-Windows-TaskScheduler/Operational /e:true` from an elevated prompt. It's one switch for every task on the machine, and it records only from the moment you turn it on.

### How do I get an email when a scheduled task fails?

Task Scheduler's email action was removed in Windows 8 and Windows Server 2012. Microsoft's suggested replacement, `Send-MailMessage`, is itself marked obsolete ([Send-MailMessage](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/send-mailmessage?view=powershell-7.5)). An external monitor such as CronRadar sends the alert instead: a `fail` check-in alerts immediately, and a check-in that never arrives alerts after the grace period, by email, Slack, webhook, Discord, or PagerDuty.

### Does Task Scheduler run missed tasks after the computer was off?

Only if **Run task as soon as possible after a scheduled start is missed** is checked, and it's off by default. When it's on, Task Scheduler starts the task after the machine comes back, which Microsoft's Windows Server 2008 documentation says happens after a ten-minute wait ([Task Settings](<https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/cc722178(v=ws.10)>)), and logs event 114. A monitor watching the schedule alerts on the missed time once its grace period passes, and the late run's check-in then records the recovery.
