Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
PowerShell functions have several features that greatly improve the way users interact with them.
One important feature that is often overlooked is -WhatIf and -Confirm support and it's easy to
add to your functions. In this article, we dive deep into how to implement this feature.
Note
The original version of this article appeared on the blog written by @KevinMarquette. The PowerShell team thanks Kevin for sharing this content with us. Please check out his blog at PowerShellExplained.com.
This is a simple feature you can enable in your functions to provide a safety net for the users that
need it. There's nothing scarier than running a command that you know can be dangerous for the
first time. The option to run it with -WhatIf can make a big difference.
CommonParameters
Before we look at implementing these common parameters, I want to take a quick look at how they're used.
Using -WhatIf
When a command supports the -WhatIf parameter, it allows you to see what the command would have
done instead of making changes. it's a good way to test out the impact of a command, especially
before you do something destructive.
PS C:\temp> Get-ChildItem
Directory: C:\temp
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 4/19/2021 8:59 AM 0 importantfile.txt
-a---- 4/19/2021 8:58 AM 0 myfile1.txt
-a---- 4/19/2021 8:59 AM 0 myfile2.txt
PS C:\temp> Remove-Item -Path .\myfile1.txt -WhatIf
What if: Performing the operation "Remove File" on target "C:\Temp\myfile1.txt".
If the command correctly implements ShouldProcess, it should show you all the changes that it
would have made. Here is an example using a wildcard to delete multiple files.
PS C:\temp> Remove-Item -Path * -WhatIf
What if: Performing the operation "Remove File" on target "C:\Temp\myfile1.txt".
What if: Performing the operation "Remove File" on target "C:\Temp\myfile2.txt".
What if: Performing the operation "Remove File" on target "C:\Temp\importantfile.txt".
Using -Confirm
Commands that support -WhatIf also support -Confirm. This gives you a chance confirm an action
before performing it.
PS C:\temp> Remove-Item .\myfile1.txt -Confirm
Confirm
Are you sure you want to perform this action?
Performing the operation "Remove File" on target "C:\Temp\myfile1.txt".
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"):
In this case, you have multiple options that allow you to continue, skip a change, or stop the script. The help prompt describes each of those options like this.
Y - Continue with only the next step of the operation.
A - Continue with all the steps of the operation.
N - Skip this operation and proceed with the next operation.
L - Skip this operation and all subsequent operations.
S - Pause the current pipeline and return to the command prompt. Type "exit" to resume the pipeline.
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"):
Localization
This prompt is localized in PowerShell so the language changes based on the language of your operating system. This is one more thing that PowerShell manages for you.
Switch parameters
Let's take quick moment to look at ways to pass a value to a switch parameter. The main reason I call this out is that you often want to pass parameter values to functions you call.
The first approach is a specific parameter syntax that can be used for all parameters but you mostly see it used for switch parameters. You specify a colon to attach a value to the parameter.
Remove-Item -Path:* -WhatIf:$true
You can do the same with a variable.
$DoWhatIf = $true
Remove-Item -Path * -WhatIf:$DoWhatIf
The second approach is to use a hashtable to splat the value.
$RemoveSplat = @{
Path = '*'
WhatIf = $true
}
Remove-Item @RemoveSplat
If you're new to hashtables or splatting, I have another article on that covers everything you wanted to know about hashtables.
SupportsShouldProcess
The first step to enable -WhatIf and -Confirm support is to specify SupportsShouldProcess in
the CmdletBinding of your function.
function Test-ShouldProcess {
[CmdletBinding(SupportsShouldProcess)]
param()
Remove-Item .\myfile1.txt
}
By specifying SupportsShouldProcess in this way, we can now call our function with -WhatIf (or
-Confirm).
PS> Test-ShouldProcess -WhatIf
What if: Performing the operation "Remove File" on target "C:\Temp\myfile1.txt".
Notice that I did not create a parameter called -WhatIf. Specifying SupportsShouldProcess
automatically creates it for us. When we specify the -WhatIf parameter on Test-ShouldProcess,
some things we call also perform -WhatIf processing.
Note
When you use SupportsShouldProcess, PowerShell doesn't add the $WhatIf variable to the
function. You don't need to check the value of $WhatIf because the ShouldProcess() method
takes care of that for you.
Trust but verify
There's some danger here trusting that everything you call inherits -WhatIf values. For the rest
of the examples, I'm going to assume that it doesn't work and be very explicit when making calls
to other commands. I recommend that you do the same.
function Test-ShouldProcess {
[CmdletBinding(SupportsShouldProcess)]
param()
Remove-Item .\myfile1.txt -WhatIf:$WhatIfPreference
}
I will revisit the nuances much later once you have a better understanding of all the pieces in play.
$PSCmdlet.ShouldProcess
The method that allows you to implement SupportsShouldProcess is $PSCmdlet.ShouldProcess. You
call $PSCmdlet.ShouldProcess(...) to see if you should process some logic and PowerShell takes
care of the rest. Let's start with an example:
function Test-ShouldProcess {
[CmdletBinding(SupportsShouldProcess)]
param()
$file = Get-ChildItem './myfile1.txt'
if($PSCmdlet.ShouldProcess($file.Name)){
$file.Delete()
}
}
The call to $PSCmdlet.ShouldProcess($file.Name) checks for the -WhatIf (and -Confirm
parameter) then handles it accordingly. The -WhatIf causes ShouldProcess to output a
description of the change and return $false:
PS> Test-ShouldProcess -WhatIf
What if: Performing the operation "Test-ShouldProcess" on target "myfile1.txt".
A call using -Confirm pauses the script and prompts the user with the option to continue. It
returns $true if the user selected Y.
PS> Test-ShouldProcess -Confirm
Confirm
Are you sure you want to perform this action?
Performing the operation "Test-ShouldProcess" on target "myfile1.txt".
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"):
An awesome feature of $PSCmdlet.ShouldProcess is that it doubles as verbose output. I depend on
this often when implementing ShouldProcess.
PS> Test-ShouldProcess -Verbose
VERBOSE: Performing the operation "Test-ShouldProcess" on target "myfile1.txt".
Overloads
There are a few different overloads for $PSCmdlet.ShouldProcess with different parameters for
customizing the messaging. We already saw the first one in the example above. Let's take a closer
look at it.
function Test-ShouldProcess {
[CmdletBinding(SupportsShouldProcess)]
param()
if($PSCmdlet.ShouldProcess('TARGET')){
# ...
}
}
This produces output that includes both the function name and the target (value of the parameter).
What if: Performing the operation "Test-ShouldProcess" on target "TARGET".
Specifying a second parameter as the operation uses the operation value instead of the function name in the message.
## $PSCmdlet.ShouldProcess('TARGET','OPERATION')
What if: Performing the operation "OPERATION" on target "TARGET".
The next option is to specify three parameters to fully customize the message. When three parameters
are used, the first one is the entire message. The second two parameters are still used in the
-Confirm message output.
## $PSCmdlet.ShouldProcess('MESSAGE','TARGET','OPERATION')
What if: MESSAGE
Quick parameter reference
Just in case you came here only to figure out what parameters you should use, here is a quick
reference showing how the parameters change the message in the different -WhatIf scenarios.
## $PSCmdlet.ShouldProcess('TARGET')
What if: Performing the operation "FUNCTION_NAME" on target "TARGET".
## $PSCmdlet.ShouldProcess('TARGET','OPERATION')
What if: Performing the operation "OPERATION" on target "TARGET".
## $PSCmdlet.ShouldProcess('MESSAGE','TARGET','OPERATION')
What if: MESSAGE
I tend to use the one with two parameters.
ShouldProcessReason
We have a fourth overload that's more advanced than the others. It allows you to get the reason
ShouldProcess was executed. I'm only adding this here for completeness because we can just check
if $WhatIfPreference is $true instead.
$reason = ''
if($PSCmdlet.ShouldProcess('MESSAGE','TARGET','OPERATION',[ref]$reason)){
Write-Output "Some Action"
}
$reason
We have to pass the $reason variable into the fourth parameter as a reference variable with
[ref]. ShouldProcess populates $reason with the value None or WhatIf. I didn't say this
was useful and I have had no reason to ever use it.
Where to place it
You use ShouldProcess to make your scripts safer. So you use it when your scripts are making
changes. I like to place the $PSCmdlet.ShouldProcess call as close to the change as possible.
## general logic and variable work
if ($PSCmdlet.ShouldProcess('TARGET','OPERATION')){
# Change goes here
}
If I'm processing a collection of items, I call it for each item. So the call gets placed inside
the foreach loop.
foreach ($node in $collection){
# general logic and variable work
if ($PSCmdlet.ShouldProcess($node,'OPERATION')){
# Change goes here
}
}
The reason why I place ShouldProcess tightly around the change, is that I want as much code to
execute as possible when -WhatIf is specified. I want the setup and validation to run if possible
so the user gets to see those errors.
I also like to use this in my Pester tests that validate my projects. If I have a piece of logic
that is hard to mock in pester, I can often wrap it in ShouldProcess and call it with -WhatIf in
my tests. It's better to test some of your code than none of it.
$WhatIfPreference
The first preference variable we have is $WhatIfPreference. This is $false by default. If you
set it to $true then your function executes as if you specified -WhatIf. If you set this in
your session, all commands perform -WhatIf execution.
When you call a function with -WhatIf, the value of $WhatIfPreference gets set to $true inside
the scope of your function.
ConfirmImpact
Most of my examples are for -WhatIf but everything so far also works with -Confirm to prompt the
user. You can set the ConfirmImpact of the function to high and it prompts the user as if it was
called with -Confirm.
function Test-ShouldProcess {
[CmdletBinding(
SupportsShouldProcess,
ConfirmImpact = 'High'
)]
param()
if ($PSCmdlet.ShouldProcess('TARGET')){
Write-Output "Some Action"
}
}
This call to Test-ShouldProcess is performing the -Confirm action because of the High impact.
PS> Test-ShouldProcess
Confirm
Are you sure you want to perform this action?
Performing the operation "Test-ShouldProcess" on target "TARGET".
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"): y
Some Action
The obvious issue is that now it's harder to use in other scripts without prompting the user. In
this case, we can pass a $false to -Confirm to suppress the prompt.
PS> Test-ShouldProcess -Confirm:$false
Some Action
I'll cover how to add -Force support in a later section.
$ConfirmPreference
$ConfirmPreference is an automatic variable that controls when ConfirmImpact asks you to
confirm execution. Here are the possible values for both $ConfirmPreference and ConfirmImpact.
HighMediumLowNone
With these values, you can specify different levels of impact for each function. If you have
$ConfirmPreference set to a value higher than ConfirmImpact, then you aren't prompted to
confirm execution.
By default, $ConfirmPreference is set to High and ConfirmImpact is Medium. If you want your
function to automatically prompt the user, set your ConfirmImpact to High. Otherwise set it to
Medium if its destructive and use Low if the command is always safe run in production. If you
set it to none, it doesn't prompt even if -Confirm was specified (but it still gives you
-WhatIf support).
When calling a function with -Confirm, the value of $ConfirmPreference gets set to Low inside
the scope of your function.
Suppressing nested confirm prompts
The $ConfirmPreference can get picked up by functions that you call. This can create scenarios
where you add a confirm prompt and the function you call also prompts the user.
What I tend to do is specify -Confirm:$false on the commands that I call when I have already
handled the prompting.
function Test-ShouldProcess {
[CmdletBinding(SupportsShouldProcess)]
param()
$file = Get-ChildItem './myfile1.txt'
if($PSCmdlet.ShouldProcess($file.Name)){
Remove-Item -Path $file.FullName -Confirm:$false
}
}
This brings us back to an earlier warning: There are nuances as to when -WhatIf is not passed to a
function and when -Confirm passes to a function. I promise I'll get back to this later.
$PSCmdlet.ShouldContinue
If you need more control than ShouldProcess provides, you can trigger the prompt directly with
ShouldContinue. ShouldContinue ignores $ConfirmPreference, ConfirmImpact, -Confirm,
$WhatIfPreference, and -WhatIf because it prompts every time it's executed.
At a quick glance, it's easy to confuse ShouldProcess and ShouldContinue. I tend to remember to
use ShouldProcess because the parameter is called SupportsShouldProcess in the CmdletBinding.
You should use ShouldProcess in almost every scenario. That is why I covered that method first.
Let's take a look at ShouldContinue in action.
function Test-ShouldContinue {
[CmdletBinding()]
param()
if($PSCmdlet.ShouldContinue('TARGET','OPERATION')){
Write-Output "Some Action"
}
}
This provides us a simpler prompt with fewer options.
Test-ShouldContinue
Second
TARGET
[Y] Yes [N] No [S] Suspend [?] Help (default is "Y"):
The biggest issue with ShouldContinue is that it requires the user to run it interactively because
it always prompts the user. You should always be building tools that can be used by other
scripts. The way you do this is by implementing -Force. I'll revisit this idea later.
Yes to all
This is automatically handled with ShouldProcess but we have to do a little more work for
ShouldContinue. There's a second method overload where we have to pass in a few values by
reference to control the logic.
function Test-ShouldContinue {
[CmdletBinding()]
param()
$collection = 1..5
$yesToAll = $false
$noToAll = $false
foreach($target in $collection) {
$continue = $PSCmdlet.ShouldContinue(
"TARGET_$target",
'OPERATION',
[ref]$yesToAll,
[ref]$noToAll
)
if ($continue){
Write-Output "Some Action [$target]"
}
}
}
I added a foreach loop and a collection to show it in action. I pulled the ShouldContinue call
out of the if statement to make it easier to read. Calling a method with four parameters starts to
get a little ugly, but I tried to make it look as clean as I could.
Implementing -Force
ShouldProcess and ShouldContinue need to implement -Force in different ways. The trick to
these implementations is that ShouldProcess should always get executed, but ShouldContinue
should not get executed if -Force is specified.
ShouldProcess -Force
If you set your ConfirmImpact to high, the first thing your users are going to try is to
suppress it with -Force. That's the first thing I do anyway.
Test-ShouldProcess -Force
Error: Test-ShouldProcess: A parameter cannot be found that matches parameter name 'force'.
If you recall from the ConfirmImpact section, they actually need to call it like this:
Test-ShouldProcess -Confirm:$false
Not everyone realizes they need to do that and -Force doesn't suppress ShouldContinue.
So we should implement -Force for the sanity of our users. Take a look at this full example here:
function Test-ShouldProcess {
[CmdletBinding(
SupportsShouldProcess,
ConfirmImpact = 'High'
)]
param(
[switch]$Force
)
if ($Force -and -not $PSBoundParameters.ContainsKey('Confirm')) {
$ConfirmPreference = 'None'
}
if ($PSCmdlet.ShouldProcess('TARGET')) {
Write-Output "Some Action"
}
}
We add our own -Force switch as a parameter. The -Confirm parameter is automatically added when
using SupportsShouldProcess in the CmdletBinding. However, when you use SupportsShouldProcess,
PowerShell doesn't add the $Confirm variable to the function. If you are running in Strict Mode
and try to use the $Confirm variable before it has been defined, you get an error. To avoid the
error you can use $PSBoundParameters to test if the parameter was passed by the user.
if ($Force -and -not $PSBoundParameters.ContainsKey('Confirm')) {
$ConfirmPreference = 'None'
}
If the user specifies -Force we set $ConfirmPreference to None in the local scope. If the user
also specifies -Confirm then ShoudProcess() honors the values of the -Confirm parameter.
if ($PSCmdlet.ShouldProcess('TARGET')){
Write-Output "Some Action"
}
If someone specifies both -Force and -WhatIf, then -WhatIf needs to take priority. This
approach preserves -WhatIf processing because ShouldProcess always gets executed.
Don't add a test for the $Force value inside the if statement with the ShouldProcess. That is
an anti-pattern for this specific scenario even though that's what I show you in the next section
for ShouldContinue.
ShouldContinue -Force
This is the correct way to implement -Force with ShouldContinue.
function Test-ShouldContinue {
[CmdletBinding()]
param(
[switch]$Force
)
if($Force -or $PSCmdlet.ShouldContinue('TARGET','OPERATION')){
Write-Output "Some Action"
}
}
By placing the $Force to the left of the -or operator, it gets evaluated first. Writing it
this way short circuits the execution of the if statement. If $Force is $true, then the
ShouldContinue is not executed.
PS> Test-ShouldContinue -Force
Some Action
We don't have to worry about -Confirm or -WhatIf in this scenario because they're not supported
by ShouldContinue. This is why it needs to be handled differently than ShouldProcess.
Scope issues
Using -WhatIf and -Confirm are supposed to apply to everything inside your functions and
everything they call. They do this by setting $WhatIfPreference to $true or setting
$ConfirmPreference to Low in the local scope of the function. When you call another function,
calls to ShouldProcess use those values.
This actually works correctly most of the time. Anytime you call built-in cmdlet or a function in your same scope, it works. It also works when you call a script or a function in a script module from the console.
The one specific place where it doesn't work is when a script or a script module calls a function in another script module. This may not sound like a big problem, but most of the modules you create or pull from the PSGallery are script modules.
The core issue is that script modules do not inherit the values for $WhatIfPreference or
$ConfirmPreference (and several others) when called from functions in other script modules.
The best way to summarize this as a general rule is that this works correctly for binary modules and never trust it to work for script modules. If you aren't sure, either test it or just assume it doesn't work correctly.
I personally feel this is very dangerous because it creates scenarios where you add -WhatIf
support to multiple modules that work correctly in isolation, but fail to work correctly when they
call each other.
We do have a GitHub RFC working to get this issue fixed. See Propagate execution preferences beyond script module scope for more details.
In closing
I have to look up how to use ShouldProcess every time I need to use it. It took me a long time to
distinguish ShouldProcess from ShouldContinue. I almost always need to look up what parameters
to use. So don't worry if you still get confused from time to time. This article will be here when
you need it. I'm sure I will reference it often myself.
If you liked this post, please share your thoughts with me on Twitter using the link below. I always like hearing from people that get value from my content.