Skip to content

Scheduled Tasks

Note

Create, inspect, and manage Windows Scheduled Tasks entirely from PowerShell using the ScheduledTasks module — no Task Scheduler GUI required.

Overview

The ScheduledTasks module (built into Windows 8/Server 2012 and later) lets you define a task's action, trigger, and settings as PowerShell objects, then register it in one call. This replaces schtasks.exe for anything beyond the simplest one-liners, and it's the natural way to have a script deploy its own recurring schedule.

Basic Syntax

Get-ScheduledTask -TaskName "MyBackupTask"
$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-File C:\Scripts\backup.ps1"
$trigger = New-ScheduledTaskTrigger -Daily -At "2:00 AM"
Register-ScheduledTask -TaskName "MyBackupTask" -Action $action -Trigger $trigger
Start-ScheduledTask -TaskName "MyBackupTask"
Unregister-ScheduledTask -TaskName "MyBackupTask"

Key Points

  • A task is built from three pieces: an Action (what to run), a Trigger (when), and optional Settings
  • Register-ScheduledTask requires an elevated session for tasks that run as SYSTEM or another user
  • -Argument for a PowerShell script needs -File (or -Command) just like running powershell.exe manually
  • Task names support folders: "MyGroup\MyBackupTask" organizes tasks in Task Scheduler's tree view

Viewing Existing Tasks

# All scheduled tasks
Get-ScheduledTask

# A specific task
Get-ScheduledTask -TaskName "MyBackupTask"

# Only your own tasks (not built-in Microsoft ones)
Get-ScheduledTask | Where-Object { $_.TaskPath -notlike "\Microsoft\*" }

# Task state and last run info
Get-ScheduledTask -TaskName "MyBackupTask" | Get-ScheduledTaskInfo

Output (Get-ScheduledTaskInfo):

LastRunTime        : 7/1/2026 2:00:03 AM
LastTaskResult      : 0
NextRunTime         : 7/2/2026 2:00:00 AM
NumberOfMissedRuns  : 0
TaskName            : MyBackupTask

LastTaskResult 0 Means Success

A LastTaskResult of 0 means the task's action exited cleanly. Any non-zero value is an error code from whatever ran — check the action's own logging (or the task's history in Task Scheduler) to see what actually happened.

Creating a Scheduled Task

Building the Pieces

# Action: what actually runs
$action = New-ScheduledTaskAction `
    -Execute "powershell.exe" `
    -Argument "-NoProfile -ExecutionPolicy Bypass -File C:\Scripts\backup.ps1"

# Trigger: when it runs
$trigger = New-ScheduledTaskTrigger -Daily -At "2:00 AM"

# Settings: how it behaves
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -DontStopOnIdleEnd

# Principal: which account it runs as
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest

Registering It

Register-ScheduledTask -TaskName "NightlyBackup" `
    -Action $action `
    -Trigger $trigger `
    -Settings $settings `
    -Principal $principal `
    -Description "Runs the nightly backup script"

Common Trigger Types

# Daily at a specific time
New-ScheduledTaskTrigger -Daily -At "2:00 AM"

# Weekly on specific days
New-ScheduledTaskTrigger -Weekly -DaysOfWeek Monday, Wednesday, Friday -At "6:00 AM"

# One-time, at a specific date/time
New-ScheduledTaskTrigger -Once -At (Get-Date "2026-07-15 09:00")

# At system startup
New-ScheduledTaskTrigger -AtStartup

# At user logon
New-ScheduledTaskTrigger -AtLogOn

# Repeating: run every 30 minutes, indefinitely
New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 30) -RepetitionDuration ([TimeSpan]::MaxValue)

Managing Existing Tasks

# Run a task immediately, outside its normal schedule
Start-ScheduledTask -TaskName "NightlyBackup"

# Stop a currently running task
Stop-ScheduledTask -TaskName "NightlyBackup"

# Disable without deleting
Disable-ScheduledTask -TaskName "NightlyBackup"

# Re-enable
Enable-ScheduledTask -TaskName "NightlyBackup"

# Remove entirely
Unregister-ScheduledTask -TaskName "NightlyBackup" -Confirm:$false

Updating a Task's Trigger

# Change when an existing task runs
$task = Get-ScheduledTask -TaskName "NightlyBackup"
$task.Triggers[0].StartBoundary = (Get-Date "2026-07-01 03:00").ToString("s")
Set-ScheduledTask -InputObject $task

Important Parameters

Parameter Type Description Example
-TaskName String Task identifier Get-ScheduledTask -TaskName "X"
-TaskPath String Task Scheduler folder path -TaskPath "\MyGroup\"
-Execute String New-ScheduledTaskAction: program to run -Execute "powershell.exe"
-Argument String New-ScheduledTaskAction: arguments passed -Argument "-File script.ps1"
-Daily / -Weekly / -Once Switch New-ScheduledTaskTrigger: recurrence type -Daily -At "2:00 AM"
-UserId String New-ScheduledTaskPrincipal: run-as account -UserId "SYSTEM"
-RunLevel String Highest for elevated, Limited otherwise -RunLevel Highest

Common Patterns

# Pattern 1: Create-or-update — safe to re-run during deployment
$taskName = "NightlyBackup"
if (Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue) {
    Unregister-ScheduledTask -TaskName $taskName -Confirm:$false
}
Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger -Principal $principal

# Pattern 2: Report on task health across the system
Get-ScheduledTask | Where-Object { $_.State -eq "Ready" } | ForEach-Object {
    $info = $_ | Get-ScheduledTaskInfo
    [PSCustomObject]@{
        Task        = $_.TaskName
        LastResult  = $info.LastTaskResult
        LastRun     = $info.LastRunTime
        NextRun     = $info.NextRunTime
    }
} | Where-Object { $_.LastResult -ne 0 }

# Pattern 3: Run a task and wait for it to finish
Start-ScheduledTask -TaskName "NightlyBackup"
do {
    Start-Sleep -Seconds 5
    $state = (Get-ScheduledTask -TaskName "NightlyBackup").State
} while ($state -eq "Running")

Real-World Examples

Example: Self-Deploying Scheduled Script

Scenario: A deployment script that installs itself as a scheduled task, replacing any prior version of the task.

$taskName = "DailyHealthCheck"
$scriptPath = "C:\Scripts\health-check.ps1"

# Remove old registration if it exists
if (Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue) {
    Unregister-ScheduledTask -TaskName $taskName -Confirm:$false
}

$action = New-ScheduledTaskAction -Execute "powershell.exe" `
    -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`""
$trigger = New-ScheduledTaskTrigger -Daily -At "6:00 AM"
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable

Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger `
    -Principal $principal -Settings $settings -Description "Runs daily health check"

Write-Output "Registered '$taskName' to run daily at 6:00 AM"

Explanation: Unregistering before re-registering makes the script idempotent — running it twice doesn't create duplicate tasks or leave a stale trigger behind. Running as SYSTEM avoids the task failing later because a user account got locked or its password expired.

Tips & Tricks

Quote Paths With Spaces in -Argument

# BAD - breaks if the path has spaces
-Argument "-File C:\Program Files\Scripts\backup.ps1"

# GOOD - escaped quotes around the path
-Argument "-File `"C:\Program Files\Scripts\backup.ps1`""
The -Argument string is passed to powershell.exe as a single command line — spaces in paths need their own quoting, separate from the outer PowerShell string quoting.

SYSTEM Tasks Can't Access Mapped Network Drives

A task running as SYSTEM has no access to the interactively-logged-in user's mapped drives or credentials. Use full UNC paths (\\server\share\...) instead of drive letters, and store any needed credentials with Register-ScheduledTask -User -Password or a credential vault rather than relying on the user's session.

  • Services Management - An alternative for tasks that need to run continuously rather than on a schedule
  • Event Logs - Task Scheduler logs execution history to Microsoft-Windows-TaskScheduler/Operational
  • Script Configuration Files - Externalizing settings for a scheduled script

Additional Resources