Powershell脚本编写:当函数调用嵌套时实现ShouldProcess的推荐方法?
测试脚本:
function outer
{
[cmdletbinding(supportsshouldprocess=$true)]
param($s)
process
{
$pscmdlet.shouldprocess("outer $s", "ShouldProcess") | out-null
"" | out-file "outer $s"
inner ImplicitPassthru
inner VerbosePassthru -Verbose:$Verbose
inner WhatifPassthru -WhatIf:$WhatIf
}
}
function inner
{
[cmdletbinding(supportsshouldprocess=$true)]
param($s)
process
{
$pscmdlet.shouldprocess("inner $s", "ShouldProcess") | out-null
"" | out-file "inner $s"
}
}
"`n** NORMAL **"
outer normal
"`n** VERBOSE **"
outer verbose -Verbose
"`n** WHATIF **"
outer whatif -WhatIf
输出:
** NORMAL **
VERBOSE: Performing operation "ShouldProcess" on Target "inner VerbosePassthru".
What if: Performing operation "ShouldProcess" on Target "inner WhatifPassthru".
What if: Performing operation "Output to File" on Target "inner WhatifPassthru".
** VERBOSE **
VERBOSE: Performing operation "ShouldProcess" on Target "outer verbose".
VERBOSE: Performing operation "ShouldProcess" on Target "inner VerbosePassthru".
What if: Performing operation "ShouldProcess" on Target "inner WhatifPassthru".
What if: Performing operation "Output to File" on Target "inner WhatifPassthru".
** WHATIF **
What if: Performing operation "ShouldProcess" on Target "outer whatif".
What if: Performing operation "Output to File" on Target "outer whatif".
What if: Performing operation "ShouldProcess" on Target "inner ImplicitPassthru".
What if: Performing operation "Output to File" on Target "inner ImplicitPassthru".
What if: Performing operation "ShouldProcess" on Target "inner VerbosePassthru".
What if: Performing operation "Output to File" on Target "inner VerbosePassthru".
What if: Performing operation "ShouldProcess" on Target "inner WhatifPassthru".
What if: Performing operation "Output to File" on Target "inner WhatifPassthru".
在我看来,这里有几个奇怪的地方:
- 指定 -WhatIf:$foo 将始终在被调用者(及其被调用者)中打开 $WhatIf,无论 $foo 是什么。
- 当您确实指定 -WhatIf “for real”(不将其限制为现有变量)时,它会隐式传播到被调用者。无需直通或泼溅。
- 与 -WhatIf 不同,显式 -Verbose 不会隐式级联到被调用者。
- 当您尝试手动传递 -Verbose:$foo 时,您确实会看到与 -WhatIf:$foo 类似的行为。但它只影响手动测试 $psCmdlet.ShouldProcess() 的脚本——内置 cmdlet 不受影响。
注意:Confirm 的行为与 WhatIf 相同。为了简洁起见,我省略了它。
在网络和 Connect 中搜索,我几乎看不到任何有关高级功能的 ShouldProcess 行为(赞成或反对)的深入讨论。最接近的是 James 的帖子O'Neill 建议在整个调用堆栈中传递 $psCmdlet 的单个实例。然而,他这样做是为了解决一个完全不同的问题(避免多个 -Confirm 提示)。同时,当您坚持使用为每个函数提供的标准 $psCmdlet 时,我没有看到任何有关预期内容的文档......更不用说设计模式、最佳实践等......
Test script:
function outer
{
[cmdletbinding(supportsshouldprocess=$true)]
param($s)
process
{
$pscmdlet.shouldprocess("outer $s", "ShouldProcess") | out-null
"" | out-file "outer $s"
inner ImplicitPassthru
inner VerbosePassthru -Verbose:$Verbose
inner WhatifPassthru -WhatIf:$WhatIf
}
}
function inner
{
[cmdletbinding(supportsshouldprocess=$true)]
param($s)
process
{
$pscmdlet.shouldprocess("inner $s", "ShouldProcess") | out-null
"" | out-file "inner $s"
}
}
"`n** NORMAL **"
outer normal
"`n** VERBOSE **"
outer verbose -Verbose
"`n** WHATIF **"
outer whatif -WhatIf
Output:
** NORMAL **
VERBOSE: Performing operation "ShouldProcess" on Target "inner VerbosePassthru".
What if: Performing operation "ShouldProcess" on Target "inner WhatifPassthru".
What if: Performing operation "Output to File" on Target "inner WhatifPassthru".
** VERBOSE **
VERBOSE: Performing operation "ShouldProcess" on Target "outer verbose".
VERBOSE: Performing operation "ShouldProcess" on Target "inner VerbosePassthru".
What if: Performing operation "ShouldProcess" on Target "inner WhatifPassthru".
What if: Performing operation "Output to File" on Target "inner WhatifPassthru".
** WHATIF **
What if: Performing operation "ShouldProcess" on Target "outer whatif".
What if: Performing operation "Output to File" on Target "outer whatif".
What if: Performing operation "ShouldProcess" on Target "inner ImplicitPassthru".
What if: Performing operation "Output to File" on Target "inner ImplicitPassthru".
What if: Performing operation "ShouldProcess" on Target "inner VerbosePassthru".
What if: Performing operation "Output to File" on Target "inner VerbosePassthru".
What if: Performing operation "ShouldProcess" on Target "inner WhatifPassthru".
What if: Performing operation "Output to File" on Target "inner WhatifPassthru".
To my eye there are several oddities here:
- Specifying -WhatIf:$foo will always turn on $WhatIf in the callee (and its callees), no matter what $foo is.
- When you do specify -WhatIf "for real" (without constraining it to an existing variable), it propagates to callees implicitly. No need for passthru or splatting.
- Unlike -WhatIf, explicit -Verbose does not cascade to callees implicitly.
- When you try to manually passthru -Verbose:$foo, you do see behavior is similar to -WhatIf:$foo. But it only affects scripts that manually test $psCmdlet.ShouldProcess() -- built in cmdlets aren't affected.
N.B.: Confirm behaves identical to WhatIf. I omitted it for brevity.
Searching the web and Connect, I see hardly any in-depth discussion of ShouldProcess behavior (pro or con) as pertains to advanced functions. Closest thing is a post from James O'Neill that recommends passing a single instance of $psCmdlet throughout the call stack. However, he does so to workaround an entirely different problem (avoiding multiple -Confirm prompts). Meanwhile, when you stick with the standard $psCmdlet provided to each function, I see no docs on what to expect...much less design patterns, best practices, etc...
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
您不能真正引用 $WhatIf 或 $Verbose,因为它们是为您合成的,即这些变量在您的函数中不存在。如果用户指定了它们,那么您可以通过 $PSBoundParameters 获取它们,但如果用户没有指定,那么显然它们不会出现在这个哈希表中。
当您将值传递给开关时,PowerShell 将执行典型的强制过程以尝试将指定值转换为布尔值。由于 $whatif 未定义,因此它的值为 $null,这会导致开关值设置为 $true。这可能是因为它看到开关被显式指定,实际上没有值,这相当于只指定 -Whatif 没有值。当您跟踪参数绑定时,您可以看到这一点:
根据外部是使用 -verbose 还是 -whatif 调用,$WhatIfPreference 和 $VerbosePreference 在外部中进行适当设置。我可以看到这些值很好地传播到内部。 $pscmdlet.ShouldProcess 似乎存在 PowerShell 错误。在这种情况下,它似乎没有尊重 $VerbosePreference 的价值。您可以尝试像这样通过 -Verbose 传递到内部:
另一种选择是使用 Get-Variable -Scope ,如下所示:
我不确定我喜欢这个,因为它意味着您知道外部比内部高 1 级。您可以“遍历”作用域堆栈,在堆栈中查找下一个 PSCmdlet 变量。这有效地消除了必须传递 PSCmdlet (这很恶心)的情况,但它仍然是一个 hack。您应该考虑在 MS Connect 上提交有关此问题的错误。
You can't really refer to $WhatIf or $Verbose since these are synthesized for you i.e. these variables don't exist in your function. If the user specifies them then you can get at them via $PSBoundParameters but if the user didn't specify then obviously they won't be in this hashtable.
When you pass a value to a switch PowerShell will do the typical coercion process to attempt to convert the specified value to a bool. Since $whatif isn't defined this evals to a $null which results in the switch value being set to $true. This is presumably because it sees the switch is explicitly specified with effectively no value which is the equivalent of just specifying -Whatif with no value. You can see this when you trace the parameter binding:
The $WhatIfPreference and $VerbosePreference gets set appropriately in outer based on whether outer was called with -verbose or -whatif. I can see that those values propagate to inner just fine. It would seem that there is a PowerShell bug with $pscmdlet.ShouldProcess. It doesn't seem to be honoring the value of $VerbosePreference in this case. You could try passing through -Verbose to inner like so:
Another option is to use Get-Variable -Scope like so:
I'm not sure I like this because it implies that you know outer is 1 level above inner. You could "walk" the scope stack looking for the next PSCmdlet variable up the stack. This effectively gets rid of having to pass in PSCmdlet (which is gross) but it's still a hack. You should consider filing a bug on MS Connect about this.
我一直想写出完全相同的问题,而我在差不多 7 年后才写下这篇文章。令我惊讶的是,微软的 PowerShell 团队还没有修复这个问题。我已使用 PowerShell Version 6 Preview(最新版本)重现了该问题。
我想出了一个简单的解决方法,即在
Inner
函数内,我们创建并运行一个scriptblock
,设置-Verbose
标志通过检查$VerbosePreference
是否正确设置为Continue
,即使ShouldProcess
不尊重它:I was looking to write exactly the same question, and I am writing this almost 7 years later. I am surprised that Microsoft's PowerShell team have not fixed this yet. I have reproduced the issue with PowerShell Version 6 Preview (latest version).
I have come up with a simple workaround, that is, inside the
Inner
function, we create and run ascriptblock
, setting the-Verbose
flag by checking$VerbosePreference
which is correctly set toContinue
, even though it is not respected byShouldProcess
: