Task Scheduler
Task Scheduler runs a program on a schedule or in response to an event instead of someone doing it by hand, and nearly every failure it produces — a task that silently never fires, one that dies with a batch-logon error — traces back to the security context it was told to run under.
The Windows service (service name Schedule) that evaluates triggers and launches actions.
The MMC console for creating, inspecting, and running tasks by hand.
The PowerShell cmdlets for defining and deploying tasks at scale instead of clicking through the console.
What Is Task Scheduler?
Task Scheduler is Windows’s built-in facility for running a program, script, or command automatically when a schedule or event says to, instead of someone remembering to run it at the right moment. Think of it as an alarm clock wired directly to a to-do list — rather than ringing to wake someone up to do the task, it just does the task itself.
The current engine, Task Scheduler 2.0, has shipped since Windows Vista and Windows Server 2008 and is what every supported version of Windows still uses; the older Task Scheduler 1.0 API belongs to Windows XP and Server 2003 and is effectively legacy. Task definitions live as XML files under %SystemDrive%\Windows\System32\Tasks, with the console-facing catalogue and security descriptors cached in the registry under HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache. The console you configure it from is taskschd.msc.
Triggers and Actions
Every task is built from one or more triggers, which decide when it fires, and one or more actions, which decide what it does. A single task can hold up to 48 triggers and 32 actions, so “one task, one job” is a convention, not a limit.
| Trigger | Fires When |
|---|---|
| Time (one-time) | A single specific date and time is reached. |
| Daily / Weekly / Monthly | A recurring calendar schedule, including specific days of the week or month. |
| Monthly DOW | A relative date such as “the third Wednesday of every other month.” |
| At log on | Any user, or a specific user, logs on interactively. |
| At startup (boot trigger) | The system boots. |
| On an event | A matching entry is written to a specified event log. |
| On idle | The computer enters an idle state. |
| At task creation or modification (registration trigger) | The task itself is registered or updated — useful for run-once-on-deploy logic. |
| Session state change | A Terminal Services session connects, disconnects, or the workstation locks or unlocks. |
Action types are narrower: Start a program (the workhorse — runs any executable or script interpreter), Send an e-mail, and Display a message. The last two have been marked deprecated in the console since Windows 8 and Windows Server 2012 — Microsoft still ships the underlying interfaces, but the guidance is to not build new automation on them.
powershell.exe with a script, not the deprecated e-mail or message actions. One caveat worth knowing before you reach for it: Send-MailMessage itself is now flagged obsolete in current PowerShell because it can’t negotiate TLS securely; it still works in Windows PowerShell 5.1, but Microsoft’s own guidance for new scripts is a library like MailKit or the Microsoft Graph mail cmdlets instead.
Security Context — Run As Options
Every task runs as some account, and how Task Scheduler is allowed to authenticate that account at run time is controlled by the task’s logon type. This single setting explains most of the “it works when I test it, but not on schedule” reports.
| Logon Type | Meaning |
|---|---|
| Password | A password is supplied at registration and stored; the task can access the network and encrypted files. |
| S4U | “Do not store password” — no password is stored, task runs via Service-for-User Kerberos extensions, no network or EFS access. |
| Interactive token | “Run only when user is logged on” — requires an existing interactive session; fails otherwise. |
| Interactive token or password | Tries an interactive token first, falls back to stored password. Documented by Microsoft as not recommended for new tasks. |
| Group | Activation tied to a group rather than a single account. |
| Service account | For the well-known accounts only — Local System, Local Service, Network Service. |
A related but separate setting, Run with highest privileges, decides whether an administrator-group account runs with its full elevated token or a filtered UAC token. If the run-as account is an administrator and this box isn’t checked, operations that need elevation fail silently rather than prompting — there’s no interactive session to prompt in.
Running Under a Service Account
Scheduling a task to run as a dedicated service account — rather than a real person’s account or a well-known system identity — is the normal pattern for anything unattended and production-facing. It’s also where most of the permission failures in this feature live.
The specific permission is the Log on as a batch job user right (SeBatchLogonRight). When you schedule a task through the console or schtasks and supply a password for a non-system account, Windows automatically grants that account this right — you don’t normally have to assign it yourself. The well-known accounts (NT AUTHORITY\SYSTEM, NT AUTHORITY\LOCAL SERVICE, NT AUTHORITY\NETWORK SERVICE) don’t need it at all; they authenticate through the service-account logon type instead of a batch logon.
A task registered without this right returns SCHED_S_BATCH_LOGON_PROBLEM at registration — Task Scheduler is explicitly telling you it saved the task but expects it to fail to start. Missing the right at run time, or an active Deny log on as a batch job assignment (which always wins over an allow), surfaces as a generic logon failure in the task’s Last Run Result rather than anything Task Scheduler-specific.
Group Managed Service Accounts (gMSA) are also a supported run-as identity for scheduled tasks, but only on Windows Server 2012 and later. Because a gMSA’s password is managed automatically by the domain rather than set by an administrator, you leave the password field blank in the console or omit it from schtasks entirely — there’s nothing to type.
Creating Tasks: schtasks and PowerShell
Anything you can do in taskschd.msc can be scripted with schtasks.exe or the ScheduledTasks PowerShell module, which is the only sane approach once you’re deploying the same task to more than a handful of machines.
# Create a daily task running as a service account, password supplied
# and stored (LogonType Password)
schtasks /Create /TN "Nightly\ReportExport" /TR "C:\Scripts\Export-Report.ps1" ^
/SC DAILY /ST 02:00 /RU "CORP\svc-reportexport" /RP "P@ssw0rd" /RL LIMITED
# Same idea for the well-known System account - never pass /RP with /RU System,
# schtasks will not prompt for a password it doesn't need
schtasks /Create /TN "Nightly\Cleanup" /TR "C:\Scripts\Cleanup.ps1" ^
/SC DAILY /ST 03:00 /RU System
# Query a task's last run result and next run time
schtasks /Query /TN "Nightly\ReportExport" /V /FO LIST
# Run a task immediately, outside its schedule, to test it
schtasks /Run /TN "Nightly\ReportExport"
# Remove a task
schtasks /Delete /TN "Nightly\ReportExport" /F
# PowerShell equivalent, built from separate trigger/action/principal objects
$trigger = New-ScheduledTaskTrigger -Daily -At 2:00AM
$action = New-ScheduledTaskAction -Execute "PowerShell.exe" `
-Argument "-NoProfile -File C:\Scripts\Export-Report.ps1"
# LogonType Password stores credentials; ServiceAccount is only valid
# for the well-known LocalService/NetworkService/System identities
$principal = New-ScheduledTaskPrincipal -UserId "CORP\svc-reportexport" `
-LogonType Password -RunLevel Limited
Register-ScheduledTask -TaskName "Nightly\ReportExport" -Trigger $trigger `
-Action $action -Principal $principal
# Well-known account example straight from Microsoft's own reference
$sysPrincipal = New-ScheduledTaskPrincipal -UserId "LOCALSERVICE" -LogonType ServiceAccount
Gotchas and Best Practices
Task History is disabled by default. Open Task Scheduler, select the root Task Scheduler Library node, and click Enable All Tasks History in the Actions pane before you need it — trying to diagnose a silent failure with history off leaves you with just the Last Run Result code and nothing else.
“Run only when user is logged on” is not the same as “the task ran.” A task with this logon type registered against a service account that never has an interactive session will never fire, and it fails quietly — the Last Run Result becomes the generic “user not logged on” error rather than anything that jumps out in a status column glance.
Troubleshooting Cheat Sheet
| Symptom | Likely Cause | Fix |
|---|---|---|
Last Run Result 0x0004131C |
SCHED_S_BATCH_LOGON_PROBLEM — the run-as account lacks “Log on as a batch job.” |
Grant the right explicitly if a GPO enumerates it; otherwise re-save the task’s credentials so Windows re-grants it automatically. |
Last Run Result 0x8004131F |
SCHED_E_ALREADY_RUNNING — a previous instance is still running and the Multiple Instances policy is set to “Do not start a new instance” (the default). |
Check whether the previous run hung; change the Multiple Instances setting only if concurrent runs are actually safe. |
| Task never fires on a server, works fine when tested manually | Logon type is “Run only when user is logged on” but the account never has an interactive session. | Switch to “Run whether user is logged on or not.” |
| Task suddenly fails after a security policy update | A GPO began explicitly enumerating “Log on as a batch job” and dropped the service account that relied on the automatic grant. | Add the account to the GPO’s explicit list rather than reverting the policy. |
Last Run Result 0x80070002 |
Standard Windows ERROR_FILE_NOT_FOUND surfacing through Task Scheduler — not a Task Scheduler-specific code, just the launched program returning a generic Win32 error. |
Check the action’s program path and working directory; this is the target program failing, not a scheduling problem. |
| No useful detail in the Status column | Task History is disabled, which is the default state. | Enable it from the Task Scheduler Library root before you need to diagnose anything. |
| gMSA-based task fails intermittently | Uncertain which logon right the gMSA actually needs — Microsoft doesn’t document this definitively. | Grant both “Log on as a batch job” and “Log on as a service” to the gMSA and re-test. |
Final Thoughts
Task Scheduler’s mechanics — triggers, actions, a handful of logon types — are small enough to hold in your head completely, which is exactly why failures in it are so often misdiagnosed as something more exotic. The schedule almost always fired correctly; the account it ran as almost always didn’t have what it needed at that moment.
Treat the run-as account with the same care you’d give a service account anywhere else in the environment: know which logon right it depends on, know whether a GPO is managing that right explicitly, and enable task history before you need it rather than after.
0x0004131C means the account is missing “Log on as a batch job,” 0x8004131F means a previous run is still active. Confirm whether a GPO explicitly manages that logon right before assuming the account’s permissions are simply broken.
The batch-logon and gMSA questions this post raises tie directly into how Windows evaluates logon rights more broadly — a natural next stop is a closer look at Group Managed Service Accounts themselves and where their automatic password management actually gets enforced.