如何在调用其他 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
ShouldTestCaller
从ShouldProcess()
产生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 参数?的主要内容,如果未能解决你的问题,请参考以下文章
是否可以/如何使用某些 cmdlet 停止 powershell?
是否可以/如何使用某些 cmdlet 停止 powershell?
powershell 此代码块将允许您以其他用户身份运行后续cmdlet