Skip to content
AgileLogics
5 min read

Scheduling and Unattended Scripts

Prerequisites: Errors: terminating and non-terminating

Scripts that run on a schedule have no one watching. They must be non-interactive, log what they do, and fail loudly.

A robust script skeleton

Set-Content job.ps1 @'
[CmdletBinding()]
param([string]$LogPath = './job.log')

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

function Write-Log {
  param([string]$Message, [string]$Level = 'INFO')
  $line = '{0} [{1}] {2}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Level, $Message
  Add-Content -Path $LogPath -Value $line
}

try {
  Write-Log 'Job started'
  Get-Item ./input.csv | Out-Null
  Write-Log 'Job finished'
  exit 0
} catch {
  Write-Log $_.Exception.Message -Level 'ERROR'
  exit 1
}
'@

./job.ps1
"Exit code: $LASTEXITCODE"
Get-Content job.log | ForEach-Object { $_.Substring(20) }
Exit code: 1
[INFO] Job started
[ERROR] Cannot find path 'C:\demo\input.csv' because it does not exist.
  • $ErrorActionPreference = 'Stop' makes every error terminating, so the catch sees it.
  • A log file records what happened (the timestamps are trimmed from this output).
  • exit 1 returns a non-zero exit code, so the scheduler, CI system, or monitoring can detect the failure.
  • Start-Transcript -Path log.txt is another option: it records everything the script prints.

Windows Task Scheduler

# illustrative
$action  = New-ScheduledTaskAction -Execute 'pwsh.exe' `
  -Argument '-NoProfile -NonInteractive -File C:\Scripts\job.ps1'
$trigger = New-ScheduledTaskTrigger -Daily -At 2am
Register-ScheduledTask -TaskName 'Nightly report' -Action $action -Trigger $trigger `
  -User 'SYSTEM' -RunLevel Highest
  • -NoProfile avoids loading profiles (faster and predictable); -NonInteractive makes any prompt fail instead of hanging forever.
  • Paths in scheduled scripts should be absolute, or built from $PSScriptRoot (the script's own folder), because the working folder isn't your usual one.

Linux: cron and systemd

# crontab -e — run at 02:00 every day
0 2 * * * /usr/bin/pwsh -NoProfile -File /opt/scripts/job.ps1

In the cloud

Azure Automation runbooks and Azure Functions (with a timer trigger) run PowerShell on a schedule without a server; both can use a managed identity instead of stored credentials.