Best Practices for Production Scripts
Prerequisites: Building production modules
A checklist that pulls the course together — use it to review scripts before they run unattended or are shared with a team.
Structure
- Put reusable logic in advanced functions inside modules; keep scripts as thin entry points.
- One function, one job.
Verb-Nounnames with approved verbs. #Requires -Version 7.4and#Requires -Modules Az.Accountsat the top of scripts document and enforce dependencies.
Input and output
- Typed, validated parameters;
Mandatorywhere required; no prompts in automation. - Accept pipeline input where natural; output objects, one per item.
- No
Write-Hostfor data;Write-Verbosefor progress.
Safety
SupportsShouldProcesson anything that changes state; test with-WhatIf.Set-StrictMode -Version Latestand$ErrorActionPreference = 'Stop'.try/catchwith logging and meaningful exit codes;finallyfor cleanup.- No
Invoke-Expressionon input;-LiteralPathfor user-supplied paths. - Secrets from a vault or managed identity — never in code or logs.
Quality
- Comment-based help with examples on every public function.
- Pester tests for logic, run in CI, with PSScriptAnalyzer clean.
- Source control, code review, semantic versioning, and a changelog.
A final example
#Requires -Version 7.0
function Get-StaleFile {
<#
.SYNOPSIS
Finds files not modified for a given number of days.
.EXAMPLE
Get-StaleFile -Path ./logs -Days 30
#>
[CmdletBinding()]
[OutputType([System.IO.FileInfo])]
param(
[Parameter(Mandatory, ValueFromPipeline)]
[ValidateScript({ Test-Path -LiteralPath $_ -PathType Container })]
[string]$Path,
[ValidateRange(1, 3650)]
[int]$Days = 30
)
begin { $cutoff = (Get-Date).AddDays(-$Days) }
process {
Write-Verbose "Scanning $Path for files older than $cutoff"
Get-ChildItem -LiteralPath $Path -File -Recurse |
Where-Object LastWriteTime -lt $cutoff
}
}
New-Item -ItemType Directory logs | Out-Null
'a.log', 'b.log' | ForEach-Object { New-Item "logs/$_" | Out-Null }
(Get-Item logs/a.log).LastWriteTime = (Get-Date).AddDays(-60)
Get-StaleFile -Path logs -Days 30 | Select-Object -ExpandProperty Name
'logs' | Get-StaleFile -Days 90 | Measure-Object | Select-Object -ExpandProperty Count
a.log
0
Help, validation, pipeline input, verbose output, and object output — in about twenty lines.
What's next
Try the Advanced Projects, then read What You Should Know Now — and What's Next for where to go from here.