如何在调用其他 Cmdlet 的 Cmdlet 中支持 PowerShell 的 -WhatIf 和 -Confirm 参数?

Posted

技术标签:

【中文标题】如何在调用其他 Cmdlet 的 Cmdlet 中支持 PowerShell 的 -WhatIf 和 -Confirm 参数?【英文标题】:How do you support PowerShell's -WhatIf & -Confirm parameters in a Cmdlet that calls other Cmdlets? 【发布时间】:2011-11-03 01:29:50 【问题描述】:

我有一个支持 -WhatIf-Confirm 参数的 PowerShell 脚本 cmdlet。

它通过在执行更改之前调用$PSCmdlet.ShouldProcess() 方法来做到这一点。 这按预期工作。

我的问题是我的 Cmdlet 是通过调用其他 Cmdlet 来实现的,而 -WhatIf-Confirm 参数没有传递给我调用的 Cmdlet。

如何将 -WhatIf-Confirm 的值传递给我从 Cmdlet 调用的 Cmdlet?

例如,如果我的 Cmdlet 是 Stop-CompanyXyzServices 并且它使用 Stop-Service 来实现其操作。

如果 -WhatIf 传递给 Stop-CompanyXyzServices 我希望它也传递给 Stop-Service。

这可能吗?

【问题讨论】:

【参考方案1】:

显式传递参数

您可以使用$WhatIfPreference$ConfirmPreference 变量传递-WhatIf-Confirm 参数。以下示例使用parameter splatting 实现了这一点:

if($ConfirmPreference -eq 'Low') $conf = @Confirm = $true

StopService MyService -WhatIf:([bool]$WhatIfPreference.IsPresent) @conf

如果在包含函数上使用了-WhatIf 开关,则$WhatIfPreference.IsPresent 将是True。使用包含函数上的-Confirm 开关临时将$ConfirmPreference 设置为low

隐式传递参数

既然-Confirm-WhatIf临时自动设置了$ConfirmPreference$WhatIfPreference变量,那还需要传递吗?

考虑这个例子:

function ShouldTestCallee 
    [cmdletBinding(SupportsShouldProcess=$true,ConfirmImpact='Medium')] 
    param($test)

    $PSCmdlet.ShouldProcess($env:COMPUTERNAME,"Confirm?")



function ShouldTestCaller 
    [cmdletBinding(SupportsShouldProcess=$true)]
    param($test)

    ShouldTestCallee


$ConfirmPreference = 'High'
ShouldTestCaller
ShouldTestCaller -Confirm

ShouldTestCallerShouldProcess() 产生True

ShouldTestCaller -Confirm 会导致确认提示,即使我没有通过开关。

编辑

@manojlds 的回答让我意识到我的解决方案总是将$ConfirmPreference 设置为“低”或“高”。如果确认首选项为“低”,我已更新我的代码以仅设置 -Confirm 开关。

【讨论】:

+1 为$WhatIfPreference.IsPresent 。不确定这是否可以用于 OP 想要的解决方案。 用它来获得完整的解决方案。看我的回答。以前不知道 $WhatIfPreference,很高兴知道。 @Dan 说他已经有了一个支持-WhatIf-Confirm 参数的脚本。我认为我也不需要编写其他代码。 -Confirm-WhatIf 将被您调用的命令继承。 很好的答案,但这些编辑使结论令人困惑且难以理解。那么,正确的做法是:$ConfirmPreference.IsPresent还是依靠偏好变量的透传机制?【参考方案2】:

经过一番谷歌搜索,我想出了一个很好的解决方案,可以将常用参数传递给调用的命令。您可以使用 @ splatting 运算符传递传递给您的命令的所有参数。例如,如果

Start-Service -Name ServiceAbc @PSBoundParameters

位于脚本的主体中,powershell 会将传递给脚本的所有参数传递给 Start-Service 命令。唯一的问题是,如果你的脚本包含一个 -Name 参数,它也会被传递,PowerShell 会抱怨你包含了两次 -Name 参数。我编写了以下函数来将所有常用参数复制到一个新字典中,然后将其吐出。

function Select-BoundCommonParameters

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)]
        $BoundParameters
    )
    begin
    
        $boundCommonParameters = New-Object -TypeName 'System.Collections.Generic.Dictionary[string, [Object]]'
    
    process
    
        $BoundParameters.GetEnumerator() |
            Where-Object  $_.Key -match 'Debug|ErrorAction|ErrorVariable|WarningAction|WarningVariable|Verbose'  |
            ForEach-Object  $boundCommonParameters.Add($_.Key, $_.Value) 

        $boundCommonParameters
    

最终结果是您将 -Verbose 等参数传递给脚本中调用的命令,它们尊重调用者的意图。

【讨论】:

当我将它插入一些现有代码时,我遇到了一些麻烦 - PSBoundParameters 实际上是 System.Management.Automation.PSBoundParametersDictionary 类型。我通过在数组中构建一个不匹配键的数组来解决这个问题,然后调用 $BoundParameters.Remove() 去除非命令参数,然后在 end 块中返回修改后的 PSBoundParameters。【参考方案3】:

这是基于@Rynant 和@Shay Levy 的答案的完整解决方案:

function Stop-CompanyXyzServices

    [CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact='Medium')]

    Param(
        [Parameter(
            Position=0,
            ValueFromPipeline=$true,
            ValueFromPipelineByPropertyName=$true
        )]      
        [string]$Name
    )

    process
    
        if($PSCmdlet.ShouldProcess($env:COMPUTERNAME,"Stop XYZ services '$Name'"))  
            ActualCmdletProcess
        
        if([bool]$WhatIfPreference.IsPresent)
            ActualCmdletProcess
        
    


function ActualCmdletProcess
# add here the actual logic of your cmdlet, and any call to other cmdlets
Stop-Service $name -WhatIf:([bool]$WhatIfPreference.IsPresent) -Confirm:("Low","Medium" -contains $ConfirmPreference)

我们必须查看-WhatIf 是否也单独传递,以便可以将whatif 传递给各个cmdlet。 ActualCmdletProcess 基本上是一种重构,这样您就不会只为WhatIf 再次调用同一组命令。希望这对某人有所帮助。

【讨论】:

我认为你不应该检查"Low","Medium" -contains $ConfirmPreference。设置-Confirm:$true$ConfirmPreference 设置为“低”,但如果它是“中”,您的代码会将$ConfirmPreference 设置为“低”。但我想知道-Confirm-WhatIF 是否需要通过;查看我的编辑。 @Rynant - 它们必须为每个单独的 cmdlet 和我们正在编写的自定义 cmdlet 完成。所以会有多次确认。 你试过我的例子了吗? ShouldTestCallee 函数根据是否在 ShouldTestCaller 上使用 -Confirm 进行确认,即使我没有传递确认参数。【参考方案4】:

根据@manojlds 评论更新

将 $WhatIf 和 $Confirm 转换为布尔值并将值传递给底层 cmdlet:

function Stop-CompanyXyzServices

    [CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact='High')]

    Param(
        [Parameter(
            Position=0,
            ValueFromPipeline=$true,
            ValueFromPipelineByPropertyName=$true
        )]      
        [string]$Name
    )


    process
    
        if($PSCmdlet.ShouldProcess($env:COMPUTERNAME,"Stop service '$Name'"))
                           
            Stop-Service $name -WhatIf:([bool]$WhatIf) -Confirm:([bool]$confirm)
                               
    

【讨论】:

你确定这有效吗?在 cmdlet 中,我不这么认为。甚至 whatif 输出也仅显示自定义 cmdlet,OP 已经从 ShouldProcess 抱歉,没用。这也是我最初的解决方案,所以我希望它会。两个问题 - 即使您执行-WhatIf,也没有设置 $WhatIf。 @Rynant 对 $WhatIfPreference.IsPresent 的建议确实有效。此外,由于您是在 ShouldProcess 的 if 检查中执行此操作,如果它进入,则无论如何都不会设置 -WhatIf 不知道为什么它不适合你。如果我在没有 WhatIf 或确认的情况下调用该函数,我会得到确认,然后按 Enter 并停止服务。如果我用 WhatIf 调用该函数,我会得到一个 whatif 文本。如果我用 Confirm 调用它,我会得到确认,按 Enter,服务就会停止。 嘿,它是这样工作的,但 OP 要求确认,以及个别 cmdlet 的 whatif。如果它使我的意思和我认为 OP 想要清楚,请参阅我的答案。基本上,当我给出 whatif 时,它应该从我们的 cmdlet 给出 whatif,然后从每个正在使用的 cmdlet 给出 whatif。

以上是关于如何在调用其他 Cmdlet 的 Cmdlet 中支持 PowerShell 的 -WhatIf 和 -Confirm 参数?的主要内容,如果未能解决你的问题,请参考以下文章

从 C# PowerShell cmdlet 返回退出代码

是否可以/如何使用某些 cmdlet 停止 powershell?

是否可以/如何使用某些 cmdlet 停止 powershell?

powershell 此代码块将允许您以其他用户身份运行后续cmdlet

从 C# 调用 Lync server 2010 Powershell cmdlet

powershell 如何查找需要使用的命令