当前位置:网站首页 > 更多 > 玩电脑 > 正文

[玩转系统] 您想了解的有关 ShouldProcess 的所有信息

作者:精品下载站 日期:2024-12-14 03:04:03 浏览:13 分类:玩电脑

您想了解的有关 ShouldProcess 的所有信息


PowerShell 函数具有多项功能,可以极大地改善用户与其交互的方式。经常被忽视的一个重要功能是 -WhatIf-Confirm 支持,并且可以轻松添加到您的函数中。在本文中,我们将深入探讨如何实现此功能。

笔记

本文的原始版本出现在@KevinMarquette 撰写的博客上。 PowerShell 团队感谢 Kevin 与我们分享这些内容。请查看他的博客:PowerShellExplained.com。

这是一个简单的功能,您可以在函数中启用它,为需要它的用户提供安全网。没有什么比第一次运行一个你知道可能很危险的命令更可怕的了。使用 -WhatIf 运行它的选项可以产生很大的不同。

CommonParameters

在我们考虑实现这些通用参数之前,我想快速了解一下它们的使用方式。

使用-WhatIf

当命令支持 -WhatIf 参数时,您可以查看该命令将执行的操作,而不用进行更改。这是测试命令影响的好方法,尤其是在执行破坏性操作之前。

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".

如果该命令正确实现了 ShouldProcess,它应该向您显示它会进行的所有更改。以下是使用通配符删除多个文件的示例。

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".

使用-确认

支持 -WhatIf 的命令也支持 -Confirm。这使您有机会在执行操作之前确认该操作。

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 为您管理的另一件事。

开关参数

让我们快速了解一下将值传递给 switch 参数的方法。我指出这一点的主要原因是您经常希望将参数值传递给您调用的函数。

第一种方法是特定的参数语法,可用于所有参数,但您通常会看到它用于开关参数。您指定一个冒号将值附加到参数。

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

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

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

第二种方法是使用哈希表来展开值。

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

如果您不熟悉哈希表或 splatting,我有另一篇文章涵盖了您想了解的有关哈希表的所有内容。

支持应处理

启用 -WhatIf-Confirm 支持的第一步是在函数的 CmdletBinding 中指定 SupportsShouldProcess

function Test-ShouldProcess {
    [CmdletBinding(SupportsShouldProcess)]
    param()
    Remove-Item .\myfile1.txt
}

通过以这种方式指定 SupportsShouldProcess,我们现在可以使用 -WhatIf(或 -Confirm)调用我们的函数。

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

请注意,我没有创建名为 -WhatIf 的参数。指定 SupportsShouldProcess 会自动为我们创建它。当我们在Test-ShouldProcess上指定-WhatIf参数时,我们调用的一些东西也会执行-WhatIf处理。

信任但要验证

相信您调用的所有内容都继承 -WhatIf 值存在一些危险。对于其余示例,我将假设它不起作用,并且在调用其他命令时非常明确。我建议您也这样做。

function Test-ShouldProcess {
    [CmdletBinding(SupportsShouldProcess)]
    param()
    Remove-Item .\myfile1.txt -WhatIf:$WhatIfPreference
}

一旦你对游戏中的所有部分有了更好的理解,我将在稍后重新讨论这些细微差别。

$PSCmdlet.ShouldProcess

允许您实现 SupportsShouldProcess 的方法是 $PSCmdlet.ShouldProcess。您可以调用 $PSCmdlet.ShouldProcess(...) 来查看是否应该处理某些逻辑,然后 PowerShell 会处理其余的事情。让我们从一个例子开始:

function Test-ShouldProcess {
    [CmdletBinding(SupportsShouldProcess)]
    param()

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

$PSCmdlet.ShouldProcess($file.name) 的调用会检查 -WhatIf(和 -Confirm 参数),然后进行相应处理。 -WhatIf 导致 ShouldProcess 输出更改的描述并返回 $false

PS> Test-ShouldProcess -WhatIf
What if: Performing the operation "Test-ShouldProcess" on target "myfile1.txt".

使用 -Confirm 的调用会暂停脚本并提示用户选择继续。如果用户选择 Y,则返回 $true

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

$PSCmdlet.ShouldProcess 的一个很棒的功能是它兼作详细输出。在实现 ShouldProcess 时,我经常依赖于此。

PS> Test-ShouldProcess -Verbose
VERBOSE: Performing the operation "Test-ShouldProcess" on target "myfile1.txt".

重载

$PSCmdlet.ShouldProcess 有一些不同的重载,具有不同的参数用于自定义消息传递。我们已经在上面的示例中看到了第一个。让我们仔细看看它。

function Test-ShouldProcess {
    [CmdletBinding(SupportsShouldProcess)]
    param()

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

这会生成包含函数名称和目标(参数值)的输出。

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

指定第二个参数作为操作使用操作值而不是消息中的函数名称。

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

下一个选项是指定三个参数以完全自定义消息。当使用三个参数时,第一个参数是整个消息。后两个参数仍然在 -Confirm 消息输出中使用。

## $PSCmdlet.ShouldProcess('MESSAGE','TARGET','OPERATION')
What if: MESSAGE

快速参数参考

以防万一您来到这里只是为了弄清楚应该使用哪些参数,这里有一个快速参考,显示参数如何在不同的 -WhatIf 场景中更改消息。

## $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

我倾向于使用带有两个参数的那个。

应处理原因

我们有第四个重载,它比其他重载更先进。它允许您获取执行 ShouldProcess 的原因。我只是为了完整性才在此处添加此内容,因为我们只需检查 $WhatIfPreference 是否为 $true 即可。

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

我们必须使用 [ref]$reason 变量作为引用变量传递到第四个参数。 ShouldProcess 使用值 NoneWhatIf 填充 $reason。我并没有说这很有用,我也没有理由使用它。

放置在哪里

您可以使用 ShouldProcess 来使脚本更安全。因此,当您的脚本进行更改时,您可以使用它。我喜欢将 $PSCmdlet.ShouldProcess 调用放置在尽可能靠近更改的地方。

## 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
    }
}

我将 ShouldProcess 紧密地放在更改周围的原因是,我希望在指定 -WhatIf 时执行尽可能多的代码。我希望尽可能运行设置和验证,以便用户可以看到这些错误。

我也喜欢在我的 Pester 测试中使用它来验证我的项目。如果我有一段难以在 pester 中模拟的逻辑,我通常可以将其包装在 ShouldProcess 中,并在测试中使用 -WhatIf 调用它。测试一些代码总比不测试任何代码要好。

$WhatIfPreference

我们的第一个偏好变量是$WhatIfPreference。默认情况下这是$false。如果您将其设置为 $true,则您的函数将像您指定 -WhatIf 一样执行。如果您在会话中设置此项,则所有命令都会执行 -WhatIf 执行。

当您使用 -WhatIf 调用函数时,$WhatIfPreference 的值会在函数作用域内设置为 $true

ConfirmImpact

我的大多数示例都是针对 -WhatIf 的,但到目前为止,所有内容都可以使用 -Confirm 来提示用户。您可以将函数的 ConfirmImpact 设置为高,它会提示用户,就像使用 -Confirm 调用它一样。

function Test-ShouldProcess {
    [CmdletBinding(
        SupportsShouldProcess,
        ConfirmImpact = 'High'
    )]
    param()

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

由于影响,对Test-ShouldProcess的调用正在执行-Confirm操作。

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

明显的问题是,现在在其他脚本中使用而不提示用户变得更加困难。在这种情况下,我们可以将 $false 传递给 -Confirm 来抑制提示。

PS> Test-ShouldProcess -Confirm:$false
Some Action

我将在后面的部分中介绍如何添加 -Force 支持。

$ConfirmPreference

$ConfirmPreference 是一个自动变量,用于控制 ConfirmImpact 何时要求您确认执行。以下是 $ConfirmPreferenceConfirmImpact 的可能值。

    High
    Medium
    Low
    None

    使用这些值,您可以为每个功能指定不同的影响级别。如果您将 $ConfirmPreference 设置为高于 ConfirmImpact 的值,则系统不会提示您确认执行。

    默认情况下,$ConfirmPreference 设置为 HighConfirmImpact 设置为 Medium。如果您希望函数自动提示用户,请将 ConfirmImpact 设置为 High。否则,如果其具有破坏性,则将其设置为;如果该命令始终在生产中安全运行,则将其设置为。如果您将其设置为none,即使指定了-Confirm,它也不会提示(但它仍然为您提供-WhatIf支持)。

    使用 -Confirm 调用函数时,$ConfirmPreference 的值在函数范围内设置为 Low

    抑制嵌套的确认提示

    $ConfirmPreference 可以由您调用的函数获取。这可以创建这样的场景:您添加确认提示,并且您调用的函数也会提示用户。

    我倾向于做的是在我已经处理了提示时在调用的命令上指定 -Confirm:$false

    function Test-ShouldProcess {
        [CmdletBinding(SupportsShouldProcess)]
        param()
    
        $file = Get-ChildItem './myfile1.txt'
        if($PSCmdlet.ShouldProcess($file.Name)){
            Remove-Item -Path $file.FullName -Confirm:$false
        }
    }
    

    这让我们回到了之前的警告:何时不将 -WhatIf 传递给函数以及何时将 -Confirm 传递给函数存在细微差别。我保证稍后会再讨论这个问题。

    $PSCmdlet.ShouldContinue

    如果您需要比 ShouldProcess 提供的更多控制,您可以使用 ShouldContinue 直接触发提示。 ShouldContinue 忽略 $ConfirmPreferenceConfirmImpact-Confirm$WhatIfPreference-WhatIf 因为每次执行都会提示。

    乍一看,很容易混淆 ShouldProcessShouldContinue。我倾向于记住使用 ShouldProcess,因为该参数在 CmdletBinding 中称为 SupportsShouldProcess。几乎在所有场景中您都应该使用 ShouldProcess。这就是为什么我首先介绍该方法的原因。

    让我们看一下 ShouldContinue 的实际应用。

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

    ShouldContinue 的最大问题是它要求用户以交互方式运行它,因为它总是提示用户。您应该始终构建可供其他脚本使用的工具。执行此操作的方法是实施 -Force。稍后我会重新审视这个想法。

    是的

    这是由 ShouldProcess 自动处理的,但我们必须为 ShouldContinue 做更多的工作。还有第二个方法重载,我们必须通过引用传入一些值来控制逻辑。

    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]"
            }
        }
    }
    

    我添加了一个 foreach 循环和一个集合来显示它的实际情况。我将 ShouldContinue 调用从 if 语句中取出,以使其更易于阅读。调用具有四个参数的方法开始变得有点难看,但我试图让它看起来尽可能干净。

    执行力

    ShouldProcessShouldContinue 需要以不同的方式实现-Force。这些实现的技巧是 ShouldProcess 应始终执行,但如果指定 -Force,则不应执行 ShouldContinue

    应该处理-强制

    如果您将 ConfirmImpact 设置为 high,您的用户首先要尝试的就是使用 -Force 抑制它。无论如何,这是我做的第一件事。

    Test-ShouldProcess -Force
    Error: Test-ShouldProcess: A parameter cannot be found that matches parameter name 'force'.
    

    如果您还记得 ConfirmImpact 部分,他们实际上需要这样调用它:

    Test-ShouldProcess -Confirm:$false
    

    并不是每个人都意识到他们需要这样做,并且 -Force 不会抑制 ShouldContinue。因此,为了用户的理智,我们应该实现-Force。在这里查看这个完整的示例:

    function Test-ShouldProcess {
        [CmdletBinding(
            SupportsShouldProcess,
            ConfirmImpact = 'High'
        )]
        param(
            [Switch]$Force
        )
    
        if ($Force -and -not $Confirm){
            $ConfirmPreference = 'None'
        }
    
        if ($PSCmdlet.ShouldProcess('TARGET')){
            Write-Output "Some Action"
        }
    }
    

    我们添加自己的 -Force 开关作为参数。在 CmdletBinding 中使用 SupportsShouldProcess 时,会自动添加 -Confirm 参数。

    [CmdletBinding(
        SupportsShouldProcess,
        ConfirmImpact = 'High'
    )]
    param(
        [Switch]$Force
    )
    

    这里重点关注 -Force 逻辑:

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

    如果用户指定-Force,我们希望抑制确认提示,除非他们也指定-Confirm。这允许用户强制更改但仍确认更改。然后我们在本地范围内设置$ConfirmPreference。现在,使用 -Force 参数临时将 $ConfirmPreference 设置为 none,从而禁用确认提示。

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

    如果有人同时指定 -Force-WhatIf,则 -WhatIf 需要优先。此方法保留了 -WhatIf 处理,因为 ShouldProcess 始终被执行。

    不要使用 ShouldProcessif 语句内添加对 $Force 值的检查。这是针对此特定场景的反模式,尽管我将在下一节的 ShouldContinue 中向您展示这一点。

    应该继续-强制

    这是使用 ShouldContinue 实现 -Force 的正确方法。

    function Test-ShouldContinue {
        [CmdletBinding()]
        param(
            [Switch]$Force
        )
    
        if($Force -or $PSCmdlet.ShouldContinue('TARGET','OPERATION')){
            Write-Output "Some Action"
        }
    }
    

    通过将 $Force 放置在 -or 运算符的左侧,它会首先被求值。以这种方式编写会缩短 if 语句的执行。如果$force$true,则不执行ShouldContinue

    PS> Test-ShouldContinue -Force
    Some Action
    

    在这种情况下,我们不必担心 -Confirm-WhatIf,因为 ShouldContinue 不支持它们。这就是为什么它需要以不同于 ShouldProcess 的方式进行处理。

    范围问题

    使用 -WhatIf-Confirm 应该适用于函数内的所有内容及其调用的所有内容。他们通过在本地范围内将 $WhatIfPreference 设置为 $true 或将 $ConfirmPreference 设置为 Low 来实现此目的。功能。当您调用另一个函数时,对 ShouldProcess 的调用将使用这些值。

    这实际上在大多数情况下都可以正常工作。每当您在同一范围内调用内置 cmdlet 或函数时,它都会起作用。当您从控制台调用脚本或脚本模块中的函数时,它也适用。

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

    核心问题是,当从其他脚本模块中的函数调用时,脚本模块不会继承 $WhatIfPreference$ConfirmPreference (以及其他几个)的值。

    将其总结为一般规则的最佳方法是,这对于二进制模块可以正确工作,并且永远不要相信它对于脚本模块也可以工作。如果您不确定,请对其进行测试或假设它无法正常工作。

    我个人认为这是非常危险的,因为它会创建这样的场景:您向多个单独正常工作的模块添加 -WhatIf 支持,但当它们相互调用时却无法正常工作。

    我们确实有一个 GitHub RFC 正在努力解决这个问题。有关更多详细信息,请参阅将执行首选项传播到脚本模块范围之外。

    结束时

    每次我需要使用它时,我都必须查找如何使用它。我花了很长时间才区分 ShouldProcessShouldContinue。我几乎总是需要查找要使用哪些参数。因此,如果您仍然时不时感到困惑,请不要担心。当您需要时,这篇文章就会出现。我确信我自己会经常参考它。

    如果您喜欢这篇文章,请使用下面的链接在 Twitter 上与我分享您的想法。我总是喜欢听到人们从我的内容中获得价值。

    您需要 登录账户 后才能发表评论

    取消回复欢迎 发表评论:

    关灯