PowerShell Scheduled Task: Create, Edit, and Audit via CIM

PowerShell Scheduled Task: Create, Edit, and Audit via CIM

PowerShell Tips Editor 4 min read
PowerShell Scheduled Task: Create, Edit, and Audit via CIM

Configuring a scheduled task through the Task Scheduler GUI is fine for a single machine, but deploying the same task across 200 servers requires a repeatable, scriptable approach. PowerShell’s ScheduledTasks module — backed by CIM rather than the legacy COM object — gives you full task lifecycle management: create, modify, enable, disable, and audit. You can deploy a standard maintenance task fleet-wide in a single loop and confirm every machine has the correct configuration without opening a single remote desktop session.

Quick Answer

Use New-ScheduledTaskAction, New-ScheduledTaskTrigger, and New-ScheduledTaskPrincipal to build the task components, then register everything with Register-ScheduledTask. Audit existing tasks with Get-ScheduledTask and Get-ScheduledTaskInfo.

Creating a Task with New-ScheduledTaskAction and New-ScheduledTaskTrigger

An action defines what runs; a trigger defines when. Both are separate objects that get passed to Register-ScheduledTask. You can combine multiple triggers on a single task — for example, a daily schedule plus an at logon trigger.

# Define the action: run a PowerShell script
$action = New-ScheduledTaskAction `
    -Execute "pwsh.exe" `
    -Argument "-NonInteractive -ExecutionPolicy Bypass -File C:\Scripts\Maintenance.ps1" `
    -WorkingDirectory "C:\Scripts"

# Define the trigger: daily at 03:00 AM
$trigger = New-ScheduledTaskTrigger -Daily -At "03:00"

# Define optional settings
$settings = New-ScheduledTaskSettingsSet `
    -ExecutionTimeLimit (New-TimeSpan -Hours 1) `
    -RestartCount 2 `
    -RestartInterval (New-TimeSpan -Minutes 5) `
    -StartWhenAvailable

Always set -WorkingDirectory in the action. Without it the task starts in C:\Windows\System32, which causes relative path references inside the script to fail silently.

Setting RunAs Account with New-ScheduledTaskPrincipal

The principal controls which account runs the task and at what privilege level. For tasks that need network access use a domain service account or group-managed service account (gMSA). For tasks that need local administrator rights set -RunLevel Highest.

# Run as a specific service account with highest privilege
$principal = New-ScheduledTaskPrincipal `
    -UserId "CORP\svc-maintenance" `
    -LogonType Password `
    -RunLevel Highest

# Alternatively, run as SYSTEM for local-only tasks
$systemPrincipal = New-ScheduledTaskPrincipal `
    -UserId "NT AUTHORITY\SYSTEM" `
    -LogonType ServiceAccount `
    -RunLevel Highest

Registering and Enabling with Register-ScheduledTask

Bring all the components together with Register-ScheduledTask. Use -Force to overwrite an existing task with the same name — this is the idempotent deployment pattern for fleet management.

Register-ScheduledTask `
    -TaskName    "Corp-NightlyMaintenance" `
    -TaskPath    "\Corporate\" `
    -Action      $action `
    -Trigger     $trigger `
    -Principal   $principal `
    -Settings    $settings `
    -Description "Nightly system maintenance — deployed via PowerShell" `
    -Force

# Confirm it registered successfully
Get-ScheduledTask -TaskName "Corp-NightlyMaintenance" -TaskPath "\Corporate\" |
    Select-Object TaskName, State, TaskPath

Modifying Existing Tasks Without Deleting Them

Use Set-ScheduledTask to update individual components of an existing task. You only need to pass the components that are changing — the others remain untouched. This is important for tasks that are currently in a Running state, as it avoids interrupting an active job.

# Update only the trigger to change the execution time
$newTrigger = New-ScheduledTaskTrigger -Daily -At "02:00"

Set-ScheduledTask `
    -TaskName "Corp-NightlyMaintenance" `
    -TaskPath "\Corporate\" `
    -Trigger  $newTrigger

Auditing All Tasks on Remote Servers with Get-ScheduledTask via CIM

The ScheduledTasks module uses CIM internally. Pass -CimSession objects to run the audit against remote machines without using Invoke-Command.

$servers = "srv-app01", "srv-app02", "srv-db01"
$sessions = New-CimSession -ComputerName $servers

$allTasks = Get-ScheduledTask -CimSession $sessions |
    Get-ScheduledTaskInfo |
    Select-Object TaskName, TaskPath, LastRunTime, LastTaskResult,
                  NextRunTime, PSComputerName

$allTasks | Where-Object { $_.LastTaskResult -ne 0 } |
    Format-Table TaskName, PSComputerName, LastTaskResult -AutoSize

Remove-CimSession $sessions

LastTaskResult of 0 means success; any other value is an error code from the task’s last execution.

Finding Tasks Running as Sensitive Service Accounts

Security audits often require identifying scheduled tasks that run as privileged service accounts. Query across all servers and filter on the Principal property to build this inventory.

Common Errors

  • Task runs but immediately exits: The most common cause is a missing -WorkingDirectory on the action, causing the script to fail to find its dependencies. Always specify a fully qualified -WorkingDirectory and use absolute paths inside the script itself.
  • Highest privilege flag required for UAC elevation: If the task needs to perform operations that require elevation (writing to HKLM, modifying system files), set -RunLevel Highest in New-ScheduledTaskPrincipal. Without it, the task runs in a restricted token even if the account is an administrator.

Related Cmdlets / See Also

Wrapping Up

Managing scheduled tasks entirely in PowerShell makes fleet-wide deployments repeatable and auditable. Build your task components once, register with -Force for idempotency, and use CIM sessions to audit task health across all servers from a single management script.

Send-Item -To