Writing the script is half the work. Running it reliably, on schedule, with good observability — that is what production automation needs. PowerShell has good options at every scale.
Azure Automation Accounts
Azure Automation is the managed service for running PowerShell (and Python) at scale, on a schedule or in response to webhooks. The unit of work is a runbook.
Runbook types
| Type | When to use |
|---|---|
| PowerShell | General scripts, most common type |
| PowerShell 7.2 / 7.4 | Modern PS, cross-platform Az / Graph modules |
| Python 3 | Same model, different language |
| PowerShell Workflow | Legacy; avoid for new work |
| Graphical | Designed in portal; rarely used |
Use PowerShell 7.x for new runbooks.
Creating a runbook
- Create an Automation Account (portal, CLI, Bicep).
- Enable a system-assigned Managed Identity on it.
- Grant the MI permissions to the target resources (e.g., Contributor on a resource group).
- Create a runbook; paste your PowerShell.
- Test pane to dry-run; publish when ready.
- Schedule (cron-like) or wire to a webhook.
A simple runbook
# shutdown-dev-vms.ps1 — runbook content
Param(
[string]$ResourceGroupName = "rg-dev",
[string[]]$ExcludeNames = @()
)
Connect-AzAccount -Identity
$vms = Get-AzVM -ResourceGroupName $ResourceGroupName |
Where-Object Name -notin $ExcludeNames
foreach ($vm in $vms) {
Write-Output "Stopping $($vm.Name)"
Stop-AzVM -Name $vm.Name -ResourceGroupName $vm.ResourceGroupName -Force | Out-Null
}
Schedule for 19:00 UTC every weekday. Cost: pennies per month.
Modules and dependencies
Automation Account has a module catalogue — install Az, Microsoft.Graph, your own modules into the account. Pin versions in production. Recent PS7.2+ supports automatic dependency resolution which makes this simpler.
Hybrid Runbook Workers
Need to run against on-prem or AWS / GCP? Install the Hybrid Runbook Worker on a machine in that environment. The runbook executes on that worker, with full network access to the local environment. Common pattern for cross-cloud automation orchestrated from Azure.
Azure Functions with PowerShell
Functions is a better fit when:
- Trigger is HTTP, Service Bus, Event Grid, Timer, Blob — not just a schedule
- You want sub-second cold start and per-request scaling
- The code is more "API endpoint" than "scheduled batch"
# run.ps1 — HTTP-triggered function
using namespace System.Net
param($Request, $TriggerMetadata)
$name = $Request.Query.Name
if (-not $name) { $name = "world" }
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
StatusCode = [HttpStatusCode]::OK
Body = "Hello, $name!"
})
Functions supports the same Managed Identity, Application Insights logging, and modules story. Pricing: Consumption plan = pay per execution; Premium / Dedicated for predictable load.
Scheduled Tasks (Windows on-prem)
For Windows servers without Azure:
# Register a scheduled task
$action = New-ScheduledTaskAction -Execute "pwsh.exe" -Argument "-File C:\scripts\backup.ps1"
$trigger = New-ScheduledTaskTrigger -Daily -At 2am
$principal = New-ScheduledTaskPrincipal -UserId "NT AUTHORITY\SYSTEM" -RunLevel Highest
Register-ScheduledTask -TaskName "DailyBackup" -Action $action -Trigger $trigger -Principal $principal
Use pwsh.exe (PowerShell 7) rather than powershell.exe (5.1). Log to a file the script controls; do not rely on Task Scheduler's logging.
systemd timers (Linux pwsh)
# /etc/systemd/system/backup.service
[Unit]
Description=Daily backup
[Service]
Type=oneshot
ExecStart=/usr/bin/pwsh /opt/scripts/backup.ps1
# /etc/systemd/system/backup.timer
[Unit]
Description=Run backup daily at 02:00
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
[Install]
WantedBy=timers.target
sudo systemctl enable --now backup.timer
sudo systemctl list-timers backup.timer
journalctl -u backup.service # logs
CI / CD Pipelines
GitHub Actions
name: Weekly Audit
on:
schedule:
- cron: '0 7 * * 1'
workflow_dispatch:
jobs:
audit:
runs-on: ubuntu-latest
permissions:
id-token: write # for OIDC
contents: read
steps:
- uses: actions/checkout@v4
- uses: azure/login@v2
with:
client-id: ${{ secrets.AZ_CLIENT_ID }}
tenant-id: ${{ secrets.AZ_TENANT_ID }}
subscription-id: ${{ secrets.AZ_SUB_ID }}
- shell: pwsh
run: |
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
./scripts/Audit-Resources.ps1 | Tee-Object audit.json
- uses: actions/upload-artifact@v4
with:
name: audit-report
path: audit.json
The azure/login action handles federated identity. Inside shell: pwsh blocks you have full PowerShell 7 with Az pre-installed.
Azure DevOps
Same pattern with AzurePowerShell@5 tasks and Service Connections backed by workload identity federation.
Observability
- Automation Accounts have built-in job history; integrate Application Insights for structured logs.
- Functions auto-integrate with Application Insights — every
Write-Informationand exception flows there. - Scheduled / systemd tasks rely on your own logging — JSON to a file, shipped via Fluent Bit / Filebeat / Azure Monitor agent.
- CI — workflow logs plus artifacts for reports.
Standardise on JSON structured logs (lesson 4's Write-Log pattern) so wherever the script runs, the logs are queryable.
Choosing Where to Run
| Need | Best fit |
|---|---|
| Scheduled Azure resource management | Automation Account runbook |
| Event-driven (webhook, message) | Azure Function |
| HTTP API endpoint | Azure Function |
| On-prem Windows server task | Scheduled Task |
| On-prem Linux server task | systemd timer |
| Periodic audit / report against many resources | GitHub Actions scheduled workflow |
| Cross-cloud automation orchestrated from Azure | Hybrid Runbook Worker |
| Bash CI pipelines using Azure CLI patterns | GitHub Actions + Az CLI / Az PowerShell |
Best Practices for Production Runbooks
- Use Managed Identity wherever possible — no secrets.
- Pin module versions; freeze in a separate test runbook before pushing to prod.
- Idempotent design — a runbook should be safe to re-run.
- Emit structured logs to Application Insights / Log Analytics.
- Alert on runbook failure via Azure Monitor.
- Use a parameter for "dry-run" so you can validate logic safely.
- Store runbook source in Git; deploy via Bicep / Terraform / GitHub Actions, not portal copy-paste.
- Tag the Automation Account and runbook for cost / ownership.
With runbook plumbing covered, the final lesson looks at the modern cross-platform pwsh story and where PowerShell fits in a polyglot DevOps world.