【问题标题】:How NOT to propagate -WhatIf and -Confirm?如何不传播 -WhatIf 和 -Confirm?
【发布时间】:2018-02-21 07:59:27
【问题描述】:

Powershell 的默认行为似乎是将 -WhatIf 和 -Confirm 传播到从使用 -WhatIf 或 -Confirm 调用的函数中调用的所有函数。

但是如果(双关语)我不想要那个怎么办?如果我只想保护在我的系统上执行实际更改的代码而不是我的其余代码怎么办?

这是一个例子:

function Set-Something {
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    [OutputType([string])] 

    Param (
        [Parameter(Mandatory = $false, ValueFromPipeline = $true)]
        [string] $ComputerName = $env:COMPUTERNAME
    )

    Process {
        New-Item -Path "beforeChange" -Type File | Out-Null
        Remove-Item -Path "beforeChange" -ErrorAction SilentlyContinue

        # Here is the bit of code that needs to be "protected"
        if ($PSCmdlet.ShouldProcess($ComputerName, "Do something")) {
            # Actually do something on $ComputerName
            New-Item -Path "inChange" -Type File | Out-Null
            Remove-Item -Path "inChange" -ErrorAction SilentlyContinue
        }

        New-Item -Path "afterChange" -Type File | Out-Null
        Remove-Item -Path "afterChange" -ErrorAction SilentlyContinue
    }
}

"-"*50 + "Set-Something -WhatIf" + "-"*50 
Set-Something -WhatIf
"-"*50 + "Set-Something -Confirm" + "-"*50 
Set-Something -Confirm

Set-Something 函数仅在受保护位中执行系统更改内容。它也使用临时文件,但 New-Item 和 Remove-Item 不应触发 WhatIf 或 Confirm。不幸的是,它不是这样工作的:

--------------------------------------------------Set-Something -WhatIf--------------------------------------------------
What if: Performing the operation "Create File" on target "Destination: C:\Users\GO7\beforeChange".
What if: Performing the operation "Do something" on target "GEOCED".
What if: Performing the operation "Create File" on target "Destination: C:\Users\GO7\afterChange".

我可以将-WhatIf:$false -Confirm:$false 添加到每个支持 ShouldProcess 的函数中,但这会很乏味。

所以到目前为止我所做的是:

  1. 将 $WhatIfPreference 和 $ConfirmPreference 保存在我的函数顶部
  2. 将这些首选项重置为默认值
  3. 在受保护的块之前恢复保存的首选项
  4. 在块内和块外再次重置首选项

像这样:

function Set-Something2 {
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    [OutputType([string])] 

    Param (
        [Parameter(Mandatory = $false, ValueFromPipeline = $true)]
        [string] $ComputerName = $env:COMPUTERNAME
    )

    Begin {
        # Save the ShouldProcess parameters for later use
        $WhatIfPref = $WhatIfPreference
        $ConfirmPref = $ConfirmPreference

        # Reset ShouldProcess parameters to their default value
        $WhatIfPreference = 0
        $ConfirmPreference = 'High'
    }

    Process {
        New-Item -Path "beforeChange" -Type File | Out-Null
        Remove-Item -Path "beforeChange" -ErrorAction SilentlyContinue

        # We restore the previously saved ShouldProcess parameters just before the "protected" bit of code
        $WhatIfPreference = $WhatIfPref
        $ConfirmPreference = $ConfirmPref

        # Here is the bit of code that needs to be "protected"
        if ($PSCmdlet.ShouldProcess($ComputerName, "Do something")) {
            # Inside the protected bit, we reset ShouldProcess parameters to their default value
            $WhatIfPreference = 0
            $ConfirmPreference = 'High'

            # Actually do something on $ComputerName
            New-Item -Path "inChange" -Type File | Out-Null
            Remove-Item -Path "inChange" -ErrorAction SilentlyContinue
        }

        # After the protected bit, we reset ShouldProcess parameters to their default value
        $WhatIfPreference = 0
        $ConfirmPreference = 'High'

        New-Item -Path "afterChange" -Type File | Out-Null
        Remove-Item -Path "afterChange" -ErrorAction SilentlyContinue
    }
}

"-"*50 + "Set-Something2 -WhatIf" + "-"*50 
Set-Something2 -WhatIf
"-"*50 + "Set-Something2 -Confirm" + "-"*50 
Set-Something2 -Confirm

结果如我所愿:

--------------------------------------------------Set-Something2 -WhatIf--------------------------------------------------
What if: Performing the operation "Do something" on target "GEOCED".
--------------------------------------------------Set-Something2 -Confirm--------------------------------------------------

Confirm
Are you sure you want to perform this action?
Performing the operation "Do something" on target "GEOCED".
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"):

但是我应该这样做吗?我觉得它不是很优雅...

【问题讨论】:

  • $ConfirmPreference$WhatIfPreference 是变量,因此它们遵循变量范围规则。 (如果这就是你要问的?)
  • 我看不到变量作用域在这里有什么帮助?即使我将范围更改为本地,new-item 和 remove-item 仍会触发确认提示,并且如果将 whatif 传递给函数,则不会创建/删除文件。
  • 这不是什么“帮助”的问题。这是一个了解它是如何工作的问题。您观察到的行为是设计使然。
  • 是的,我了解,Powershell 设计为将这两个首选项从调用者函数传播到所有被调用者。但我不关心 New-Item 要求我确认,我只关心我的函数中的特定代码块。所以我猜我在 Set-Something2 中的做法是绕过这个自动传播功能的正确方法?
  • 是的,但正如我所说,为每个支持 ShouldProcess 的函数添加 -WhatIf:$false -Confirm:$false 对于调用大量内置 Powershell 函数或自定义函数的复杂函数来说真的很乏味在同一个文件或其他模块中的函数。

标签: powershell


【解决方案1】:

您的问题似乎与范围有关。运行它并阅读文档:

help about_Scopes

您可以在作用域中更改首选项变量(例如,$ConfirmPreference),但这不会影响其在父作用域中的值。这是一个简短的例子:

PS C:\Scripts> $ConfirmPreference
High
PS C:\Scripts> Get-Content .\Test-Preference.ps1
$ConfirmPreference = "Low"
$ConfirmPreference
PS C:\Scripts> .\Test-Preference.ps1
Low
PS C:\Scripts> $ConfirmPreference
High

从这个简短的示例中可以看出,一旦范围(即脚本)终止,首选项变量会自动恢复为之前的值。

如果要在当前范围内执行新范围,可以使用 & 运算符调用脚本块。示例:

$ConfirmPreference

# execute a scriptblock (create new scope, change preference variable)
& {
  $ConfirmPreference = "Low"
  $ConfirmPreference
}

$ConfirmPreference

如果你把以上几行放在一个脚本文件中并运行它,输出将是:

High
Low
High

【讨论】:

  • 我不认为我们彼此了解!这不是一个可以用范围解决的问题。我发现的最接近的问题是:stackoverflow.com/questions/36291735/… 恕我直言,Powershell 默认的行为应该是不传播 WhatIf 和 Confirm。我真的没有看到任何“隐式传播”有用的情况。我更愿意在需要的地方使用显式传播。最后,Set-Something2 可能是要走的路……
  • 查看我的扩展答案 - 您可以在当前范围内调用脚本块来创建新范围。然后,您可以根据需要设置首选项变量。但请记住,它们是变量,因此它们遵守范围规则。
  • 是的,您的解决方案基本上就是我在 Set-Something2 中所做的:将首选项保存在另一个变量中并将它们重置为默认值,以控制 -Confirm-WhatIf 并阻止它们从到处都是。但是我每次调用$PSCmdlet.ShouldProcess(...) 时都必须恢复它们,否则我的cmdlet 将不支持-Confirm-Whatif。它很丑,但它有效!
  • 等待... $PSCmdlet.ShouldProcess(...) 似乎没有检查首选项变量,所以我不需要在调用它之前恢复变量。我只需要在我的 cmdlet 顶部重置两个变量,就是这样!好多了!
  • 现在我理解你的困惑@Bill:我对$PSCmdlet.ShouldProcess(...) 工作方式的假设是错误的。我认为它依赖于偏好变量或if ($PSBoundParameters.ContainsKey('WhatIf')),但无论我重置这些偏好变量还是$PSBoundParameters.Remove('WhatIf'),它似乎都有效
【解决方案2】:

为了在我不希望传递 -WhatIf 的特定函数上解决此问题,我将其明确设置为 $false

例如,来自需要设置订阅上下文以便以后只读查询正常工作的 Azure 脚本:

Set-AzContext -Subscription $Subscription -WhatIf:$false

这可以防止Set-AzContext 从我的主脚本继承-WhatIf,但仍然允许我稍后有选择地使用$PSCmdlet.ShouldProcess() 进行更危险的更改。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-11-03
    • 2011-12-20
    • 1970-01-01
    • 1970-01-01
    • 2023-02-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多