A script that fails loudly on bad input is a minor inconvenience. A script that accepts bad input and does something plausible-looking with it is a genuine problem. Particularly if it’s deleting things, or reporting numbers someone will act on.
PowerShell has a validation system that catches most of this before your code runs at all. It’s declarative, it’s built into param(), and it’s underused.
Quick Facts
- Validation attributes run before your script body, so bad input never reaches your logic.
[ValidateSet()]gives you tab-completion for free. A usability win as much as a safety one.[Parameter(Mandatory)]prompts rather than failing, which is right interactively and wrong in a scheduled task.SupportsShouldProcessgives you-WhatIfand-Confirmwithout writing them yourself.- Validate at the boundary. Once input is inside your script, trust it.
The attributes worth knowing
| Attribute | What it enforces |
[ValidateNotNullOrEmpty()] | Value isn’t null, empty string or empty array |
[ValidateSet('A','B')] | One of a fixed list, and enables tab completion |
[ValidateRange(1,100)] | A number within bounds |
[ValidatePattern('^SRV\d{3}$')] | Matches a regular expression |
[ValidateScript({...})] | Any arbitrary test you can express in code |
[ValidateCount(1,10)] | Array has between N and M elements |
[ValidateLength(3,15)] | String length within bounds |
What it looks like in practice
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string[]]$ComputerName,
[ValidateRange(1,365)]
[int]$DaysOld = 30,
[ValidateSet('Log','Tmp','Both')]
[string]$FileType = 'Log',
[ValidateScript({
if (Test-Path $_) { $true }
else { throw "Path not found: $_" }
})]
[string]$LogPath = 'C:\Logs'
)
Every one of those failures now happens before a single line of your logic executes, with a clear message naming the offending parameter.
Write real messages in ValidateScript
The default failure message from ValidateScript is close to useless. It just echoes the failing expression. Throwing your own message is worth the extra line:
# Unhelpful
[ValidateScript({ Test-Path $_ })]
# Helpful
[ValidateScript({
if (-not (Test-Path $_)) { throw "LogPath '$_' does not exist or is not accessible." }
$true
})]
Mandatory is not always what you want
[Parameter(Mandatory)] prompts the user when the value is missing. Interactively that’s helpful. In a scheduled task it means the script sits there forever, waiting for input that will never come, and it looks like a hang rather than an error.
For anything unattended, prefer a sensible default, or validate explicitly and exit with a clear message:
if (-not $ComputerName) {
Write-Error 'ComputerName is required when running unattended.'
exit 1
}
Getting -WhatIf for free
Add SupportsShouldProcess to [CmdletBinding()] and wrap destructive operations in $PSCmdlet.ShouldProcess(). You get -WhatIf and -Confirm with no further work:
[CmdletBinding(SupportsShouldProcess)]
param([string]$Path)
foreach ($file in Get-ChildItem $Path) {
if ($PSCmdlet.ShouldProcess($file.FullName, 'Delete')) {
Remove-Item $file.FullName
}
}
Run it with -WhatIf and it lists exactly what it would delete without touching anything. On any script that removes, stops or overwrites, this should be non-negotiable.
A rule of thumb
- Validate at the edge. Everything entering the script gets checked once, at the parameter block.
- Don’t re-validate inside. If the parameter block did its job, trust the value.
- Prefer declarative attributes over
ifchecks. They self-document and appear inGet-Help. - Fail early and specifically. “ThresholdPercent must be 1-100” beats “an error occurred”.
Glossary
| Term | What it means |
| Validation attribute | A declaration in param() that PowerShell enforces before the script body runs. |
SupportsShouldProcess | A CmdletBinding option that adds -WhatIf and -Confirm. |
$PSCmdlet | An automatic variable exposing runtime facilities, including ShouldProcess(). |
| Advanced function | A function or script using [CmdletBinding()], gaining common parameters. |
| Common parameters | -Verbose, -Debug, -ErrorAction and friends, added automatically. |
Frequently asked questions
Does validation slow my script down?
Not measurably. Attributes are evaluated once as parameters bind. ValidateScript runs your code, so keep it light. Checking a path is fine, querying Active Directory is not.
Can I validate one parameter against another?
Not with attributes, which see parameters individually. Cross-parameter rules go in a begin block, or use parameter sets to make invalid combinations impossible to express.
Why does my scheduled script hang instead of failing?
Almost certainly a Mandatory parameter prompting for input nobody is there to give. Use defaults or explicit checks for anything unattended.
What’s the difference between ValidateSet and an enum?
ValidateSet is simpler and gives tab completion immediately. A custom enum is worth it when the same set is reused across many scripts.
Should every script support -WhatIf?
Every script that changes something, yes. Read-only reporting scripts don’t need it, and adding it there is just noise.
Gear We Recommend
Testing scripts is easier with a dedicated admin machine set up right. Here’s the kit we use.
Browse our Admin Machine picks on AmazonAs an Amazon Associate, TechyGeeksHome earns from qualifying purchases
Disclosure: this post may contain affiliate links. If you buy through one of them, we may earn a small commission at no extra cost to you. We only recommend products we’ve tested or genuinely rate.
Discover more from TechyGeeksHome
Subscribe to get the latest posts sent to your email.