Windows Scheduling
Quote
“The first rule of any technology used in a business is that automation applied to an efficient operation will magnify the efficiency. The second is that automation applied to an inefficient operation will magnify the inefficiency.”
— Bill Gates
Summary
Windows scheduling is built around Task Scheduler but exposed through several interfaces with different strengths, and this note defines how to use
schtasks.exe, the PowerShell ScheduledTasks module, and PSScheduledJob to schedule scripts, SQL maintenance, SSIS packages, and event-driven automation with repeatable trigger, action, principal, and logging patterns.Windows scheduler surfaces
- Covers Task Scheduler core concepts,
schtasks.exefor universal CLI access, the ScheduledTasks PowerShell module for modern object-based management, and PSScheduledJob for PowerShell-native jobs with output retrieval.- Positions these as overlapping tools over the same scheduling platform rather than as unrelated schedulers.
Task construction and trigger design
- Defines triggers, actions, principals, conditions, settings, XML import and export, and every major trigger family including daily, weekly, startup, logon, repeating, and event-based patterns.
- Shows how registration, inspection, modification, and deletion work through both CLI and PowerShell paths.
Operational and data-engineering patterns
- Covers complete patterns for Python pipelines,
sqlcmd, SSIS packages, file watchers, service-failure triggers, logging, monitoring, and email notification wrappers.- Maps Windows scheduling concepts back to Linux cron behavior through comparison tables and cron-like wrapper patterns.
Operations and safety
- Covers service-account execution, XML source control, log retention, common failure codes, multi-instance policy, and troubleshooting cases where a task succeeds interactively but fails when scheduled.
- Warnings: execution identity changes behavior, scheduled tasks need explicit working directories and wrapper scripts, and event-based triggers can create long-lived or high-noise automation if not constrained carefully.
- Comparison tables: the Linux-cron comparison and scheduler-choice guidance are the note’s main decision surfaces.
Glossary
Task Scheduler
The built-in Windows scheduling service that stores tasks and launches them based on time, event, logon, startup, and other trigger types.
It matters here because every other interface in the note ultimately drives this same underlying scheduler.
Platform scheduler of record
Whether tasks are created in the GUI, with
schtasks.exe, or through PowerShell, Task Scheduler is the service that owns their runtime behavior.
schtasks.exe
The legacy but still fully capable command-line interface for creating, querying, changing, and deleting scheduled tasks.
It matters here because it works everywhere and remains useful in batch files, restricted shells, and automation environments without rich PowerShell modules.
Universal CLI fallback
schtasks.exeis not the prettiest interface, but it is the most portable one across Windows environments and automation contexts.
ScheduledTasks module
The PowerShell module that exposes Task Scheduler as structured cmdlets and objects rather than string-heavy CLI syntax.
It matters here because it is the most maintainable way to define tasks programmatically in modern Windows automation.
Object model still maps to Task Scheduler rules
The PowerShell surface is cleaner, but the same scheduler semantics still apply underneath. Triggers, principals, and settings still need explicit design.
PSScheduledJob
A PowerShell-centric scheduling feature that runs PowerShell script blocks or scripts as scheduled jobs with native output history.
It matters here because some teams want tighter PowerShell integration and easier result retrieval than ordinary scheduled tasks provide.
Best for PowerShell-native workflows
PSScheduledJob is useful when the job itself is PowerShell-first. It is less universal than Task Scheduler for mixed-language or non-PowerShell workloads.
Trigger
The scheduling condition that determines when a task should fire, such as a time, startup event, logon, or Windows event log entry.
It matters here because trigger choice determines cadence, operational semantics, and how surprising the task’s runtime behavior will be.
Firing semantics shape the whole task
A task’s reliability is often more about choosing the right trigger pattern than about the command being run. Repetition, event noise, and missed-run behavior all start here.
Action
The program, script, or command line Task Scheduler runs when a trigger fires.
It matters here because Windows tasks often fail due to how the action is wrapped, quoted, or pointed at interpreters and working directories.
Wrapper quality matters
The action should usually be a stable wrapper script, not a fragile inline command. That is the easiest way to control env vars, logging, and exit codes consistently.
Principal
The Windows identity under which the task runs, such as SYSTEM, a service account, or a specific user.
It matters here because permissions, network access, desktop interaction, and credential behavior all depend on the chosen principal.
Execution identity changes behavior
A task that works interactively can fail when scheduled simply because it runs under a different identity with different access to files, shares, or secrets.
Task settings
The scheduler rules that control retries, timeouts, missed-run behavior, power conditions, and multi-instance policy.
It matters here because these settings decide how the task behaves under failure, overlap, reboot, and host-state transitions.
Defaults are rarely enough
Reliable scheduled work depends on explicit timeout, retry, and overlap settings. Leaving them at defaults often creates confusing partial automation.
Task XML
The serialized definition format Task Scheduler uses to store and exchange tasks.
It matters here because export, import, and source control for Windows task definitions all revolve around XML.
Infrastructure-as-code artifact
XML export is one of the best ways to make Windows scheduler configuration reviewable and portable across machines.
Event trigger
A trigger that launches a task when a matching Windows event log record or related system event occurs.
It matters here because event-driven automation is one of the main ways Windows scheduling goes beyond simple cron-style timers.
Easy to overfire
Event filters need precision. A broad subscription can generate noisy or effectively continuous task execution under real operational workloads.
Service account
A non-human Windows identity used to run scheduled tasks with controlled privileges.
It matters here because production jobs should usually run under dedicated service identities rather than personal user accounts.
Shared automation credential
Service-account scope and credential storage determine how safely tasks can access network shares, databases, and remote systems. Treat them as a security boundary.
LastTaskResult
The status code Task Scheduler records for the most recent task execution.
It matters here because it is one of the quickest operational signals for whether a scheduled task actually completed successfully.
Code needs interpretation
A nonzero result tells you failure happened, but not why. Pair it with task history and wrapper-script logging instead of treating it as a full diagnosis.
Multi-instance policy
The scheduler setting that decides what happens when a trigger fires while a prior instance of the task is still running.
It matters here because overlap handling is one of the most common sources of duplicate work, skipped work, or silent queueing confusion.
Overlap semantics must be intentional
Whether tasks queue, stop, or run in parallel changes data integrity and resource consumption. This should be chosen explicitly for every serious recurring task.
Working directory
The filesystem location from which a scheduled task launches its action.
It matters here because Windows tasks frequently fail or appear to do nothing when relative paths are resolved from the wrong directory.
Interactive shells hide this bug
Scripts that work manually often rely on the current directory without realizing it. Scheduled execution needs the start path and file references to be explicit.
SSIS /
sqlcmdscheduling pattern
A Windows-specific scheduling use case where Task Scheduler launches Integration Services packages or SQL Server command-line maintenance jobs.
It matters here because this is a core reason many data teams still need strong Windows scheduling guidance even when the rest of the stack is cross-platform.
Native data-platform fit
Some operational workloads belong on Windows because the surrounding tooling does. Task Scheduler remains relevant when the platform dependencies are genuinely Windows-native.
Windows Task Scheduler Core Concepts
Task Scheduler (the Windows service Schedule) stores tasks as XML files under C:\Windows\System32\Tasks\. Each task contains four components:
| Component | Purpose |
|---|---|
| Trigger | When the task fires (schedule, event, logon, idle) |
| Action | What to execute (program, script, COM handler, email, message) |
| Condition | Pre-conditions that must hold for the task to run |
| Settings | Behavior rules (retries, timeouts, multi-instance policy) |
Task XML Location
Tasks created by users live under
C:\Windows\System32\Tasks\and can be exported/imported viaschtasks /query /xmlor the GUI’s Export/Import commands. Storing these XML files in source control is a valid infrastructure-as-code approach for Windows schedulers.
schtasks.exe — Full Reference
schtasks.exe is the built-in command-line interface to Task Scheduler, available on every Windows version without additional modules. It covers the full CRUD lifecycle and is useful in cmd scripts, batch files, and contexts where PowerShell is restricted.
Verbs
| Verb | Action |
|---|---|
/create | Create a new scheduled task |
/query | List tasks and their status |
/change | Modify an existing task |
/delete | Remove a task |
/run | Trigger a task immediately |
/end | Terminate a running task instance |
/showsid | Display the SID for a task account |
Query — Inspect Existing Tasks
List all tasks on the local machine in table format
schtasks /query /fo TABLE /vList all tasks in CSV format (easier to parse in scripts)
schtasks /query /fo CSV /v > tasks-export.csvExport a specific task as XML (for source control)
schtasks /query /tn "BackupDB" /xml > BackupDB.xmlQuery tasks on a remote machine
schtasks /query /s SQLSERVER01 /u DOMAIN\Admin /p Password /fo TABLECreate — All Trigger Types
Every /create call requires at minimum: /sc (schedule type), /tn (task name), /tr (task run — the program or script to execute).
MINUTE — run every N minutes
:: Run every 15 minutes, indefinitely
schtasks /create /sc MINUTE /mo 15 /tn "Poll-API" /tr "C:\Scripts\poll_api.py" /fHOURLY — run every N hours
:: Run once per hour starting at :30
schtasks /create /sc HOURLY /mo 1 /st 00:30 /tn "Hourly-ETL" /tr "C:\Scripts\run_etl.bat" /fDAILY — run once a day at a fixed time
:: Run every day at 02:00 AM as a service account, with highest privileges
schtasks /create ^
/sc DAILY ^
/tn "BackupDB" ^
/tr "\"C:\Program Files\Scripts\backup_db.ps1\"" ^
/st 02:00 ^
/ru "DOMAIN\svc-etl" ^
/rp "ServiceAccountPassword" ^
/rl HIGHEST ^
/fQuoting Paths with Spaces
When the script path contains spaces, wrap the entire path in escaped quotes inside the
/trvalue:/tr "\"C:\My Scripts\run.ps1\"". Without this, Task Scheduler truncates the path at the first space.
Fix: store scripts in paths without spaces, or always use escaped quotes
Place pipeline scripts under
C:\Scripts\orC:\Pipelines\(no spaces) to avoid quoting issues entirely. When spaces are unavoidable, construct the/trvalue as"/tr \"\"C:\My Scripts\run.ps1\"\"". With the PowerShell module, set-Executeand-Argumentas separate parameters — spaces in the-Executepath are handled correctly byNew-ScheduledTaskAction.
WEEKLY — run on specific days
:: Every Monday and Wednesday at 06:00
schtasks /create ^
/sc WEEKLY /d MON,WED /st 06:00 ^
/tn "Weekly-Report" ^
/tr "powershell.exe -ExecutionPolicy Bypass -File C:\Scripts\report.ps1" ^
/ru SYSTEM /fMONTHLY — run on specific day of month
:: 1st day of every month at midnight
schtasks /create ^
/sc MONTHLY /d 1 /mo * /st 00:00 ^
/tn "Monthly-Rollup" ^
/tr "C:\Scripts\monthly_rollup.bat" /f
:: Last day of every month (use /d LASTDAY)
schtasks /create /sc MONTHLY /d LASTDAY /st 23:30 /tn "EOM-Archive" /tr "C:\Scripts\archive.bat" /fONCE — run a single time
:: Fire exactly once on 2026-04-01 at 09:00
schtasks /create /sc ONCE /sd 04/01/2026 /st 09:00 /tn "One-Time-Migration" /tr "C:\Scripts\migrate.ps1" /fONSTART — run at every system startup
:: Restart a monitoring agent on boot, run as SYSTEM
schtasks /create /sc ONSTART /tn "Start-Monitor" /tr "C:\Agents\monitor.exe" /ru SYSTEM /fONLOGON — run when any user logs on
:: Sync drive mappings for any user
schtasks /create /sc ONLOGON /tn "Drive-Map" /tr "C:\Scripts\map_drives.bat" /fONIDLE — run when the machine is idle for N minutes
:: Run data quality scan when idle for 10 minutes
schtasks /create /sc ONIDLE /i 10 /tn "Idle-DQ-Scan" /tr "C:\Scripts\dq_scan.ps1" /fONEVENT — run on a Windows Event Log entry
:: Trigger on Event ID 1000 in Application log (failure event)
schtasks /create ^
/sc ONEVENT ^
/ec Application ^
/mo "*[System[EventID=1000]]" ^
/tn "On-App-Error" ^
/tr "C:\Scripts\notify_on_error.ps1" ^
/ru SYSTEM /fChange — Modify Existing Tasks
Change the run time of an existing task
schtasks /change /tn "BackupDB" /st 03:00Disable a task without deleting it
schtasks /change /tn "BackupDB" /disableRe-enable a disabled task
schtasks /change /tn "BackupDB" /enableChange the run-as user
schtasks /change /tn "BackupDB" /ru "DOMAIN\new-svc-account" /rp "NewPassword"Delete
:: Delete with confirmation prompt
schtasks /delete /tn "BackupDB"
:: Delete without prompt (/f = force)
schtasks /delete /tn "BackupDB" /f
:: Delete an entire task folder and all tasks inside it
schtasks /delete /tn "\MyFolder\*" /fRun and End
:: Trigger a task on demand (does not reset its schedule)
schtasks /run /tn "BackupDB"
:: Stop a currently running task instance
schtasks /end /tn "BackupDB"Import from XML
:: Create a task from a previously exported XML file
schtasks /create /tn "BackupDB" /xml "BackupDB.xml" /fPowerShell ScheduledTasks Module
The ScheduledTasks module (built into Windows 8+ / Server 2012+) exposes Task Scheduler through PowerShell objects. It supports the full feature set of the GUI and produces scriptable, version-controllable task definitions.
Check Module Availability
Get-Module -ListAvailable ScheduledTasks— if it returns nothing, you are on Windows 7 or Server 2008 R2. Useschtasks.exeor install RSAT.
Core Cmdlets
| Cmdlet | Purpose |
|---|---|
New-ScheduledTaskTrigger | Define when a task fires |
New-ScheduledTaskAction | Define what a task runs |
New-ScheduledTaskPrincipal | Define who the task runs as |
New-ScheduledTaskSettingsSet | Define behavioral settings |
Register-ScheduledTask | Create (register) the task |
Set-ScheduledTask | Modify a registered task |
Unregister-ScheduledTask | Delete a task |
Get-ScheduledTask | Retrieve task objects |
Get-ScheduledTaskInfo | Retrieve last-run results and next-run time |
Start-ScheduledTask | Run a task on demand |
Stop-ScheduledTask | Terminate a running task |
Enable-ScheduledTask | Enable a disabled task |
Disable-ScheduledTask | Disable without deleting |
Export-ScheduledTask | Export task XML |
New-ScheduledTaskTrigger — All Trigger Types
Daily trigger
# Fire every day at 02:00 AM
$trigger = New-ScheduledTaskTrigger -Daily -At "02:00"Weekly trigger
# Every Monday at 06:00 AM
$trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Monday -At "06:00"
# Multiple days
$trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Monday,Wednesday,Friday -At "08:00"Repeating trigger (every N minutes, using RepetitionInterval)
# Fire at startup, then repeat every 30 minutes indefinitely
$trigger = New-ScheduledTaskTrigger -AtStartup
$trigger.Repetition = (New-CimInstance -ClassName MSFT_TaskRepetitionPattern `
-ClientOnly -Namespace Root/Microsoft/Windows/TaskScheduler `
-Property @{
Interval = "PT30M" # ISO 8601 duration: 30 minutes
Duration = "PT0S" # PT0S = indefinite
StopAtDurationEnd = $false
})ISO 8601 Duration Strings
Task Scheduler uses ISO 8601 duration format:
PT15M= 15 minutes,PT1H= 1 hour,P1D= 1 day,P1DT2H30M= 1 day, 2 hours, 30 minutes.
Once trigger
$trigger = New-ScheduledTaskTrigger -Once -At "2026-04-01 09:00"At logon
# Any user
$trigger = New-ScheduledTaskTrigger -AtLogOn
# Specific user only
$trigger = New-ScheduledTaskTrigger -AtLogOn -User "DOMAIN\jdoe"At startup
$trigger = New-ScheduledTaskTrigger -AtStartupEvent-based trigger (via CIM, not natively in New-ScheduledTaskTrigger)
# Event triggers require building the CIM object directly
$eventTrigger = New-CimInstance -ClassName MSFT_TaskEventTrigger `
-ClientOnly -Namespace Root/Microsoft/Windows/TaskScheduler `
-Property @{
Enabled = $true
Subscription = '<QueryList><Query Id="0" Path="Application"><Select Path="Application">*[System[EventID=1000]]</Select></Query></QueryList>'
Delay = "PT1M" # Wait 1 minute after event before firing
}New-ScheduledTaskAction
# Run a PowerShell script
$action = New-ScheduledTaskAction `
-Execute "powershell.exe" `
-Argument "-ExecutionPolicy Bypass -NonInteractive -File `"C:\Scripts\backup.ps1`"" `
-WorkingDirectory "C:\Scripts"
# Run a Python script inside a virtualenv
$action = New-ScheduledTaskAction `
-Execute "C:\Pipelines\venv\Scripts\python.exe" `
-Argument "C:\Pipelines\run_pipeline.py --env prod" `
-WorkingDirectory "C:\Pipelines"
# Run sqlcmd
$action = New-ScheduledTaskAction `
-Execute "sqlcmd.exe" `
-Argument "-S SQLSERVER01 -d master -E -i `"C:\Scripts\backup.sql`" -o `"C:\Logs\backup.log`""New-ScheduledTaskPrincipal
# Run as SYSTEM (no password needed, no interactive logon)
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
# Run as a service account (stored password in Credential Manager)
$principal = New-ScheduledTaskPrincipal `
-UserId "DOMAIN\svc-etl" `
-LogonType Password `
-RunLevel Highest
# Run as current user only when logged on (interactive, can show windows)
$principal = New-ScheduledTaskPrincipal `
-UserId "$env:USERDOMAIN\$env:USERNAME" `
-LogonType InteractiveToken `
-RunLevel LimitedNew-ScheduledTaskSettingsSet
$settings = New-ScheduledTaskSettingsSet `
-ExecutionTimeLimit (New-TimeSpan -Hours 2) ` # Kill task after 2 hours
-RestartCount 3 ` # Retry up to 3 times on failure
-RestartInterval (New-TimeSpan -Minutes 5) ` # Wait 5 minutes between retries
-StartWhenAvailable ` # Run ASAP if missed start time
-MultipleInstances IgnoreNew ` # Don't start if already running
-DisallowStartIfOnBatteries $false ` # Run even on battery power
-StopIfGoingOnBatteries $false ` # Don't stop on battery switch
-WakeToRun $false ` # Don't wake computer to run
-RunOnlyIfNetworkAvailable $false ` # Run even without network
-Priority 7 # Normal priority (0=highest, 10=lowest)MultipleInstances options
| Value | Behavior |
|---|---|
IgnoreNew | Skip new invocation if already running |
Parallel | Allow multiple simultaneous instances |
Queue | Queue new instance; run after current finishes |
StopExisting | Kill the running instance and start a new one |
Register-ScheduledTask — Putting It Together
# Full registration with all components
Register-ScheduledTask `
-TaskName "ETL-Daily-Backup" `
-TaskPath "\DataEngineering\" ` # Folder in Task Scheduler tree
-Trigger $trigger `
-Action $action `
-Principal $principal `
-Settings $settings `
-Description "Nightly database backup via sqlcmd, runs at 02:00 AM" `
-Force # Overwrite if task already existsGet-ScheduledTask and Get-ScheduledTaskInfo
# List all tasks
Get-ScheduledTask
# Filter by folder path
Get-ScheduledTask -TaskPath "\DataEngineering\"
# Filter by name pattern
Get-ScheduledTask | Where-Object { $_.TaskName -like "ETL-*" }
# Get last run result and next scheduled run
Get-ScheduledTaskInfo -TaskName "ETL-Daily-Backup"
# Check last run result code (0 = success)
$info = Get-ScheduledTaskInfo -TaskName "ETL-Daily-Backup"
Write-Host "Last result: $($info.LastTaskResult)"
Write-Host "Next run: $($info.NextRunTime)"
Write-Host "Last run: $($info.LastRunTime)"Last task result codes
| Code | Meaning |
|---|---|
0 | Success |
1 | Incorrect function call or unknown error |
0x41301 | Task is currently running |
0x41303 | Task has not yet run |
0x41306 | Task was terminated by user |
0xFFFFFFFF | Task ran but returned an error (check your script’s exit code) |
Translating Exit Codes
Your script’s exit code maps directly to
LastTaskResult. Always useexit 0on success andexit 1(or a non-zero code) on failure in PowerShell scripts to make this field meaningful.
Modify and Delete
# Update the trigger on an existing task
$newTrigger = New-ScheduledTaskTrigger -Daily -At "03:00"
Set-ScheduledTask -TaskName "ETL-Daily-Backup" -Trigger $newTrigger
# Update the action
Set-ScheduledTask -TaskName "ETL-Daily-Backup" -Action $newAction
# Disable / enable
Disable-ScheduledTask -TaskName "ETL-Daily-Backup"
Enable-ScheduledTask -TaskName "ETL-Daily-Backup"
# Delete without prompt
Unregister-ScheduledTask -TaskName "ETL-Daily-Backup" -Confirm:$false
# Delete all tasks in a folder
Get-ScheduledTask -TaskPath "\DataEngineering\" |
Unregister-ScheduledTask -Confirm:$falseExport / Import for Source Control
# Export to XML
Export-ScheduledTask -TaskName "ETL-Daily-Backup" | Out-File "ETL-Daily-Backup.xml" -Encoding UTF8
# Import from XML on another machine
$xml = Get-Content "ETL-Daily-Backup.xml" -Raw
Register-ScheduledTask -Xml $xml -TaskName "ETL-Daily-Backup" -ForceInfrastructure as Code
Store exported task XMLs in a
tasks/folder in your project repository. During CI/CD deployment, useRegister-ScheduledTask -Xmlto idempotently apply task definitions to target servers. This makes scheduled task configuration auditable and reproducible.
Complete Example — Schedule a Python Pipeline on Windows
This example schedules a Python ETL pipeline that uses a virtual environment, logs to a timestamped file, and sends an email notification on failure.
# === Schedule a Python ETL Pipeline ===
# Runs every day at 01:30 AM as a service account
$taskName = "Python-ETL-Pipeline"
$taskFolder = "\DataEngineering\"
$pythonExe = "C:\Pipelines\etl-venv\Scripts\python.exe"
$scriptPath = "C:\Pipelines\run_etl.py"
$logDir = "C:\Logs\ETL"
$logFile = "$logDir\etl-%DATE:~10,4DATE:~7,2%.log" # yyyyMMdd suffix
# Ensure log directory exists (run once during setup)
New-Item -ItemType Directory -Path $logDir -Force | Out-Null
# Action: python.exe with log redirection handled in the wrapper script
$action = New-ScheduledTaskAction `
-Execute $pythonExe `
-Argument "`"$scriptPath`" --env prod --log-dir `"$logDir`"" `
-WorkingDirectory "C:\Pipelines"
# Trigger: daily at 01:30
$trigger = New-ScheduledTaskTrigger -Daily -At "01:30"
# Principal: service account, run whether or not user is logged on
$principal = New-ScheduledTaskPrincipal `
-UserId "DOMAIN\svc-etl" `
-LogonType Password `
-RunLevel Highest
# Settings: 3-hour timeout, retry twice on failure, start if missed
$settings = New-ScheduledTaskSettingsSet `
-ExecutionTimeLimit (New-TimeSpan -Hours 3) `
-RestartCount 2 `
-RestartInterval (New-TimeSpan -Minutes 10) `
-StartWhenAvailable `
-MultipleInstances IgnoreNew `
-DisallowStartIfOnBatteries $false
# Register the task (requires elevation)
Register-ScheduledTask `
-TaskName $taskName `
-TaskPath $taskFolder `
-Action $action `
-Trigger $trigger `
-Principal $principal `
-Settings $settings `
-Description "Nightly Python ETL pipeline. Logs to $logDir." `
-Force
Write-Host "Task '$taskName' registered in '$taskFolder'."Password Storage
When using
LogonType Password, the service account password is stored encrypted in the task definition by the Task Scheduler service (using DPAPI). The password is not retrievable in plaintext but must be re-entered if changed. Consider using a Group Managed Service Account (gMSA) withLogonType Passwordand no explicit password — Windows manages gMSA passwords automatically.
Fix: use a gMSA or SYSTEM account to eliminate password management entirely
Create a Group Managed Service Account with
New-ADServiceAccountand grant it logon-as-a-batch-job rights. Register the task with-UserId "DOMAIN\svc-etl$"(note the trailing$) and-LogonType Passwordbut no-Passwordparameter — the domain controller rotates the password automatically. Alternatively, run underSYSTEMif the task does not need network credentials.
Complete Example — Schedule a SQL Server Backup with sqlcmd
# === Schedule nightly SQL Server backup via sqlcmd ===
$taskName = "SQLServer-NightlyBackup"
$sqlServer = "SQLSERVER01"
$database = "OperationsDB"
$backupDir = "\\BACKUPSERVER\SQLBackups"
$scriptDir = "C:\DBScripts"
$sqlScript = "$scriptDir\backup_full.sql"
# The SQL script (create this file separately):
# BACKUP DATABASE [OperationsDB]
# TO DISK = N'\\BACKUPSERVER\SQLBackups\OperationsDB_FULL_$(Get-Date -Format yyyyMMdd).bak'
# WITH COMPRESSION, STATS = 10;
$action = New-ScheduledTaskAction `
-Execute "sqlcmd.exe" `
-Argument "-S $sqlServer -d master -E -i `"$sqlScript`" -o `"C:\Logs\backup_$(Get-Date -Format yyyyMMdd).log`" -b" `
-WorkingDirectory $scriptDir
# -E = Windows Authentication (trusted connection)
# -b = exit with error code on SQL errors (makes LastTaskResult non-zero on failure)
$trigger = New-ScheduledTaskTrigger -Daily -At "02:00"
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet `
-ExecutionTimeLimit (New-TimeSpan -Hours 4) `
-RestartCount 1 `
-RestartInterval (New-TimeSpan -Minutes 30) `
-StartWhenAvailable `
-MultipleInstances IgnoreNew
Register-ScheduledTask `
-TaskName $taskName `
-TaskPath "\DataEngineering\" `
-Action $action `
-Trigger $trigger `
-Principal $principal `
-Settings $settings `
-Description "Full backup of $database to $backupDir via sqlcmd. Runs at 02:00 daily." `
-Forcesqlcmd -b Flag
The
-bflag is critical: it makessqlcmdexit with a non-zero code when SQL errors occur. Without-b, a backup failure still exits with code0, causing Task Scheduler to report success.
PowerShell Scheduled Jobs (PSScheduledJob)
PSScheduledJob (Register-ScheduledJob) is a PowerShell-centric alternative that integrates with the PowerShell job infrastructure (Get-Job, Receive-Job). It stores job results locally and allows you to retrieve script output long after the job completes — useful for debugging pipelines.
PSScheduledJob vs ScheduledTask
Use
Register-ScheduledJobwhen you need to inspect rich PowerShell output objects (not just exit codes) after the job runs. UseRegister-ScheduledTaskfor everything else — it is more configurable and runs any executable, not just PowerShell scripts.
Register-ScheduledJob
# Import the module (auto-imported on PS 3.0+, explicit for clarity)
Import-Module PSScheduledJob
# Define a trigger
$trigger = New-JobTrigger -Daily -At "03:00"
# Define options (analogous to ScheduledTask settings)
$options = New-ScheduledJobOption `
-RunElevated ` # Run with elevated privileges
-RequireNetwork ` # Only run if network is available
-StartIfOnBattery ` # Run even on battery
-ContinueIfGoingOnBattery ` # Don't stop on battery switch
-IdleTimeout (New-TimeSpan -Minutes 10) `
-MultipleInstancePolicy IgnoreNew
# Register the job
Register-ScheduledJob `
-Name "ETL-ScheduledJob" `
-ScriptBlock {
# All PowerShell output here is captured and retrievable via Receive-Job
param($Server, $Database)
Import-Module SqlServer
$result = Invoke-Sqlcmd -ServerInstance $Server -Database $Database `
-Query "EXEC usp_RunETL"
Write-Output $result
} `
-ArgumentList "SQLSERVER01", "OperationsDB" `
-Trigger $trigger `
-ScheduledJobOption $options `
-Credential (Get-Credential "DOMAIN\svc-etl") `
-MaxResultCount 7 # Keep results from last 7 runsRetrieve Job Results
# List all scheduled jobs
Get-ScheduledJob
# Get the job object
$job = Get-Job -Name "ETL-ScheduledJob" | Sort-Object PSBeginTime -Descending | Select-Object -First 1
# Check status
$job.State # Completed, Failed, Running, NotStarted
# Retrieve output (objects written to pipeline inside the ScriptBlock)
$results = Receive-Job -Job $job -Keep # -Keep so you can retrieve again later
# Or retrieve output from a specific historical run stored on disk
$storedJob = Get-Job -Name "ETL-ScheduledJob"
$storedJob | Receive-Job -KeepJob result storage location
C:\Users\<username>\AppData\Local\Microsoft\Windows\PowerShell\ScheduledJobs\
ETL-ScheduledJob\
Output\
1\ ← run 1 results
2\ ← run 2 results
...
7\ ← run 7 (MaxResultCount limit)
Manage Scheduled Jobs
# Disable without deleting
Get-ScheduledJob -Name "ETL-ScheduledJob" | Disable-ScheduledJob
# Re-enable
Get-ScheduledJob -Name "ETL-ScheduledJob" | Enable-ScheduledJob
# Update trigger
$newTrigger = New-JobTrigger -Weekly -DaysOfWeek Monday -At "06:00"
Get-ScheduledJob -Name "ETL-ScheduledJob" | Set-ScheduledJob -Trigger $newTrigger
# Permanently remove
Get-ScheduledJob -Name "ETL-ScheduledJob" | Unregister-ScheduledJobEvent-Based Triggers
Trigger on Windows Event Log Entry
Use ONEVENT triggers to react to system events — application errors, service state changes, security events.
schtasks approach
:: Trigger when SQL Server writes Event ID 18456 (login failure) to Application log
schtasks /create ^
/sc ONEVENT ^
/ec Application ^
/mo "*[System[Provider[@Name='MSSQLSERVER'] and EventID=18456]]" ^
/tn "Alert-SQLLoginFailure" ^
/tr "powershell.exe -File C:\Scripts\alert_login_failure.ps1" ^
/ru SYSTEM /fPowerShell approach with CIM
# Build the WMI event filter subscription XML
$eventSubscription = @'
<QueryList>
<Query Id="0" Path="Application">
<Select Path="Application">
*[System[Provider[@Name='MSSQLSERVER'] and EventID=18456]]
</Select>
</Query>
</QueryList>
'@
$eventTrigger = New-CimInstance `
-ClassName MSFT_TaskEventTrigger `
-ClientOnly `
-Namespace Root/Microsoft/Windows/TaskScheduler `
-Property @{
Enabled = $true
Subscription = $eventSubscription
Delay = "PT30S" # Wait 30 seconds after event before firing
}
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
-Argument "-File C:\Scripts\alert_login_failure.ps1"
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet -MultipleInstances Queue
Register-ScheduledTask `
-TaskName "Alert-SQLLoginFailure" `
-TaskPath "\Monitoring\" `
-Trigger $eventTrigger `
-Action $action `
-Principal $principal `
-Settings $settings `
-ForceTrigger on File System Changes (FileSystemWatcher)
Task Scheduler has no native file-watch trigger. Use a persistent PowerShell process with FileSystemWatcher as a lightweight alternative — typically run as a Windows Service or a background scheduled task at startup.
# === File System Watcher — watch a drop folder and process new CSV files ===
# Run this script via a Task Scheduler ONSTART task (perpetual process)
$watchFolder = "C:\DataDrop\Incoming"
$processScript = "C:\Scripts\process_csv.ps1"
$logFile = "C:\Logs\file-watcher.log"
function Write-Log {
param($Message)
"$((Get-Date -Format 'yyyy-MM-dd HH:mm:ss')) $Message" | Tee-Object -FilePath $logFile -Append
}
# Create watcher
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = $watchFolder
$watcher.Filter = "*.csv"
$watcher.IncludeSubdirectories = $false
$watcher.EnableRaisingEvents = $true
# Register Created event
$action = {
$filePath = $Event.SourceEventArgs.FullPath
Write-Log "New file detected: $filePath"
# Brief delay to ensure file write is complete before processing
Start-Sleep -Seconds 2
try {
& "C:\Scripts\process_csv.ps1" -FilePath $filePath
Write-Log "Processed: $filePath"
} catch {
Write-Log "ERROR processing $filePath`: $_"
}
}
Register-ObjectEvent -InputObject $watcher -EventName Created -Action $action | Out-Null
Write-Log "FileSystemWatcher started. Watching: $watchFolder"
# Block indefinitely — task runs perpetually, restarted on reboot by Task Scheduler
while ($true) { Start-Sleep -Seconds 60 }# Register the watcher script as an ONSTART task
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
-Argument "-ExecutionPolicy Bypass -NonInteractive -File `"C:\Scripts\file-watcher.ps1`""
$trigger = New-ScheduledTaskTrigger -AtStartup
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Days 0) # No timeout
Register-ScheduledTask -TaskName "FileWatcher-DataDrop" -TaskPath "\DataEngineering\" `
-Action $action -Trigger $trigger -Principal $principal -Settings $settings -ForceFileSystemWatcher and network shares
FileSystemWatcherdoes not reliably detect changes on UNC paths or mapped drives. For network share monitoring, poll the directory withGet-ChildItemon a scheduled interval instead.
Fix: poll network shares with Get-ChildItem on a short interval
Schedule a task to run every 1–5 minutes that calls
Get-ChildItem -Path "\\server\share\incoming" -Filter "*.csv"and compares results against a state file of already-processed files. This is more reliable thanFileSystemWatcheron network paths and survives transient network interruptions.
Trigger on Service Failure
Windows Services have built-in failure recovery actions (restart, run a program, reboot). For a scheduled-task-based approach using event triggers:
# Trigger when a specific Windows Service fails (Event ID 7034 = service crashed)
$subscription = @'
<QueryList>
<Query Id="0" Path="System">
<Select Path="System">
*[System[Provider[@Name='Service Control Manager'] and EventID=7034]]
and *[EventData[Data[@Name='param1']='MyServiceName']]
</Select>
</Query>
</QueryList>
'@
# Action: restart the service
$action = New-ScheduledTaskAction `
-Execute "powershell.exe" `
-Argument "-Command `"Start-Service 'MyServiceName'; Write-EventLog -LogName Application -Source 'TaskScheduler' -EventId 9999 -EntryType Warning -Message 'MyServiceName auto-restarted by scheduled task'`""
# ... (register task as above using $subscription as event trigger)Data Engineering Patterns on Windows
Scheduling SSIS Packages
SQL Server Agent is the preferred scheduler for SSIS, but Task Scheduler works when SQL Server Agent is unavailable.
# === Schedule an SSIS package via DTExec ===
# DTExec is installed with SQL Server Integration Services
$action = New-ScheduledTaskAction `
-Execute "DTExec.exe" `
-Argument "/F `"C:\SSIS\Packages\LoadDimCustomer.dtsx`" /REPORTING EW /SET `"\Package.Variables[User::TargetDate].Properties[Value]`";`"$(Get-Date -Format yyyy-MM-dd)`"" `
-WorkingDirectory "C:\SSIS\Packages"
# /F = file path to .dtsx package
# /REPORTING EW = report errors and warnings
# /SET = override package variable at runtime
$trigger = New-ScheduledTaskTrigger -Daily -At "04:00"
$principal = New-ScheduledTaskPrincipal -UserId "DOMAIN\svc-ssis" -LogonType Password -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Hours 2) -StartWhenAvailable
Register-ScheduledTask -TaskName "SSIS-LoadDimCustomer" -TaskPath "\DataEngineering\" `
-Action $action -Trigger $trigger -Principal $principal -Settings $settings -ForceSQL Agent vs Task Scheduler for SSIS
SQL Server Agent is strongly preferred for SSIS: it captures package execution history, supports SQL Server Agent alerts, and integrates with SSIS Catalog (SSISDB) deployment model. Use Task Scheduler for SSIS only when running DTSX file-system packages on machines without SQL Server Agent.
Scheduling Python Pipelines with Virtual Environments
# === Python pipeline with venv, environment variables, and structured logging ===
# Wrapper batch file approach (recommended for env var injection)
$wrapperScript = @"
@echo off
REM Set environment variables for the pipeline
set PIPELINE_ENV=prod
set DB_SERVER=SQLSERVER01
set BQ_PROJECT=my-gcp-project
set GCP_SA_KEY=C:\Secrets\sa-key.json
REM Activate virtual environment
call C:\Pipelines\etl-venv\Scripts\activate.bat
REM Run the pipeline with output logging
python C:\Pipelines\run_pipeline.py --env %PIPELINE_ENV% >> C:\Logs\pipeline_%DATE:~10,4DATE:~7,2%.log 2>&1
REM Capture exit code and deactivate
set EXITCODE=%ERRORLEVEL%
call deactivate
exit /b %EXITCODE%
"@
$wrapperPath = "C:\Pipelines\run_pipeline_wrapper.bat"
Set-Content -Path $wrapperPath -Value $wrapperScript -Encoding ASCII
$action = New-ScheduledTaskAction -Execute $wrapperPath -WorkingDirectory "C:\Pipelines"
$trigger = New-ScheduledTaskTrigger -Daily -At "01:00"
$principal = New-ScheduledTaskPrincipal -UserId "DOMAIN\svc-etl" -LogonType Password -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Hours 2) -StartWhenAvailable -MultipleInstances IgnoreNew
Register-ScheduledTask -TaskName "Python-ETL-Prod" -TaskPath "\DataEngineering\" `
-Action $action -Trigger $trigger -Principal $principal -Settings $settings -ForceWrapper batch file for Python
Directly invoking
python.exefrom Task Scheduler works, but a.batwrapper lets you inject environment variables, activate a venv, redirect both stdout and stderr to a log file with a timestamp, and capture the exit code cleanly — all in one place.
Logging and Monitoring Scheduled Tasks
# === Monitor all DataEngineering tasks and report failures ===
function Get-TaskHealthReport {
param([string]$TaskPath = "\DataEngineering\")
Get-ScheduledTask -TaskPath $TaskPath | ForEach-Object {
$info = Get-ScheduledTaskInfo -TaskName $_.TaskName -TaskPath $_.TaskPath
[PSCustomObject]@{
Name = $_.TaskName
State = $_.State
LastResult = $info.LastTaskResult
LastRun = $info.LastRunTime
NextRun = $info.NextRunTime
Status = if ($info.LastTaskResult -eq 0) { "OK" } else { "FAILED ($($info.LastTaskResult))" }
}
}
}
# Print to console
Get-TaskHealthReport | Format-Table -AutoSize
# Export to CSV for monitoring dashboard
Get-TaskHealthReport | Export-Csv "C:\Logs\task-health-$(Get-Date -Format yyyyMMdd).csv" -NoTypeInformation
# Alert on failures
$failures = Get-TaskHealthReport | Where-Object { $_.LastResult -ne 0 -and $_.LastResult -ne 267011 }
# 267011 = 0x41303 = task has never run — exclude from failure alerts
if ($failures) {
$body = $failures | Format-Table | Out-String
Send-MailMessage `
-From "noreply@company.com" `
-To "data-team@company.com" `
-Subject "ALERT: Scheduled Task Failures on $env:COMPUTERNAME" `
-Body $body `
-SmtpServer "smtp.company.com"
}Error Handling and Email Notification Pattern
A reusable pattern: wrap any pipeline script in a try/catch that sends an email on failure and exits with a non-zero code.
# === Pipeline wrapper with email notification on failure ===
# Place this boilerplate at the top of any scheduled pipeline script
param(
[string]$Env = "prod",
[string]$LogDir = "C:\Logs"
)
$ErrorActionPreference = "Stop"
$logFile = Join-Path $LogDir "pipeline-$(Get-Date -Format yyyyMMddHHmmss).log"
function Write-Log {
param($Message, $Level = "INFO")
$line = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') [$Level] $Message"
Add-Content -Path $logFile -Value $line
Write-Host $line
}
function Send-AlertEmail {
param($Subject, $Body)
try {
Send-MailMessage `
-From "alerts@company.com" `
-To "data-team@company.com" `
-Subject $Subject `
-Body $Body `
-SmtpServer "smtp.company.com"
} catch {
Write-Log "Failed to send alert email: $_" "ERROR"
}
}
try {
Write-Log "Pipeline started (env=$Env)"
# --- Your pipeline logic here ---
# Import-Module ...
# Invoke-Sqlcmd ...
# python ...
Write-Log "Pipeline completed successfully."
exit 0
} catch {
$errorMessage = $_.Exception.Message
$errorDetails = $_ | Out-String
Write-Log "Pipeline FAILED: $errorMessage" "ERROR"
Write-Log $errorDetails "ERROR"
Send-AlertEmail `
-Subject "PIPELINE FAILURE on $env:COMPUTERNAME — $(Get-Date -Format 'yyyy-MM-dd HH:mm')" `
-Body "Pipeline failed with error:`n`n$errorDetails`n`nLog: $logFile"
exit 1 # Non-zero exit: Task Scheduler marks LastTaskResult as failed
}Log Rotation for Scheduled Task Logs
# === Rotate logs older than 30 days ===
# Schedule this as a monthly task
$logDir = "C:\Logs"
$daysOld = 30
$archiveDir = "C:\Logs\Archive"
New-Item -ItemType Directory -Path $archiveDir -Force | Out-Null
Get-ChildItem -Path $logDir -Filter "*.log" -File |
Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-$daysOld) } |
ForEach-Object {
Move-Item -Path $_.FullName -Destination $archiveDir -Force
Write-Host "Archived: $($_.Name)"
}
# Compress archive logs older than 90 days
Get-ChildItem -Path $archiveDir -Filter "*.log" -File |
Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-90) } |
ForEach-Object {
Compress-Archive -Path $_.FullName -DestinationPath "$($_.FullName).zip" -Force
Remove-Item -Path $_.FullName
Write-Host "Compressed: $($_.Name)"
}When to Use Which Windows Scheduler
| Scheduler | Best For | Avoid When |
|---|---|---|
| Task Scheduler | Windows-native scripts; no SQL Server required; simple triggers; one-off or infrequent jobs | Complex dependencies between jobs; need rich history/alerting out-of-box |
| SQL Server Agent | SQL jobs, SSIS packages, database maintenance plans; need SQL-integrated alerting | No SQL Server available; non-database workloads |
| Apache Airflow | DAG-based pipelines with cross-system dependencies; need retry logic, SLAs, rich UI | Simple one-step scripts; low-budget environments without containerization |
| PSScheduledJob | PowerShell scripts where you need to inspect rich output objects post-run | Non-PowerShell executables; high-frequency jobs |
SQL Agent vs Task Scheduler
SQL Server Agent jobs are stored in
msdb(the system database), run under SQL Server Agent Service account, support multi-step jobs with conditional logic between steps, and write history queryable viamsdb.dbo.sysjobhistory. For any SQL-centric workload, Agent is the right choice. Task Scheduler is the fallback for machines without SQL Server or for scheduling non-database processes.
Comparison: Linux Cron vs Windows Task Scheduler
Feature Comparison Table
| Feature | Linux cron | Windows Task Scheduler |
|---|---|---|
| Configuration format | Text file (crontab) | XML (GUI or PowerShell) |
| Minimum interval | 1 minute | 1 minute (via repetition) |
| Sub-minute scheduling | No (use systemd timers) | No |
| Run at login | No (use .bashrc / systemd) | Yes (ONLOGON trigger) |
| Run at boot | Yes (@reboot) | Yes (ONSTART trigger) |
| Run on event | No (use auditd / inotifywait) | Yes (ONEVENT trigger) |
| Run when idle | No | Yes (ONIDLE trigger) |
| Conditions | No | Yes (battery, network, idle) |
| Retry on failure | No (external) | Yes (native settings) |
| Multi-instance policy | Parallel only | Configurable (Queue, Ignore, Stop) |
| Credentials | Runs as crontab owner | Configurable (SYSTEM, user, service account) |
| History / last result | /var/log/syslog or mail | Native (LastTaskResult, history log) |
| Source control friendly | Yes (text file) | Yes (XML export) |
| Seconds-level granularity | No | No |
Cron Expression to Task Scheduler Trigger Mapping
| cron expression | Meaning | Task Scheduler equivalent |
|---|---|---|
* * * * * | Every minute | New-ScheduledTaskTrigger -RepetitionInterval (New-TimeSpan -Minutes 1) |
*/15 * * * * | Every 15 minutes | Repetition interval of PT15M |
0 2 * * * | Daily at 02:00 | -Daily -At "02:00" |
0 6 * * 1 | Weekly Monday 06:00 | -Weekly -DaysOfWeek Monday -At "06:00" |
0 0 1 * * | Monthly, 1st, midnight | -Monthly -DaysOfMonth 1 -At "00:00" |
@reboot | At boot | -AtStartup |
@hourly | Every hour (:00) | -RepetitionInterval PT1H |
@daily | Daily at midnight | -Daily -At "00:00" |
@weekly | Weekly (Sunday midnight) | -Weekly -DaysOfWeek Sunday -At "00:00" |
@monthly | Monthly (1st, midnight) | -Monthly -DaysOfMonth 1 -At "00:00" |
30 9 * * 1-5 | Weekdays at 09:30 | -Weekly -DaysOfWeek Mon,Tue,Wed,Thu,Fri -At "09:30" |
0 */4 * * * | Every 4 hours | Repetition interval PT4H |
systemd timers vs Task Scheduler
Modern Linux uses
systemdtimers rather than cron for new services. Systemd timers support monotonic intervals (e.g., 30 minutes after boot) and calendar-based triggers with second-level precision — features closer to Task Scheduler’s capabilities than traditional cron.
Cron-Like Wrapper for Windows (reference pattern)
If your team is migrating from Linux and prefers cron syntax, this pattern translates a cron expression into a Task Scheduler registration:
# Minimal "cron-like" task creator for daily-at-time patterns
function Register-CronStyleTask {
param(
[string]$Name,
[string]$Command, # Full executable path
[string]$Arguments,
[string]$RunAt, # HH:mm format
[string[]]$DaysOfWeek = @() # Empty = daily
)
$action = New-ScheduledTaskAction -Execute $Command -Argument $Arguments
$trigger = if ($DaysOfWeek.Count -gt 0) {
New-ScheduledTaskTrigger -Weekly -DaysOfWeek $DaysOfWeek -At $RunAt
} else {
New-ScheduledTaskTrigger -Daily -At $RunAt
}
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -MultipleInstances IgnoreNew
Register-ScheduledTask -TaskName $Name -TaskPath "\CronJobs\" `
-Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force
Write-Host "Registered: $Name (runs at $RunAt$(if ($DaysOfWeek) {" on $($DaysOfWeek -join ',')"}))"
}
# Usage — equivalent to cron "0 2 * * *"
Register-CronStyleTask -Name "nightly-etl" -Command "python.exe" `
-Arguments "C:\Pipelines\etl.py" -RunAt "02:00"
# Usage — equivalent to cron "0 6 * * 1"
Register-CronStyleTask -Name "weekly-report" -Command "powershell.exe" `
-Arguments "-File C:\Scripts\report.ps1" -RunAt "06:00" -DaysOfWeek MondayTroubleshooting Common Issues
Task Runs Successfully Interactively but Fails as Scheduled
- Missing environment variables — Task Scheduler runs in a minimal environment. Explicitly set all required env vars in your script or wrapper.
- Wrong working directory — Always set
-WorkingDirectoryonNew-ScheduledTaskAction. Relative paths resolve differently in the task context. - Network resources unavailable — The task may start before the network is ready. Add
RunOnlyIfNetworkAvailableor use a delay trigger. - 32-bit vs 64-bit — Tasks can run under 32-bit
powershell.exeeven on 64-bit Windows. Use%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe(64-bit) explicitly.
Task Appears to Run but Does Nothing
- Check
LastTaskResultwithGet-ScheduledTaskInfo. - Add
-btosqlcmdinvocations. - Add
exit 1in your script on error (PowerShell$ErrorActionPreference = "Stop"and a top-leveltry/catch).
”The operator or administrator has refused the request” (0x800710E0)
Task Scheduler blocked execution due to a condition not being met (battery, network, idle). Check the Conditions tab in the GUI or review your New-ScheduledTaskSettingsSet parameters.
Task Skipped with “Task is already running”
Set MultipleInstances to Queue (run after current finishes) or increase ExecutionTimeLimit if the task legitimately needs more time.
# Fix multi-instance skipping
Set-ScheduledTask -TaskName "ETL-Daily" `
-Settings (New-ScheduledTaskSettingsSet -MultipleInstances Queue -ExecutionTimeLimit (New-TimeSpan -Hours 6))Windows Task Scheduler Quick Reference Cheat Sheet
# --- LIST ---
Get-ScheduledTask -TaskPath "\DataEngineering\"
schtasks /query /fo TABLE /v
# --- CREATE (minimal daily) ---
Register-ScheduledTask -TaskName "MyTask" -TaskPath "\MyFolder\" `
-Action (New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-File C:\Scripts\run.ps1") `
-Trigger (New-ScheduledTaskTrigger -Daily -At "02:00") `
-Principal (New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest) `
-Force
# --- STATUS ---
Get-ScheduledTaskInfo -TaskName "MyTask"
# --- RUN NOW ---
Start-ScheduledTask -TaskName "MyTask"
# --- STOP ---
Stop-ScheduledTask -TaskName "MyTask"
# --- DISABLE / ENABLE ---
Disable-ScheduledTask -TaskName "MyTask"
Enable-ScheduledTask -TaskName "MyTask"
# --- DELETE ---
Unregister-ScheduledTask -TaskName "MyTask" -Confirm:$false
# --- EXPORT ---
Export-ScheduledTask -TaskName "MyTask" | Out-File "MyTask.xml" -Encoding UTF8
# --- IMPORT ---
Register-ScheduledTask -Xml (Get-Content "MyTask.xml" -Raw) -TaskName "MyTask" -ForceRelated
- linux-scheduling — The Linux equivalent: cron, systemd timers, at, and anacron
- gcp-scheduling — Cloud-based scheduling with Cloud Scheduler for serverless and managed alternatives
- airflow-core-concepts — When pipelines outgrow Task Scheduler, Airflow provides DAG-based orchestration with dependencies, retries, and a monitoring UI