【问题标题】:Powershell Cmdlet with 'dynamic' ConfirmImpact attribute setting具有“动态”ConfirmImpact 属性设置的 Powershell Cmdlet
【发布时间】:2016-09-26 02:32:56
【问题描述】:

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

让我们假设我是一个网络托管服务提供商。我有很多网站,每个网站都属于以下类别之一,按重要性排序:ProductionTestDevelopment。作为托管管理的一部分,我有一个用于破坏网站的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 上多一点安全网。

所以我将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-Website cmdlet 的人现在都被要求确认他们确实想要销毁该站点。现在几乎没有任何生产网站被错误地破坏了,除了网络开发人员抱怨他们的自动化脚本已经停止工作。

我真正想要的是 cmdlet 的 ConfirmImpact 值在运行时根据网站类别的重要性而变化 - High 用于生产站点,Medium 用于测试站点和 @ 987654339@ 用于开发站点。下面的函数定义说明了这一点:

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

【问题讨论】:

  • 你可以用BEGIN{Switch($WebSite){{$_.Catagory -eq "High" -and $PSCmdlet.ShouldProcess("$($WebSite.Category) site $($WebSite.Name)"}{"$($WebSite.Name) was destroyed"};default {"$($WebSite.Name) was destroyed"}}代替你的If声明,这样如果影响不大就不会提示了

标签: powershell cmdlets powershell-5.0


【解决方案1】:

这并不完全符合您的要求(我认为严格来说这是不可能的),但它可能是更好的方法。

别管ConfirmImpact,而是prompt the user with $PSCmdlet.ShouldContinue()

根据Requesting Confirmation from Cmdlets(强调我的)中给出的指导:

对于大多数 cmdlet,您不必显式指定 ConfirmImpact。而是使用参数的默认设置,即“中”。如果将 ConfirmImpact 设置为 High,则默认确认操作。 将此设置保留用于具有高度破坏性的操作,例如重新格式化硬盘卷。

进一步:

大多数 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"
        }
    }
}

【讨论】:

  • 感谢您推荐ShouldContinue 方法。它看起来是一种比ShouldProcess 更直接地向用户请求确认的方法,其中确认消息取决于$ConfirmPreference 变量和ConfirmImpact 属性值。我应该能够使用它来为这个命令实现我自己的确认策略。另一个相关问题:看起来使用ShouldContinue 没有将SupportsShouldProcess设置为$true。即使我不使用ShouldProcess 方法,您认为我还应该将其设置为true 吗?
  • @DanStevens 我链接的页面建议应该使用ShouldContinue 除了ShouldProcess。无论它在技术上是否有效,我认为您仍然应该使用ShouldProcess,以便您可以正确支持-WhatIf 参数,这对于使用您的功能的任何人来说都是一个增值。如果你不使用ShouldProcess(或者你不以任何方式支持-WhatIf),你绝对不应该使用SupportsShouldProcess,因为你会错误地告诉调用者他们可以用@987654341安全地调用它@ 没有影响。
  • 在使用 ShouldProcessShouldContinue 的方式中使用它们的问题是,当我只想要 1 个时,我会得到 2 个单独的确认。我想我可以通过设置来解决这个问题ConfirmImpact='Low'CmdletBinding 属性中。
  • @DanStevens 我在示例中使用它的方式应该只提示用户一次,只要将ConfirmImpact 单独设置(继承Medium)。
  • 如果$ConfirmPreference 设置为默认的High,它只会询问一次,但如果$ConfirmPreferenceMediumLow,则询问用户两次。
【解决方案2】:

最简单的解决方案是去掉$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"
        }
    }
}

【讨论】:

  • 很好的自我回答! :)
猜你喜欢
  • 2022-01-27
  • 1970-01-01
  • 2020-10-04
  • 1970-01-01
  • 1970-01-01
  • 2011-02-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多