Skip to main content

Windows Scheduled Tasks

Introduction​

Scheduled tasks allow programs or scripts to be executed automatically according to defined triggers.

Fundamental Concepts​

Components of a Task​

  1. Trigger: When the task runs
  2. Action: What the task does
  3. Conditions: Additional criteria
  4. Settings: Execution options

Types of Triggers​

TypeDescription
AtStartupAt system startup
AtLogOnAt user logon
OnceOne time only
DailyDaily
WeeklyWeekly
OnEventOn a specific event
OnIdleWhen the system is idle

Management via GUI​

Task Scheduler (taskschd.msc)​

Win+R > taskschd.msc

Structure:

  • Task Scheduler Library
    • Microsoft (system tasks)
    • Custom folders

Create a Basic Task​

  1. Action > Create Basic Task
  2. Follow the wizard
  3. Define name, trigger, action

Create an Advanced Task​

  1. Action > Create Task
  2. General tab: Name, account, privileges
  3. Triggers tab: Triggers
  4. Actions tab: Programs to run
  5. Conditions tab: Criteria
  6. Settings tab: Options

Management via PowerShell​

List Tasks​

# All tasks
Get-ScheduledTask

# Active tasks
Get-ScheduledTask | Where-Object {$_.State -eq "Ready"}

# Search by name
Get-ScheduledTask -TaskName "*Update*"

# Task details
Get-ScheduledTask -TaskName "TaskName" | Get-ScheduledTaskInfo

Create a Task​

# Define the action
$action = New-ScheduledTaskAction `
-Execute "powershell.exe" `
-Argument "-File C:\Scripts\MyScript.ps1"

# Define the trigger
$trigger = New-ScheduledTaskTrigger -Daily -At "08:00"

# Define the principal (who runs it)
$principal = New-ScheduledTaskPrincipal `
-UserId "SYSTEM" `
-RunLevel Highest

# Options
$settings = New-ScheduledTaskSettingsSet `
-AllowStartIfOnBatteries `
-DontStopIfGoingOnBatteries

# Create the task
Register-ScheduledTask `
-TaskName "MyTask" `
-Action $action `
-Trigger $trigger `
-Principal $principal `
-Settings $settings `
-Description "Description of my task"

Types of Triggers​

# At startup
$trigger = New-ScheduledTaskTrigger -AtStartup

# At logon
$trigger = New-ScheduledTaskTrigger -AtLogOn

# Once
$trigger = New-ScheduledTaskTrigger -Once -At "2025-01-15 10:00"

# Daily
$trigger = New-ScheduledTaskTrigger -Daily -At "08:00"

# Weekly
$trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Monday -At "09:00"

# On an event
$trigger = New-ScheduledTaskTrigger -AtStartup
# (for specific events, use schtasks.exe)

Modify a Task​

# Change the trigger
$newTrigger = New-ScheduledTaskTrigger -Daily -At "10:00"
Set-ScheduledTask -TaskName "MyTask" -Trigger $newTrigger

# Enable/Disable
Enable-ScheduledTask -TaskName "MyTask"
Disable-ScheduledTask -TaskName "MyTask"

Delete a Task​

Unregister-ScheduledTask -TaskName "MyTask" -Confirm:$false

Run Manually​

Start-ScheduledTask -TaskName "MyTask"

Management via CMD​

SCHTASKS Command​

:: List tasks
schtasks /query

:: Task details
schtasks /query /tn "TaskName" /v /fo LIST

:: Create a task
schtasks /create /tn "MyTask" /tr "C:\program.exe" /sc daily /st 08:00

:: Run
schtasks /run /tn "MyTask"

:: Delete
schtasks /delete /tn "MyTask" /f

:: Modify
schtasks /change /tn "MyTask" /st 10:00

Common Options​

/sc   Schedule type (minute, hourly, daily, weekly, monthly, once, onstart, onlogon)
/st Start time (HH:MM)
/sd Start date (MM/DD/YYYY)
/ru Run as user
/rp Run as password
/rl Run level (LIMITED, HIGHEST)

Security of Scheduled Tasks​

Risks​

Scheduled tasks are a very common persistence vector used by attackers:

  • Run automatically
  • Can have elevated privileges
  • Often ignored during audits

Security Checks​

# Tasks running as SYSTEM
Get-ScheduledTask | ForEach-Object {
$task = $_
$principal = $task.Principal
if ($principal.UserId -eq "SYSTEM" -or $principal.UserId -like "*SYSTEM*") {
[PSCustomObject]@{
Name = $task.TaskName
Path = $task.TaskPath
User = $principal.UserId
}
}
} | Format-Table

# Tasks with executables in suspicious paths
Get-ScheduledTask | ForEach-Object {
$actions = $_.Actions
foreach ($action in $actions) {
if ($action.Execute -like "*\Temp\*" -or
$action.Execute -like "*\AppData\*" -or
$action.Execute -like "*\Downloads\*") {
[PSCustomObject]@{
Task = $_.TaskName
Execute = $action.Execute
Arguments = $action.Arguments
}
}
}
} | Format-Table

# Tasks created recently (last 24h)
Get-ScheduledTask | Where-Object {
$_.Date -gt (Get-Date).AddDays(-1)
}

Indicators of Compromise (IOC)​

IndicatorDescription
Task with random namePossible malware
Executable in TempSuspicious
Encoded PowerShellVery suspicious
Hidden taskEvasion technique
SYSTEM running user scriptsHigh risk

Hardening​

  1. Regularly audit scheduled tasks
  2. Limit who can create tasks
  3. Monitor task creations (Event ID 4698)
  4. Use dedicated service accounts

Persistence via Scheduled Tasks​

Attack Technique (to know for defense)​

# Example of malicious persistence (DO NOT USE)
# Shows how attackers proceed

$action = New-ScheduledTaskAction `
-Execute "powershell.exe" `
-Argument "-WindowStyle Hidden -EncodedCommand <base64>"

$trigger = New-ScheduledTaskTrigger -AtStartup

Register-ScheduledTask `
-TaskName "WindowsUpdateHelper" `
-Action $action `
-Trigger $trigger `
-Principal (New-ScheduledTaskPrincipal -UserId "SYSTEM" -RunLevel Highest) `
-Force

Detection​

# Script to detect suspicious tasks
$suspicious = @()

Get-ScheduledTask | ForEach-Object {
$task = $_
$isSuspicious = $false
$reasons = @()

# Check actions
foreach ($action in $task.Actions) {
# PowerShell with encoded commands
if ($action.Arguments -match "-[Ee]ncoded") {
$isSuspicious = $true
$reasons += "EncodedCommand"
}

# Executables in suspicious paths
if ($action.Execute -match "\\(Temp|Downloads|AppData)\\") {
$isSuspicious = $true
$reasons += "Suspicious path"
}

# Names mimicking Windows services
if ($task.TaskName -match "^(WindowsUpdate|MicrosoftEdge|Chrome|Adobe).*\d{4,}") {
$isSuspicious = $true
$reasons += "Suspicious name"
}
}

if ($isSuspicious) {
$suspicious += [PSCustomObject]@{
TaskName = $task.TaskName
TaskPath = $task.TaskPath
Reasons = ($reasons -join ", ")
}
}
}

if ($suspicious) {
Write-Host "Suspicious tasks detected:" -ForegroundColor Red
$suspicious | Format-Table -AutoSize
} else {
Write-Host "No suspicious tasks detected." -ForegroundColor Green
}

Logging​

Event IDs​

Event IDLogDescription
4698SecurityTask created
4699SecurityTask deleted
4700SecurityTask enabled
4701SecurityTask disabled
4702SecurityTask updated
106TaskScheduler/OperationalTask registered
140TaskScheduler/OperationalTask updated
141TaskScheduler/OperationalTask deleted
200TaskScheduler/OperationalAction started
201TaskScheduler/OperationalAction completed

Enable Auditing​

# Enable auditing of scheduled tasks (GPO or command)
auditpol /set /subcategory:"Other Object Access Events" /success:enable /failure:enable

View Logs​

# Task creation events
Get-WinEvent -FilterHashtable @{
LogName = 'Security'
ID = 4698
} -MaxEvents 10 | Format-Table TimeCreated, Message -Wrap

# Operational logs
Get-WinEvent -LogName 'Microsoft-Windows-TaskScheduler/Operational' -MaxEvents 20

Exercises​

  1. Create a task that runs a PowerShell script every day at 9 AM
  2. Create a task that starts at system boot
  3. Audit existing tasks to identify suspicious ones
  4. View task creation logs from the last 7 days

References​