我正在编写一个支持ShouldProcess
的Powershell cmdlet。我想要一个"动态"值,而不是一个固定的ConfirmImpact
值,它取决于传递给 cmdlet 的参数的值。让我用一个例子来说明。
让我们假装我是网络托管服务提供商。我有很多网站,每个网站都属于以下类别之一,按重要性排序:Production
、Test
和Development
。作为托管管理的一部分,我有一个用于销毁网站的Remove-WebSite
cmdlet。以下代码对此进行了说明:
Class WebSite {
[string] $Name
[string] $Category # Can be one of: Production, Test, Development
}
Function Remove-WebSite {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[WebSite] $WebSite
)
Write-Host "$($WebSite.Name) was destroyed"
}
目前,网站未经确认就被销毁。虽然这很方便,但太多的实习生错误地破坏了生产现场,所以我希望通过利用 Powershell 的 ShouldProcess 功能,在Remove-WebSite
cmdlet 上有更多的安全网。
因此,我将SupportsShouldProcess
和ConfirmImpact
值添加到CmdletBinding
属性中。我的 cmdlet 定义变为:
Function Remove-WebSite {
[CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact='High')]
Param(
[Parameter(Mandatory=$true)]
[WebSite] $WebSite
)
if ($PSCmdlet.ShouldProcess("$($WebSite.Category) site $($WebSite.Name)")) {
Write-Host "$($WebSite.Name) was destroyed"
}
}
使用此定义,现在要求调用Remote-Website
cmdlet 的任何人确认他们确实要销毁该站点。现在几乎没有任何生产站点被错误地摧毁,除了Web开发人员抱怨他们的自动化脚本已经停止工作。
我真正想要的是 cmdlet 的ConfirmImpact
值在运行时根据网站类别的重要性而变化 -High
用于生产站点,Medium
用于测试站点,Low
用于开发站点。以下函数定义说明了这一点:
Function CategoryToImpact([string]$Category) {
Switch ($Category) {
'Production' {
[System.Management.Automation.ConfirmImpact]::High
break
}
'Test' {
[System.Management.Automation.ConfirmImpact]::Medium
break
}
'Development' {
[System.Management.Automation.ConfirmImpact]::Low
break
}
default {
[System.Management.Automation.ConfirmImpact]::None
break
}
}
}
Function Remove-WebSite {
[CmdletBinding(SupportsShouldProcess=$true<#,ConfirmImpact="Depends!"#>)]
Param(
[Parameter(Mandatory=$true)]
[WebSite] $WebSite
)
# This doesn't work but I hope it illustrates what I'd *like* to do
#$PSCmdLet.ConfirmImpact = CategoryToImpact($WebSite.Category)
if ($PSCmdlet.ShouldProcess("$($WebSite.Category) site $($WebSite.Name)")) {
Write-Host "$($WebSite.Name) was destroyed"
}
}
假设这是可能的,如何做到这一点?
下面是一个完整的脚本和一些测试代码的粘贴: http://pastebin.com/kuk6HNm6
这不是你想要的(我认为严格来说,这是不可能的),但这可能是一个更好的方法。
不要理ConfirmImpact
,而是提示用户$PSCmdlet.ShouldContinue()
。
根据请求从 Cmdlet 确认(强调我的)中给出的指导:
对于大多数 cmdlet,不必显式指定确认影响。请改用参数的默认设置,即"中"。如果将"确认影响"设置为"高",则默认情况下将确认该操作。为高度破坏性的操作(如重新格式化硬盘卷)保留此设置。
进一步:
大多数 cmdlet 仅使用 ShouldProcess 方法请求确认。但是,在某些情况下可能需要额外的确认。对于这些情况,请使用对 ShouldContinue 方法的调用来补充 ShouldProcess 调用。
。
如果 cmdlet 调用 ShouldContinue 方法,则该 cmdlet 还必须提供强制开关参数。如果用户在调用 cmdlet 时指定 Force,则该 cmdlet 仍应调用 ShouldProcess,但它应绕过对 ShouldContinue 的调用。
鉴于此指导,我提出以下更改:
Function Remove-WebSite {
[CmdletBinding(SupportsShouldProcess=$true)]
Param(
[Parameter(Mandatory=$true)]
[WebSite] $WebSite ,
[Switch] $Force
)
if ($PSCmdlet.ShouldProcess("$($WebSite.Category) site $($WebSite.Name)")) {
$destroy =
$Force -or
$WebSite.Category -ne 'Production' -or
$PSCmdlet.ShouldContinue("Are you sure you want to destroy $($WebSite.Name)?", "Really destroy this?")
if ($destroy) {
Write-Host "$($WebSite.Name) was destroyed"
}
}
}
最简单的解决方案是删除$PSCmdlet.ShouldProcess
方法调用,并根据我们自己的条件有条件地调用$PSCmdlet.ShouldContinue
方法。这样做的问题是我们失去了-WhatIf
功能。正如briantist指出的那样,$PSCmdlet.ShouldContinue
应该与$PSCmdlet.ShouldProcess
一起使用,除了这可能导致多余的确认提示,即用户提示两次,而一次就足够了。
通过实验,我发现通过在CmdletBinding
属性声明中设置ConfirmImpact='None'
,ShouldProcess
不再显示提示,但如果指定了-WhatIf
,则仍返回$false
。因此,可以同时调用ShouldProcess
和ShouldContinue
,并且仍然只向用户显示一个提示。然后,我可以使用自己的逻辑来确定是否调用ShouldContinue
。
这是一个完整的解决方案:
# Represents a website
Class WebSite {
# The name of the web site
[string] $Name
# The category of the website, which can be one of: Production, Test, Development
[string] $Category # Can be one of
<#
Gets the ConfirmImpact level based on Category, as follows:
Category ConfirmImpact
----------- -------------
Production High
Test Medium
Development Low
Default None
#>
[System.Management.Automation.ConfirmImpact] GetImpact() {
Switch ($this.Category) {
'Production' {
return [System.Management.Automation.ConfirmImpact]::High
}
'Test' {
return [System.Management.Automation.ConfirmImpact]::Medium
}
'Development' {
return [System.Management.Automation.ConfirmImpact]::Low
}
}
return [System.Management.Automation.ConfirmImpact]::None
}
# String representation of WebSite
[string] ToString() {
return "$($this.Category) site $($this.Name)"
}
}
<#
.SYNOPSIS
Destroys a WebSite
.DESCRIPTION
The Remove-WebSite cmdlet permanently destroys a website so use with care.
To avoid accidental deletion, the caller will be prompted to confirm the
invocation of the command if the value of $ConfirmPreference is less than
or equal to the Impact level of the WebSite. The Impact level is based
upon the category, as follows:
Category ConfirmImpact
----------- -------------
Production High
Test Medium
Development Low
Default None
.PARAMETER Website
The WebSite to destroy.
.PARAMETER Force
Destroys website without prompt
.PARAMETER Confirm
Require confirmation prompt always regardless of $ConfirmPreference
.PARAMETER WhatIf
Show what would happen if the cmdlet was run. The cmdlet is not run.
#>
Function Remove-WebSite {
# Set ConfirmImpact to 'None' so that ShouldProcess automatically returns
# true without asking for confirmation, regardless of the value of
# $ConfirmPreference.
[CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact='None')]
Param(
[Parameter(Mandatory=$true)]
[WebSite] $WebSite,
[Switch] $Force
)
# Returns true without prompt unless -WhatIf is specified when, in which case
# false is returned without prompt
if ($PSCmdlet.ShouldProcess($WebSite)) {
# Determine whether to continue with the command. Only destroy website if...
$continue =
# ...forced to by Force parameter...
$Force -or
#...or the Impact level of the Website is less than $ConfirmPreference...
$WebSite.GetImpact() -lt $ConfirmPreference -or
#...or the user clicked 'Yes' in ShouldContinue prompt
$PSCmdlet.ShouldContinue("Are you sure you want to destroy $($WebSite)?", $null)
if ($continue) {
Write-Host "$($WebSite.Name) was destroyed"
}
}
}