Chapter 09Lesson 04~145 minutes

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 -Force

The 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.

Automation design: If an operation can be modeled with ShouldProcess, prefer that standard contract. Add ShouldContinue only when a second explicit human decision is truly part of the workflow.

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.

QuestionSafety 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

MistakeRiskCorrection
Declare SupportsShouldProcess but never call ShouldProcessUsers trust a WhatIf contract that the code ignoresGuard each mutation with $PSCmdlet.ShouldProcess()
Mutate before the guardPreview happens after damageMove the state change inside the branch
Invent manual -WhatIf/-Confirm parametersInconsistent with PowerShell conventionsLet CmdletBinding supply them
Use vague target/action textPreview is not operationally usefulName the resource and exact action
Depend on interactive prompts in CIPipeline hangs or failsPrefer ShouldProcess and explicit non-interactive policy

11. Knowledge check

Question 1. What two things are required for a function-level ShouldProcess contract?

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

Next lesson

Document the public contract and refactor functions for team use and API stability

Continue to Lesson 5, where the chapter builds on this command-design foundation with document the public contract and refactor functions for team use and api stability.

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.