电源外壳 functions are very robust with several features that greatly improves the way users interact with them. One important feature that is often overlooked is -WhatIf and -Confirm support and it is easy to add to your functions. In this article, we will dive deep into how to implement this feature.

This is a really easy feature that you can enable in your functions that provides a safety net for your users that need it. There is 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.

指数

公共og体育

在我们看待实施这些之前 共同og体育,我想快速看看它们是如何使用的。

使用-whativ.

When a command supports the -WhatIf parameter, it allows you to see what the command would have done instead of making changes. It is a good way to test out the impact of a command, especially before you do something destructive.

PS C:\temp> Remove-Item -Path .\myfile1.txt -WhatIf
What if: Performing the operation "Remove File" on target "C:\Temp\myfile1.txt".

If the commands correctly implements ShouldProcess, you should be able to see all the changes that it would have made. Here is an example where using a wildcard would 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".

使用-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"):

在这种情况下,您有多个选项允许您继续,跳过更改或停止脚本。帮助提示将描述这样的每个选项。

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"):

本土化

此提示本地化为PowerShell,因此语言将根据操作系统的语言而更改。这只是PowerShell为您管理的另一件事,所以您无需。

开关og体育

让我们快速的时刻来看看将值传递到交换机og体育的方法。我称之为答案的主要原因是您通常希望将og体育值传递给您呼叫的函数。

第一种方法是可以用于所有og体育的特定og体育语法,但大多数都可以看到它用于交换机og体育。您可以指定冒号以将值附加到og体育。

Remove-Item -Path:* -WhatIf:$true

您可以与变量执行相同的操作。

$DoWhatIf = $true
Remove-Item -Path * -WhatIf:$DoWhatIf

第二种方法是使用Hashtable来平移该值。

$RemoveSplat = @{
    Path = '*'
    WhatIf = $true
}
Remove-Item @RemoveSplat

如果您是Hashtables或Splatting的新手,我还有另一篇关于该封面的文章 你想了解哈希特的一切.

支持和解支产

The first step to enable -WhatIf and -Confirm support is to specify 支持和解支产 in the CmdletBinding of your function.

function Test-ShouldProcess {
    [CmdletBinding(支持和解支产)]
    param()
    Remove-Item .\myfile1.txt
}

By specifying 支持和解支产 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 支持和解支产 automatically creates it for us. When we specify the -WhatIf parameter on Test-ShouldProcess, some things we call will also perform -WhatIf processing.

信任但要验证

There is some danger here trusting everything you call will inherit -WhatIf values. For the rest of the examples, I am going to assume that it does not work and be very explicit when making calls to other commands. I recommend that you do the same.

function Test-ShouldProcess {
    [CmdletBinding(支持和解支产)]
    param()
    Remove-Item .\myfile1.txt -WhatIf:$WhatIf
}

一旦你更好地了解了戏剧中的所有作品,我将重新审视细微差别。

$ pscmdlet.shourco.

The method that allows you to implement 支持和解支产 is $ pscmdlet.shourco.. You call $ pscmdlet.shourco.(...) 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(支持和解支产)]
    param()

    $file = Get-ChildItem './myfile1.txt'
    if($PSCmdlet.ShouldProcess($file.Name)){
        $file.Delete()
    }
}

The call to $ pscmdlet.shourco.($file.name) checks for the -WhatIf (and -Confirm parameter) then handles it accordingly. The -WhatIf will cause 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 will pause the script and prompt the user with the option to continue. It will return $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.shourco. 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".

超负荷

There are a few different overloads for $ pscmdlet.shourco. 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(支持和解支产)]
    param()

    if($PSCmdlet.ShouldProcess('TARGET')){
        # ...
    }
}

这会产生包含功能名称和目标(og体育值)的输出。

What if: Performing the operation "Test-ShouldProcess" on target "TARGET".

指定作为操作的第二个og体育将使用操作值而不是消息中的函数名。

# $ pscmdlet.shourco.('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.shourco.('MESSAGE','TARGET','OPERATION')
What if: MESSAGE

快速og体育参考

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.shourco.('TARGET')
What if: Performing the operation "FUNCTION_NAME" on target "TARGET".

# $ pscmdlet.shourco.('TARGET','OPERATION')
What if: Performing the operation "OPERATION" on target "TARGET".

# $ pscmdlet.shourco.('MESSAGE','TARGET','OPERATION')
What if: MESSAGE

我倾向于使用具有2个og体育的那个。

应该加工致命

We have a fourth overload thats more advanced than the others that allows you to get the reason ShouldProcess was executed. I am only adding this here for completeness because we can just check if $WhatIf is $true instead.

$reason = ''
if($ pscmdlet.shourco.('MESSAGE','TARGET','OPERATION',[ref]$reason)){
    Write-Output "Some Action"
}
$reason

We have to pass the $reason variable into the 4th parameter as a reference variable with [ref] and ShouldProcess will populate $reason with the value None or WhatIf. I didn’t say this was very useful and I have no reason to ever use 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.shourco. call as close to the change as possible.

# general logic and variable work
if ($PSCmdlet.ShouldProcess('TARGET','OPERATION')){
    # Change goes here
}

如果我正在处理一系列物品,我会为每个项目称为它。所以呼叫放在Foreach循环中。

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 ShoudProcess 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 will execute as if you specified -WhatIf. If you set this in your session, all commands will perform -WhatIf execution.

When you call a function with -WhatIf, the value of $ whatifpreference. gets set to $true inside the scope of your function.

确认

Most of my examples are for -WhatIf but everything so far also works with -Confirm to prompt the user. You can set the 确认 of the function to high and it will auto prompt the user as if it was called with -Confirm.

function Test-ShouldProcess {
    [CmdletBinding(
        支持和解支产,
        确认 = '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.

$ Synempreference.

$ Synempreference. is an automatic variable that controls when 确认 will ask you to confirm execution. Here are the possible values for both $ Synempreference. and 确认.

  • High
  • Medium
  • Low
  • None

With these values, you can specify different levels of impact for each function. If you have $ Synempreference. set to a value higher than 确认, then you will not be prompted to confirm execution.

By default, $ Synempreference. is set to High and 确认 is Medium. If you want your function to automatically prompt the user, set your 确认 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 will not prompt even if -Confirm was specified (but it will still give you -WhatIf support).

When calling a function with -Confirm, the value of $ Synempreference. gets set to Low inside the scope of your function.

抑制嵌套的确认提示

The $ Synempreference. can get picked up by functions that you call. This can create scenarios where you add a confirm prompt and the function you call will also prompt 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(支持和解支产)]
    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 will not get passed to a function and when -Confirm will pass 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 $ Synempreference., 确认, -Confirm, $ whatifpreference., and -WhatIf because it will prompt every time it is executed.

At a quick glance, it is easy to confuse ShouldProcess and ShouldContinue. I tend to remember to use ShouldProcess because the parameter is called 支持和解支产 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"
    }
}

这将为我们提供更加基本的提示,较少的选项。

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 will alway prompt 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.

全部同意

This is automatically handled with ShouldProcess but we have to do a little more work for ShouldContinue. There is 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 4 parameters starts to get a little ugly, but I tried to make it look as clean as I could.

实施-Force.

ShouldProcess and ShouldContinue need to implement -Force in very different ways. The trick to these implementations is that ShouldProcess should always get executed, but ShouldContinue should not get executed if -Force is specified.

肩部应该是--Force.

If you set your 确认 to high, the first thing your users are going to try is to suppress it with -Force. Thats 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 确认 section, they actually need to call it like this:

Test-ShouldProcess -Confirm:$false

Not everyone realizes they need to do that and -Confirm:$false will not work for suppressing ShouldContinue. So we should implement -Force for the sanity of our users. Take a look at this full example here:

function Test-ShouldProcess {
    [CmdletBinding(
        支持和解支产,
        确认 = 'High'
    )]
    param(
        [Switch]$Force
    )

    if ($Force -and -not $Confirm){
        $ Synempreference. = 'None'
    }

    if ($PSCmdlet.ShouldProcess('TARGET')){
        Write-Output "Some Action"
    }
}

We add our own -Force switch as a parameter and we will use the $Confirm automatic parameter that is available when adding 支持和解支产 in the CmdletBinding.

[CmdletBinding(
    支持和解支产,
    ConfirmImpact = 'High'
)]
param(
    [Switch]$Force
)

Focusing in on the -Force logic here:

if ($Force -and -not $Confirm){
    $ Synempreference. = 'None'
}

If the user specifies -Force, we want to suppress the confirm prompt unless they also specify -Confirm. This allows a user to force a change but still confirm the change. Then we set $ Synempreference. in the local scope where our call to ShouldProcess discoverers it.

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 will always get executed.

Do not add a check for the $Force value inside the if statement with the ShouldProcess. That is an antipattern for this specific scenario even though thats what I will show you in the next section for 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 will get evaluated first. Writing it this way short circuits the execution of the if statement. If $force is $true, then the ShouldContinue will not get executed.

PS> Test-ShouldContinue -Force
Some Action

We don’t have to worry about -Confirm or -WhatIf in this scenario because they are not supported by ShouldContinue. This is why it needs to be handled differently than ShouldProcess.

范围问题

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 $ Synempreference. to Low in the local scope of the function. When you call another function, calls to ShouldProcess will use those values.

这实际上大部分时间都正常工作。随时您调用内置的cmdlet或在相同范围内的函数,它将起作用。当您从控制台拨打脚本模块中调用脚本或函数时,它也会有效。

它不起作用的一个非常特定的位置是脚本或脚本模块在另一个脚本模块中调用函数时。这可能听起来不像一个大问题,但您将从PSGallery创建或拉动的大多数模块都是脚本模块。

The core issue is that script modules do not inherit the values for $ whatifpreference. or $ Synempreference. (and several others) when called from functions in other script modules.

总结为一般规则的最佳方式是,这对二进制模块有效,并且永远不相信它为脚本模块工作。如果您不确定,要么测试它,要么只是假设它无法正常工作。

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.

我们有一个github rfc(超出脚本模块范围的传播执行偏好)努力解决这个问题,它进入更多细节。

在结束时

I have to look up how to use ShouldProcess every time I need to use it. It took me a long time to be able to distinguish ShouldProcess from ShouldContinue and 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.

如果您喜欢这篇文章,请使用下面的链接在Twitter上与我分享您的想法。我总是喜欢听到来自我内容的人的人。