SupportsShouldProcess, -WhatIf, -Confirm, and Safe Mutation
Make state-changing PowerShell functions previewable and predictable with SupportsShouldProcess, WhatIf, Confirm, and narrowly scoped mutation.
Learning objectives
- Explain why production mutations need a standard preview and confirmation contract.
- Enable SupportsShouldProcess and call $PSCmdlet.ShouldProcess() correctly.
- Use -WhatIf and -Confirm without declaring custom versions of those parameters.
- Place ShouldProcess directly around the actual state-changing operation.
- Explain ConfirmImpact and distinguish ShouldContinue from ShouldProcess.
- Build and verify a safe idempotent-style removal function in a disposable workspace.
1. State-changing functions need a standard preview and confirmation contract
A command that reads inventory can usually run safely. A command that removes files, restarts services, changes configuration, or updates cloud resources needs stronger operational controls. PowerShell’s ShouldProcess pattern gives callers the familiar -WhatIf and -Confirm experience instead of forcing each function to invent its own dry-run interface.
The contract has two parts: the function declares that it supports ShouldProcess, and the implementation calls $PSCmdlet.ShouldProcess() immediately around the actual mutation. Both parts matter.
2. SupportsShouldProcess exposes -WhatIf and -Confirm
function Remove-LabFile {
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory)]
[string]$Path
)
if ($PSCmdlet.ShouldProcess($Path, 'Remove file')) {
Remove-Item -LiteralPath $Path -Force
}
}Because this is an advanced function and SupportsShouldProcess is enabled, PowerShell supplies -WhatIf and -Confirm. You do not manually declare those parameters.
3. -WhatIf asks the command to describe the mutation without performing it
$root = Join-Path ([System.IO.Path]::GetTempPath()) 'ps-shouldprocess-demo'
New-Item -ItemType Directory -Path $root -Force | Out-Null
$file = Join-Path $root 'demo.txt'
Set-Content -LiteralPath $file -Value 'keep me'
Remove-LabFile -Path $file -WhatIf
Test-Path -LiteralPath $file
Remove-Item -LiteralPath $root -Recurse -ForceThe file should still exist after the WhatIf call. The preview comes from the ShouldProcess contract, not from a hand-written if ($WhatIf) flag.
4. Place ShouldProcess around the real state change, not around unrelated preparation
Read-only validation, path calculation, and object construction can often happen before the decision. The actual mutation belongs inside the ShouldProcess branch.
function Set-LabConfigValue {
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory)][string]$Path,
[Parameter(Mandatory)][string]$Value
)
$resolvedParent = Split-Path -Parent $Path
if (-not (Test-Path -LiteralPath $resolvedParent)) {
throw "Parent directory does not exist: $resolvedParent"
}
if ($PSCmdlet.ShouldProcess($Path, "Set content to '$Value'")) {
Set-Content -LiteralPath $Path -Value $Value -Encoding utf8
}
}The preview remains specific: target plus action. Avoid performing a destructive helper call before ShouldProcess and then claiming the function is safe because the final line is guarded.
5. Declaring SupportsShouldProcess without calling ShouldProcess is not enough
function Remove-BadExample {
[CmdletBinding(SupportsShouldProcess)]
param([string]$Path)
# WRONG: mutation runs regardless of the function-level WhatIf contract.
Remove-Item -LiteralPath $Path -Force
}The attribute adds the parameters and related behavior, but your function must call $PSCmdlet.ShouldProcess() to decide whether its own mutation should execute. Static analysis tools such as PSScriptAnalyzer can help catch this class of command-design problem later in the course.
6. ConfirmImpact participates in confirmation policy
ConfirmImpact classifies how consequential the function’s operation is. Combined with the caller’s confirmation preference, it can influence whether PowerShell prompts by default. Use the levels intentionally rather than marking every command High.
function Remove-LabResource {
[CmdletBinding(SupportsShouldProcess, ConfirmImpact='High')]
param([Parameter(Mandatory)][string]$Name)
if ($PSCmdlet.ShouldProcess($Name, 'Remove simulated resource')) {
[pscustomobject]@{ Name=$Name; Removed=$true }
}
}
# Explicit confirmation behavior is available through -Confirm.
# Use -WhatIf for non-interactive preview demonstrations.For unattended automation, design so callers can use -WhatIf and explicit policy rather than relying on an interactive prompt being available.
7. ShouldContinue is a separate interactive confirmation mechanism
$PSCmdlet.ShouldContinue() is for cases where you intentionally want an additional interactive confirmation question. It is not the same as ShouldProcess and does not provide the standard WhatIf preview contract. Because prompts are difficult or impossible in CI, scheduled jobs, and many remote contexts, use it sparingly.
8. WhatIf is strongest when combined with narrow, idempotent mutation logic
ShouldProcess does not make a function semantically safe by itself. A production command should still target the smallest intended resource, validate inputs, avoid hidden side effects, and converge state when possible. The preview should describe the same mutation that real execution would perform.
| Question | Safety check |
|---|---|
| What is the target? | Pass a clear resource identifier to ShouldProcess |
| What is the action? | Describe the actual change, not “do work” |
| Where is mutation performed? | Inside the ShouldProcess branch |
| Can the command be repeated safely? | Prefer convergence/idempotency where practical |
| Can CI preview it? | Support -WhatIf without custom prompts |
9. Lab: build a safe removal function and prove dry-run behavior
function Remove-LabArtifact {
[CmdletBinding(SupportsShouldProcess, ConfirmImpact='Medium')]
param(
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
[string]$Path
)
process {
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
Write-Verbose "Artifact already absent: $Path"
return
}
if ($PSCmdlet.ShouldProcess($Path, 'Remove artifact')) {
Remove-Item -LiteralPath $Path -Force
}
}
}
$root = Join-Path ([System.IO.Path]::GetTempPath()) 'ps-whatif-lab'
New-Item -ItemType Directory -Path $root -Force | Out-Null
$file = Join-Path $root 'artifact.zip'
Set-Content -LiteralPath $file -Value 'demo'
Remove-LabArtifact -Path $file -WhatIf
Test-Path -LiteralPath $file # True
Remove-LabArtifact -Path $file -Confirm:$false
Test-Path -LiteralPath $file # False
Remove-Item -LiteralPath $root -Recurse -Force- -WhatIf leaves the artifact in place.
- The real invocation removes only the named disposable file.
- Calling again is harmless because absence is treated as converged state.
- No critical system path is touched.
10. ShouldProcess mistakes and why they are dangerous
| Mistake | Risk | Correction |
|---|---|---|
| Declare SupportsShouldProcess but never call ShouldProcess | Users trust a WhatIf contract that the code ignores | Guard each mutation with $PSCmdlet.ShouldProcess() |
| Mutate before the guard | Preview happens after damage | Move the state change inside the branch |
Invent manual -WhatIf/-Confirm parameters | Inconsistent with PowerShell conventions | Let CmdletBinding supply them |
| Use vague target/action text | Preview is not operationally useful | Name the resource and exact action |
| Depend on interactive prompts in CI | Pipeline hangs or fails | Prefer ShouldProcess and explicit non-interactive policy |
11. Knowledge check
Question 1. What two things are required for a function-level ShouldProcess contract?
SupportsShouldProcess and call $PSCmdlet.ShouldProcess() around the mutation.Question 2. What should -WhatIf do?
Question 3. Why should you not manually declare -WhatIf and -Confirm?
Question 4. What does ConfirmImpact represent?
Question 5. Is ShouldContinue a replacement for ShouldProcess?
12. Summary
State-changing functions should expose PowerShell’s standard safety contract. Enable SupportsShouldProcess, then call $PSCmdlet.ShouldProcess(target, action) immediately around each mutation. This gives callers meaningful -WhatIf previews and -Confirm behavior. ConfirmImpact classifies operation impact; ShouldContinue is a separate interactive mechanism and should be used sparingly. Preview support is not a substitute for narrow targeting, validation, and idempotent design—it complements them.
13. Further reading
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this address.