具有'dynamic'确认影响属性设置的Powershell Cmdlet



我正在编写一个支持ShouldProcess的Powershell cmdlet。我想要一个"动态"值,而不是一个固定的ConfirmImpact值,它取决于传递给 cmdlet 的参数的值。让我用一个例子来说明。

让我们假装我是网络托管服务提供商。我有很多网站,每个网站都属于以下类别之一,按重要性排序:ProductionTestDevelopment。作为托管管理的一部分,我有一个用于销毁网站的Remove-WebSitecmdlet。以下代码对此进行了说明:

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-WebSitecmdlet 上有更多的安全网。

因此,我将SupportsShouldProcessConfirmImpact值添加到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-Websitecmdlet 的任何人确认他们确实要销毁该站点。现在几乎没有任何生产站点被错误地摧毁,除了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。因此,可以同时调用ShouldProcessShouldContinue,并且仍然只向用户显示一个提示。然后,我可以使用自己的逻辑来确定是否调用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"
}
}
}

最新更新