Blog

-WhatIf is not a dry run for everything

Shaun Hardneck · · 2 min read

Dark ScriptPreflight cover showing a script run with -WhatIf where a robocopy line still runs.

-WhatIf is the best habit in PowerShell. It's also the most misunderstood. People run a script with -WhatIf, see a tidy list of "What if:" lines, and assume nothing else happened. That's only true if every command in the script honours it.

Where -WhatIf comes from

A cmdlet or advanced function gets -WhatIf and -Confirm when it declares SupportsShouldProcess. Microsoft's docs describe -WhatIf as listing "the changes that the command would make, instead of running the command". It works per command. There's no switch that freezes a whole script.

What it doesn't cover

  • Functions that don't declare it. A home-grown Remove-OldProfiles function without SupportsShouldProcess has no real -WhatIf. Depending on how it's written, passing one either fails or is quietly ignored. Call it from a script you ran with -WhatIf and it does its work for real.
  • Commands without it. Invoke-Sqlcmd has no -WhatIf, so a DELETE inside it runs.
  • Native programs. robocopy /MIR, sc.exe delete and net user /delete have never heard of -WhatIf.
  • Code inside your own function. Even Microsoft's ShouldProcess guide advises against trusting that calls inside a function inherit -WhatIf. It passes it explicitly with -WhatIf:$WhatIfPreference.

Two columns: commands that honour -WhatIf and commands that run for real regardless.

Build a preview that covers the script

Give the script a switch and make doing nothing the default:

PowerShell
[CmdletBinding()]
param([switch]$Apply)

$targets = Import-Csv .\targets.csv
"$($targets.Count) targets"

foreach ($t in $targets) {
    if ($Apply) {
        Disable-ADAccount -Identity $t.SamAccountName -ErrorAction Stop
    } else {
        "Would disable $($t.SamAccountName)"
    }
}

Running it without -Apply is now a real preview, including the parts -WhatIf can't see. For your own functions, declare SupportsShouldProcess and wrap each change in if ($PSCmdlet.ShouldProcess($target)) { ... }. PSScriptAnalyzer flags state-changing functions that don't.

Where a preflight fits

-WhatIf asks the script what it would do, so it has to run the script to find out. A preflight reads the script as text and never runs it, so it also sees the lines -WhatIf would have let through: the native program, the SQL statement, the function without ShouldProcess. Use both. Preflight before the first run, then -WhatIf or your own preview mode on a pilot.

Sources