Adding -WhatIf Support to your scripts the right way, and how you shouldn't do it

Published September 04, 2014 by FoxDeploy

If you are a prudent sysadmin or consultant, you probably rely on using the -WhatIf command before ever running any serious PowerShell cmdlets in your environment. Since we love it so much, shouldnā€™t we build this very same courtesy into our tools that others will use?

Before I learned how easy it is to add -WhatIf support to my Functions and Scripts, Iā€™d often write something ugly like this:


Function Do-Stuff{

param([switch]$WhatIf)

ForEach($item in $args){ 
  if ($WhatIf){ 
    Write-host "WhatIf: Would be performing \`$Action on $item" -ForegroundColor Cyan 
    }
    ELSE {
      "Actually performing \`$Action on $item" 
      } 
    }
} 

Aside: This is amended from an actual script in production that Iā€™ve created. It was made a LONG time ago.

Yep, itā€™s not very nice and Iā€™m deeply, deeply ashamed.

I mean, it worked:

DontDoWhatIf_thisway_1

Itā€™s almost an example of what not to do! This little snippet has been simplified to be as easy to read as it gets, the thing would get VERY nasty when working with a complex script, as Iā€™d pretty much have to write all of my code twice. Once to actually do the thing, and another to describe what I was doing in this pseudo-WhatIf message.

It wasnā€™t uncommon to see this sort of thing over and over and over. Every single decision tree in my scripts would be made an extra six or seven lines longer because of this forking. Ā In fact, people often called me something that sounded just like ā€˜That Forking PowerShell guyā€™. Ā  :(

BUT NO MORE!

Changing this little sucker to work the right way is easy, and only requires a few edits.

First off, weā€™re going to delete the [switch], and weā€™re going to add in a proper array/parameter to capture our input. Weā€™ll also change line 5 from $args to $Objects, to match.

Function Do-Stuff{ 
  param([string[]]$Objects) 
  ForEach($item in $Objects){ 
    if ($WhatIf){ 
      Write-host "WhatIf: Would be performing \`$Action on $item" -ForegroundColor Cyan } 
    ELSE { 
      "Actually performing \`$Action on $item" 
      } 
    }
} 

We have to dump the Ā use of $args because weā€™ll be telling PowerShell to treat our code as a proper cmdlet, which means we canā€™t use catchalls like $args anymore.

Next, weā€™ll add in the magic sauce:

[CmdletBinding(SupportsShouldProcess=$true)]

. Add this little guy right after the Function Declaration like so:

Function Do-Stuff{ [CmdletBinding(SupportsShouldProcess=$true)] param([string[]]$Objects) 

Finally, weā€™ll gut the existing decision structure, and replace it with this:

 ForEach($item in $Objects){
    if ($pscmdlet.ShouldProcess("$item", "DoStuff")){ 
      "Actually performing \`$Action on $item" 
      } 
 }

In line 2 above, weā€™re specifying the syntax to be displayed to the user. In this case, if the user were to run

Do-Stuff -Objects "hello","sweetie" -WhatIf 

it would say:

What if: Performing the operation "DoStuff" on target "hello". What if: Performing the operation "DoStuff" on target "sweetie".

You also use this same structure to provide messages to the user which are displayed with Warning and ConfirmImpact, but Iā€™ll get into those examples in a later date.

ā€œBut Stephen/FoxDeploy/PowerShell guyā€, you ask, voice raising up a plaintive octaveā€ what IS all of this $pscmdlet stuff anyway? You never defined $pscmdlet anywhere in that code!ā€

Iā€™m happy you asked. $pscmdlet is the guts of what is letting us do a -WhatIf so easily. Well, this and [cmdletbinding].

In simple terms, what weā€™re doing here is replacing our janky $WhatIf switch for proper -WhatIf support, which is provided to us via the $pscmdlet, a special PowerShell variable created at runtime, and that objectā€™s method .ShouldProcess(). We donā€™t need to define or mess with $pscmdlet at all, it is provided to our function to use during execution because we told PowerShell to treat this like real compiled code (think the built-in cmdlets).

See, adding that [CmdletBinding] declaration to our function told PowerShell that we wanted to treat this code as a default cmdlet, which exposes a lot of additional functionality. For instance, adding CmdletBinding lets us specify all sorts of additional tools our script can use, from ConfirmImpact to Paging, to WhatIf support. You can read more about CmdletBinding in the built in help system.

Get-help about_Functions_CmdletBindingAttribute 

Some of this stuff is hard to approach, which is why a deep-dive into what Cmdlet Binding is and what it offers is coming in a future post.

As for $PSCmdlet itself, weā€™re essentially peering under the covers into the PowerShell runspace here with this variable. Itā€™s a bit confusing, but for now, know that you canā€™t use $pscmdlet.ShouldProcess() without adding [CmdletBinding] to your code.

Here is the completed sample of how to use -WhatIf


Function Do-Stuff{ 
  [CmdletBinding(SupportsShouldProcess=$true)] param([string[]]$Objects)

ForEach($item in $Objects){ 
  if ($pscmdlet.ShouldProcess("$item", "DoStuff")){ 
    "Actually performing `$Action on $item" 
  }
} 
} 

And the final result:

DontDoWhatIf_thisway_2

I hope you liked this shallow dive into how-to -WhatIf and how not to do it!


Microsoft MVP

Five time Microsoft MVP, and now I work for the mothership


Need Help?

Get help much faster on our new dedicated Subreddit!

depicts a crowd of people in a night club with colored lights and says 'join the foxdeploy subrreddit today'


Blog Series
series_sml_IntroToDsc
series_sml_PowerShellGUI series_sml_IntroToRaspberryPi Programming series_sml_IntroToWindows Remote Management Series The Logo for System Center Configuration Manager is displayed here Depicts a road sign saying 'Learning PowerShell Autocomplete'




Blog Stats