We hope you are enjoying this experiment in community blogging. In today's contribution I want to demonstrate how you can add support for WhatIf and Confirm to your advanced PowerShell functions. It is actually quite easy, especially if your function is simply calling other PowerShell commands that already support –Whatif and –Confirm. The recommended best practice is that if your function will do anything that changes something, it should support these parameters. Here's how.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
In your function you will need to use the cmdletbinding attribute and specify SupportsShouldProcess.
[cmdletbinding(SupportsShouldProcess)]
Beginning with PowerShell 3.0 this is all you need but you will see scripters explicitly setting this to $True.
[cmdletbinding(SupportsShouldProcess=$True)]
That's fine, although personally I find it redundant. If SupportsShouldProcess is listed then by default it is True. There is no need to explicitly set this to $False. Simply omit it. When you add this attribute, you will automatically get the –WhatIf and –Confirm parameters. The best part is that if your function is simply calling PowerShell cmdlets that already support –WhatIf, they will automatically inherit this setting. Here's a sample function.
#requires –version 4.0 Function Remove-TempFile { [cmdletbinding(SupportsShouldProcess)] Param( [Parameter(Position=0)] [ValidateScript({Test-Path $_})] [string]$Path = $env:temp ) #get last bootup time $LastBoot = (Get-CimInstance -ClassName Win32_OperatingSystem).LastBootUptime Write-Verbose "Finding all files in $path modified before $lastboot" (Get-Childitem -path $path -File).Where({$_.lastWriteTime -le $lastboot}) | Remove-Item } #end function
The function deletes all files from the %TEMP% folder that have a last modified time older than the last boot up time. As you can see in the help, PowerShell added the necessary parameters.
When I run the function with –Whatif it is passed on to Remove-Item.
It is really that easy. I also automatically get support for –Confirm.
Things gets a little trickier when you want to support WhatIf for a function where your commands don't natively recognize SupportsShouldProcess. This would be true of any .NET static method or even a command line tool you might be running, to name a few examples. To add your own support you need to invoke the built-in $PSCmdlet object and its ShouldProcess() method. Here's a simple example.
Function Set-Folder { [cmdletbinding(SupportsShouldProcess)] Param( [Parameter(Position=0, ValueFromPipeline, ValueFromPipelineByPropertyName)] [Alias("pspath")] [ValidateScript({Test-Path $_})] [string]$Path=".") Process { $Path = (Resolve-Path -Path $Path).ProviderPath if ($PSCmdlet.ShouldProcess($Path)) { #do the action $Path.ToUpper() } } #Process } #end function
This function hypothetically is going to perform some action on a folder and I'm simply displaying the folder name in upper case. The important part is the If statement. This is the bare minimum that you need. If you specify –WhatIf you'll be prompted.
The operation will be the name of your script or function. The target is the ShouldProcess parameter value which in my example is the path. But you can provide more specific information by specifying ShouldProcess parameters for the target and action. Here's a revised function.
Function Set-Folder2 { [cmdletbinding(SupportsShouldProcess)] Param( [Parameter(Position=0, ValueFromPipeline, ValueFromPipelineByPropertyName)] [Alias("pspath")] [ValidateScript({Test-Path $_})] [string]$Path=".") Process { $Path = (Resolve-Path -Path $Path).ProviderPath if ($PSCmdlet.ShouldProcess($Path,"Updating")) { #do the action $Path.ToUpper() } } #Process } #end function
You must have the code for ShouldProcess otherwise even if you set the cmdletbinding attribute, PowerShell won't know which commands need WhatIf. You can also have as many ShouldProcess statements as you need.
When it comes to confirmation, things get a little trickier and it might depend on what you really need. As you saw above, any cmdlet that supports –Confirm should automatically inherit the setting. This works because there is another cmdletbinding attribute called ConfirmImpact which has a default value of Medium. Other options are Low and High. My first function could also have been written like this:
[cmdletbinding(SupportsShouldProcess,ConfirmImpact="medium ")]
Confirmation happens by comparing the value of ConfirmImpact with the built-in $ConfirmPreference variable which has a default value of High. If the value of $ConfirmPreference is equal to or greater than ConfirmImpact, PowerShell will prompt for confirmation. Let's test this out.
Function Set-Folder6 { [cmdletbinding(SupportsShouldProcess,ConfirmImpact="High")] Param( [Parameter(Position=0, ValueFromPipeline, ValueFromPipelineByPropertyName)] [Alias("pspath")] [ValidateScript({Test-Path $_})] [string]$Path="." ) Begin { Write-Verbose "Starting $($MyInvocation.Mycommand)" } #begin Process { $Path = (Resolve-Path -Path $Path).ProviderPath Write-Verbose "Processing $path" if ($PSCmdlet.ShouldProcess($Path,"Updating")) { #do the action $Path.ToUpper() } #ShouldProcess } #Process End { Write-Verbose "Ending $($MyInvocation.Mycommand)" } #end } #end function
Notice that I am also using for WhatIf. In this function the ConfirmImpact is set to high which means PowerShell will always prompt.
If I edit the function and change to ConfirmImpact to Medium or Low, then PowerShell will only confirm if I ask.
You don't have to specify anything for cmdletbinding. If you know you always want confirmation you can do something like this:
Function Set-Folder4 { [cmdletbinding()] Param( [Parameter(Position=0, ValueFromPipeline, ValueFromPipelineByPropertyName)] [Alias("pspath")] [ValidateScript({Test-Path $_})] [string]$Path=".", [switch]$Force ) Process { $Path = (Resolve-Path -Path $Path).ProviderPath Write-Verbose "Processing $path" if ($Force -OR $PSCmdlet.ShouldContinue("Do you want to continue modifying folder?",$path)) { #do the action $Path.ToUpper() } } #Process } #end function
Notice the use of the ShouldContinue method. When I run this function, PowerShell will always prompt for confirmation.
I also added a switch parameter called Force so that if it is specified, the user is not prompted for confirmation.
The downside to this approach is that help doesn't show anything.
Perhaps in special cases this is what you want. Personally, I think you are better off using the cmdletbinding attributes as I did for my Set-Folder6 example.
Adding support for WhatIf and Confirm doesn't take much effort and it will take your advanced function to the next level.
This post is part of the PowerShell Blogging Week series on Windows PowerShell Advanced Functions, a series of coordinated posts designed to provide a comprehensive view of a particular topic.
Other articles in this series:
- Standard and Advanced Functions by Francois-Xavier Cat
- PowerShell Advanced Functions: Can we build them better? by Mike F. Robbins
- Dynamic Parameters and Parameter Validation by Adam Bertram
- Creating Help and Comments by June Blender
- Try/Catch and Essential Error Handling by Boe Prox
We hope you found our work worth your time.
So, since SupportsShouldProcess is sufficient and doesn’t require the “= $True” part, what about SupportsPaging? Same?
Anything that can be specified like =$True or =$false doesn’t require the explicit assignment. Although there’s not penalty if you do.
So not specifying it is the same as = $false, and specifying it is the same as = $true.
Yes, in v3 and later.