The Windows Task Scheduler can do far more than run a script once a day. In this guide you will build a task with several triggers, conditions, restart rules and a service account, chain one task to another, and confirm the result from PowerShell and the Task Scheduler event log.
Everything below works on Windows Server 2019, 2022 and 2025. Connect to your server over Remote Desktop first — on Windows VPS and Admin RDP that means 203.0.113.10:3389.
Before you start
You need:
- Administrator access to the server, either through the browser console in your VPS provider's control panel or over RDP.
- A script or command you want to schedule. This guide uses
C:\Scripts\NightlyMaintenance.ps1as the example. - A log folder, for example
C:\Logs. - PowerShell open as Administrator.
Tip: Set the server time zone before you create triggers. If the clock is wrong, a task set for 02:30 runs at the wrong local time. See Set the time zone on Windows Server.
Plan the task before you create it
Write down four things first:
- Trigger — daily at 02:30, at startup, on an event, or on a schedule you repeat every 15 minutes.
- Identity —
SYSTEM, a service account, or a normal user. This decides what the task can touch. - Action — the exact executable and arguments, including full paths.
- Failure handling — how many times to retry, how long to allow, and what happens if the machine is off at the trigger time.
Getting these right up front saves you from debugging a task that silently does nothing.
Create a task with schtasks
The command line is the fastest way to create a simple but complete task. Run this in an elevated Command Prompt:
schtasks /create /tn "Nightly Maintenance" ^
/tr "powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\Scripts\NightlyMaintenance.ps1" ^
/sc daily /st 02:30 /ru "SYSTEM" /rl HIGHEST /fWhat each part does:
/tnis the task name shown in Task Scheduler./tris the program plus arguments. Quote the whole string./sc daily /st 02:30sets a daily trigger at 02:30 local time./ru "SYSTEM"runs the task as the built-in system account, so no stored password is needed./rl HIGHESTruns with the highest available privileges./foverwrites an existing task with the same name.
To add a second trigger, for example at boot, use /sc onstart in a separate create call or switch to PowerShell, which handles multiple triggers in one object.
Build the same task in PowerShell
PowerShell gives you triggers, settings and the principal in one place. This is the version to use when the task is genuinely complex.
$action = New-ScheduledTaskAction -Execute 'powershell.exe' `
-Argument '-NoProfile -ExecutionPolicy Bypass -File C:\Scripts\NightlyMaintenance.ps1' `
-WorkingDirectory 'C:\Scripts'
$daily = New-ScheduledTaskTrigger -Daily -At 02:30
$atBoot = New-ScheduledTaskTrigger -AtStartup
$settings = New-ScheduledTaskSettingsSet `
-StartWhenAvailable `
-RestartCount 3 `
-RestartInterval (New-TimeSpan -Minutes 5) `
-ExecutionTimeLimit (New-TimeSpan -Hours 2) `
-MultipleInstances IgnoreNew `
-DontStopOnIdleEnd
$principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' `
-LogonType ServiceAccount -RunLevel Highest
Register-ScheduledTask -TaskName 'Nightly Maintenance' `
-Action $action -Trigger $daily,$atBoot `
-Settings $settings -Principal $principal `
-Description 'Runs nightly maintenance and again at boot.'-WorkingDirectory matters. Without it, a script that writes to a relative path such as .\output.txt writes into C:\Windows\System32 when it runs as SYSTEM.
Understand conditions and restart rules
The settings object above covers the common cases:
-StartWhenAvailableruns the task as soon as possible if the trigger time was missed because the server was off.-RestartCountand-RestartIntervalretry the task if it fails, up to three times, five minutes apart.-ExecutionTimeLimitkills a task that hangs, so a stuck job does not block the next run.-MultipleInstances IgnoreNewprevents a second copy starting while the first is still running. This is the usual cause of "the task is already running" errors.-DontStopOnIdleEndstops Windows from killing the task when the machine leaves an idle state.
You can also require a network connection or a specific idle state, but on a server those conditions usually cause more missed runs than they prevent. Leave them off unless you have a reason.
Run the task under the right account
Three practical choices:
- SYSTEM — no password to store, full local rights, no network access to other machines unless you configure it. Good for local maintenance, log rotation and cleanup.
- A dedicated service account — use this when the task must reach a share or a database with its own credentials. Create the account with a long password and grant only the rights the job needs.
- A normal user — only if the task genuinely needs that user's profile. Note that "Run only when user is logged on" means the task will not run after a reboot until someone signs in.
Warning: Changing a task's account or password can lock the account out if you type the password wrong. Test the credentials by signing in once before you save the task.
Chain tasks and react to events
Two patterns cover most chained work.
Chain by running the next task from the script. At the end of NightlyMaintenance.ps1, check the result and start the follow-up:
if ($LASTEXITCODE -eq 0) {
schtasks /run /tn "Upload Reports"
} else {
Write-EventLog -LogName Application -Source 'Maintenance' -EventId 9001 -EntryType Error -Message "Maintenance failed with $LASTEXITCODE"
}Trigger on an event. Create the task in Task Scheduler, open the Triggers tab, choose On an event, and point it at the log and source you care about. The underlying query looks like this:
<QueryList>
<Query Id="0" Path="System">
<Select Path="System">*[System[Provider[@Name='Service Control Manager'] and EventID=7031]]</Select>
</Query>
</QueryList>That fires when a service terminates unexpectedly. You can import a task definition with schtasks /create /tn "Service Recovery" /xml C:\Scripts\recovery.xml, which is easier than building event triggers by hand.
Log output and read the result
A scheduled task that produces no output is impossible to debug. Start every script with a transcript:
Start-Transcript -Path 'C:\Logs\NightlyMaintenance.log' -Append
# ... your work ...
Stop-TranscriptRotate that log weekly so it does not fill the disk. If you are watching disk space, see Check CPU, RAM and disk use on Windows.
Check it works
Run the task manually and inspect the result:
Start-ScheduledTask -TaskName 'Nightly Maintenance'
Get-ScheduledTask -TaskName 'Nightly Maintenance' | Get-ScheduledTaskInfoLastTaskResult of 0 means success. Anything else is an error code you can look up.
For the full picture, including triggers and the account:
schtasks /query /tn "Nightly Maintenance" /v /fo LISTThen check the scheduler's own log:
Get-WinEvent -LogName Microsoft-Windows-TaskScheduler/Operational -MaxEvents 20 |
Where-Object Id -in 100,102,103,201Event 100 is task started, 102 completed, 103 completed with a non-zero code, 201 action started. If the operational log is disabled, turn it on in Event Viewer under Applications and Services Logs > Microsoft > Windows > TaskScheduler.
Troubleshooting
- Last Run Result
0x1or0x2— the script ran but exited with an error, or the file path is wrong. Check the transcript log and confirm the full path in the action, including the.ps1extension. 0x41301(task is currently running) — a previous run is still going. Set-MultipleInstances IgnoreNewand add an execution time limit so stuck runs get killed.0x80070005access denied — the task account lacks rights. Either run asSYSTEMwith-RunLevel Highest, or grant the service account permission to the folders and registry keys it touches.- Task runs, but nothing happens — usually a working directory problem. Set
-WorkingDirectoryand use absolute paths inside the script. - Task never runs after a reboot — it is set to "Run only when user is logged on". Switch to "Run whether user is logged on or not" and supply the password, or use
SYSTEM.
Warning: Before you edit a task that deletes, moves or overwrites data, test it with a copy of the data first. A wrong path in a cleanup task can remove the wrong folder in seconds.
Next steps
- Schedule tasks with Windows Task Scheduler for the basics if you are new to the tool.
- Windows Update on a VPS: patches and maintenance windows to schedule patching around your jobs.
- Back up a Windows VPS so a failed maintenance task is recoverable.
- Securing Windows Server: hardening the registry and services to lock down the accounts your tasks use.
