Short answer: Task Scheduler is the built-in cron for Windows. Open it with taskschd.msc, or create a task from an elevated prompt: schtasks /create /tn "Nightly backup" /tr "C:\scripts\backup.bat" /sc daily /st 03:00 /ru SYSTEM /rl highest. That runs the script every night at 03:00, with full rights, whether anyone is logged on or not.
schtasks /create /tn "Nightly backup" /tr "C:\scripts\backup.bat" /sc daily /st 03:00 /ru SYSTEM /rl highest
schtasks /run /tn "Nightly backup"
schtasks /query /tn "Nightly backup" /v /fo listThe second line runs the task once right away, the third shows its last result. This guide is written from Microsoft Learn’s schtasks reference, the ScheduledTasks PowerShell module reference and the Task Scheduler error and success constants, as of October 2026. Everything applies to Windows Server 2016 to 2025 and to Windows 10 and 11.
The Task Scheduler window, and what each setting is called elsewhere
Press Win+R, type taskschd.msc. Use Create Task rather than Create Basic Task: the basic wizard hides the security options you need on a server. The same settings exist on the command line and in PowerShell under other names:
| GUI | schtasks /create | PowerShell |
|---|---|---|
| Name | /tn | -TaskName |
| Action: program and arguments | /tr | New-ScheduledTaskAction -Execute -Argument |
| Action: Start in | (not available) | -WorkingDirectory |
| Trigger: daily at 03:00 | /sc daily /st 03:00 | New-ScheduledTaskTrigger -Daily -At 3am |
| Trigger: at startup | /sc onstart | -AtStartup |
| Trigger: at log on | /sc onlogon | -AtLogOn |
| Run as SYSTEM | /ru SYSTEM | New-ScheduledTaskPrincipal -UserId SYSTEM -LogonType ServiceAccount |
| Run with highest privileges | /rl highest | -RunLevel Highest |
| Run only when user is logged on | /it | -LogonType Interactive |
| Stop the task if it runs longer than | (not available) | New-ScheduledTaskSettingsSet -ExecutionTimeLimit |
| Run task as soon as possible after a missed start | (not available) | -StartWhenAvailable |
| Replace an existing task | /f | Register-ScheduledTask -Force |
Two defaults surprise people: a task is stopped after it has run for three days, and if the previous run is still going, a new one is not started. Both are sensible for maintenance jobs and wrong for anything meant to run forever, such as a game server.
schtasks /create examples
rem every day at 03:00, as SYSTEM, elevated
schtasks /create /tn "Nightly backup" /tr "C:\scripts\backup.bat" /sc daily /st 03:00 /ru SYSTEM /rl highest
rem every 15 minutes
schtasks /create /tn "Health check" /tr "C:\scripts\check.bat" /sc minute /mo 15 /ru SYSTEM
rem every Sunday at 04:30
schtasks /create /tn "Weekly cleanup" /tr "C:\scripts\clean-temp.bat" /sc weekly /d SUN /st 04:30 /ru SYSTEM
rem at every boot, one minute after startup
schtasks /create /tn "Start app" /tr "C:\scripts\start-app.bat" /sc onstart /delay 0001:00 /ru SYSTEM /rl highest
rem inside a folder, replacing a task of the same name
schtasks /create /tn "\Maintenance\Nightly backup" /tr "C:\scripts\backup.bat" /sc daily /st 03:00 /ru SYSTEM /f/scis the schedule type:minute,hourly,daily,weekly,monthly,once,onstart,onlogon,onidleoronevent;/mosets the interval within it./sttakes 24-hourHH:mm./sdand/edtake a date in the server’s regional format, which differs between servers; PowerShell, below, avoids that./ru SYSTEMneeds no password. With a normal account instead, schtasks prompts for it (or takes/rp) and stores it./trmay be at most 262 characters, and a path with spaces needs escaped inner quotes:/tr "\"C:\Program Files\App\app.exe\" --nightly". A small batch file as the action avoids both problems./delayuses the format mmmm:ss and works only with onstart, onlogon and onevent.
schtasks /query, /run, /change and /delete
schtasks /query /fo table
schtasks /query /tn "Nightly backup" /v /fo list
schtasks /run /tn "Nightly backup"
schtasks /end /tn "Nightly backup"
schtasks /change /tn "Nightly backup" /st 02:30
schtasks /change /tn "Nightly backup" /disable
schtasks /change /tn "Nightly backup" /enable
schtasks /delete /tn "Nightly backup" /fThe verbose query is the quickest health check. Read Last Run Time, Last Result (explained below), Next Run Time, Run As User and Logon Mode. Per Microsoft’s reference, a SYSTEM task shows NT AUTHORITY\SYSTEM and Background only; a task created with /it shows Interactive only. /delete without /f asks for confirmation, which stops scripts.
Create a scheduled task in PowerShell
$action = New-ScheduledTaskAction -Execute 'C:\scripts\backup.bat' -WorkingDirectory 'C:\scripts'
$trigger = New-ScheduledTaskTrigger -Daily -At 3am
$principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Hours 2)
Register-ScheduledTask -TaskName 'Nightly backup' -TaskPath '\Maintenance\' -Action $action -Trigger $trigger -Principal $principal -Settings $settings
Start-ScheduledTask -TaskName 'Nightly backup' -TaskPath '\Maintenance\'
Get-ScheduledTaskInfo -TaskName 'Nightly backup' -TaskPath '\Maintenance\' |
Select-Object LastRunTime, LastTaskResult, NextRunTimeEach New-ScheduledTask* cmdlet builds one part, and Register-ScheduledTask puts them together. The SYSTEM principal with -LogonType ServiceAccount runs the task whether or not anyone is logged on. -StartWhenAvailable catches up a run that was missed because the server was off at 03:00. -ExecutionTimeLimit replaces the three-day default; ([TimeSpan]::Zero) removes the limit for a process that should run indefinitely.
Other triggers: -AtStartup, -AtLogOn, -Weekly -DaysOfWeek Sunday -At '04:30', -Once -At (Get-Date).AddMinutes(10). To manage existing tasks: Get-ScheduledTask -TaskPath '\Maintenance\', Disable-ScheduledTask, Enable-ScheduledTask and Unregister-ScheduledTask -TaskName NAME -Confirm:$false. LastTaskResult appears as a decimal number; '0x{0:X}' -f (Get-ScheduledTaskInfo -TaskName NAME).LastTaskResult turns it into the hex form used in the table below.
“Run whether user is logged on or not” and the other account options
| Option | schtasks | Password stored | Visible window | Network shares |
|---|---|---|---|---|
| Run only when user is logged on | /ru USER /it | No | Yes, in that user's session | As the user |
| Run whether user is logged on or not | /ru USER | Yes | No | As the user |
| ... with Do not store password | /ru USER /np | No | No | No: local resources only |
| SYSTEM | /ru SYSTEM | No | No | As the computer account |
On a server, SYSTEM is the usual choice for maintenance: nothing to expire, nothing to type. Microsoft’s reference is explicit that SYSTEM has no interactive logon rights, so a SYSTEM task never shows a window, even if it starts Notepad. A task with a stored password has a different weak point: schtasks does not check the password, and when the account’s password changes or expires, the task silently stops running until you enter the new one: schtasks /change /tn NAME /ru USER asks for it without showing it.
Running a .bat or .ps1 file
Batch files
Point the action at the .bat file itself. Without a Start in folder the script starts in C:\Windows\System32, so either set -WorkingDirectory or begin the script with cd /d "%~dp0". Never put quotes in the GUI’s Start in field, even for a path with spaces; that alone produces error 0x8007010B. A scheduled script has no console anyone can read, so send its output to a log:
$action = New-ScheduledTaskAction -Execute 'cmd.exe' -Argument '/c "C:\scripts\backup.bat >> C:\scripts\logs\backup.log 2>&1"'PowerShell scripts
The program is powershell.exe (or C:\Program Files\PowerShell\7\pwsh.exe for PowerShell 7), and the script goes in the arguments:
$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument '-NoProfile -NonInteractive -ExecutionPolicy Bypass -File "C:\scripts\job.ps1"'- Do not make the
.ps1itself the program. Windows opens.ps1files with Notepad by default, so the task “runs” an invisible editor and your script never does. -ExecutionPolicy Bypassapplies to this one process only and leaves the server’s policy alone.- End the script with
exit 0orexit 1: that number becomes the task’s Last Run Result.
Task history and Last Run Result codes
Task history is off by default. Turn it on with Enable All Tasks History in the Actions pane, or from the command line:
wevtutil set-log Microsoft-Windows-TaskScheduler/Operational /enabled:true
Get-WinEvent -LogName Microsoft-Windows-TaskScheduler/Operational -MaxEvents 50 |
Where-Object { $_.Message -like '*Nightly backup*' } |
Format-Table TimeCreated, Id, Message -WrapEvent 100 is a task start, 102 a completed task, 201 a completed action with the program’s return code, and 101 a task that failed to start. The Last Run Result column is mostly the exit code of your program, plus a few values of Task Scheduler’s own:
| Code | Meaning | What to check |
|---|---|---|
| 0x0 | The program exited with 0: success | Nothing |
| 0x1 | The program exited with 1 | The script’s last command failed, or it ended with exit /b 1. Robocopy returns 1 after a successful copy |
| 0x2 | Exit code 2, often a file that was not found | The paths in the action and inside the script |
| 0x41301 | The task is currently running | Normal while it runs; if it stays, the program hangs |
| 0x41303 | The task has not yet run | Normal for a new task |
| 0x41306 | The last run was terminated by the user | Ended by hand: End, schtasks /end or Stop-ScheduledTask |
| 0x8004131F | An instance of this task is already running | The previous run is still going |
| 0x800710E0 | The operator or administrator has refused the request | The run-as account, its stored password and the task's conditions |
| 0x8007010B | The directory name is invalid | The Start in folder: wrong, or in quotes |
| 0x80070005 | Access is denied | Rights of the run-as account |
Any code that starts with 0x8007 wraps a Windows error number. Convert the last four hex digits to decimal and ask Windows: 0x8007010B is 267, and net helpmsg 267 prints The directory name is invalid. For a robocopy job, let the batch file translate its codes, so that 0x1 does not look like a failure: if %ERRORLEVEL% LSS 8 exit /b 0.
cron vs Task Scheduler
| cron (Linux) | Task Scheduler (Windows) | |
|---|---|---|
| Where jobs live | One text line each in a crontab | One XML task each, managed by a service |
| Command line | crontab -e | schtasks, ScheduledTasks module |
| Schedule syntax | Five time fields | Triggers: daily, weekly, minute, at startup, on an event |
| At boot | @reboot | /sc onstart |
| On a log event | No | /sc onevent |
| Run as | The crontab's owner | Any account, or SYSTEM |
| Missed runs while off | Skipped (anacron catches up) | Skipped unless -StartWhenAvailable |
| Overlapping runs | Allowed | New run skipped by default |
| Maximum run time | None | Three days by default |
| Output and logs | Mailed or redirected; syslog or journal | Redirect yourself; history log, off by default |
| cron | Meaning | schtasks |
|---|---|---|
0 3 * * * | Daily at 03:00 | /sc daily /st 03:00 |
*/15 * * * * | Every 15 minutes | /sc minute /mo 15 |
30 4 * * 0 | Sundays at 04:30 | /sc weekly /d SUN /st 04:30 |
0 0 1 * * | First of the month, midnight | /sc monthly /d 1 /st 00:00 |
@reboot | At every boot | /sc onstart |
If you schedule jobs on Linux servers too, the crontab guide covers the other side, and the cron expression generator spells out what a cron line means before you translate it.
Why a scheduled task does not run
- It works when you start it, but not as SYSTEM. SYSTEM has no mapped drives (use
\\server\sharepaths), a different%USERPROFILE%and%TEMP%, and none of your per-user tools. winget is one of them: it is a per-user app alias that SYSTEM does not have, and the guide to winget on Windows Server shows the workarounds. - It ran at the wrong hour. Triggers follow the server’s own time zone;
tzutil /gshows it. - It stops after exactly three days. The default time limit. Long-running servers need
-ExecutionTimeLimit ([TimeSpan]::Zero), as in the Enshrouded server guide. - It never started after a reboot. Check that the trigger is
onstart, notonlogon: after updates a server usually restarts with nobody logging on.
Task Scheduler on a Windows VPS
On a server that nobody watches all day, Task Scheduler does the routine work: nightly backups, log cleanup, a weekly restart after updates, and starting game or app servers at boot without anyone logged in. On a Windows VPS from VMHeaven you are Administrator on Windows Server 2022 or 2019, so you can register tasks as SYSTEM, and if a task ever locks you out of Remote Desktop, the VNC console in the panel still reaches the desktop to disable it.
Frequently asked
What is the Windows equivalent of cron?
Task Scheduler. It runs programs on a schedule, at startup, at log on or when an event is logged. Create tasks in taskschd.msc, with schtasks /create in Command Prompt, or with Register-ScheduledTask in PowerShell.
How do I create a scheduled task from the command line?
In an elevated Command Prompt run schtasks /create /tn NAME /tr PATH-TO-SCRIPT /sc daily /st 03:00 /ru SYSTEM /rl highest. That runs the script every day at 03:00 as SYSTEM, whether or not anyone is logged on. Test it right away with schtasks /run /tn NAME.
What does Last Run Result 0x1 mean in Task Scheduler?
The program the task started exited with code 1. For a batch file, its last command failed or it ended with exit /b 1; a wrong path or Start in folder is a common cause. Robocopy returns 1 after a successful copy, so a robocopy job needs its exit codes translated.
How do I make a task run whether the user is logged on or not?
Run it as SYSTEM (/ru SYSTEM, or New-ScheduledTaskPrincipal -UserId SYSTEM -LogonType ServiceAccount), which needs no password, or as a named account with its password stored. In the GUI, select Run whether user is logged on or not on the General tab. Such tasks never show a window.