Powershell脚本:在函数调用嵌套时实现ShouldProcess的推荐方法?

Ric*_*erg 12 error-handling powershell design-patterns cmdlet

测试脚本:

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
Run Code Online (Sandbox Code Playgroud)

输出:

** 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".
Run Code Online (Sandbox Code Playgroud)

在我看来,这里有几个奇怪的地方:

  • 指定-WhatIf:$ foo将始终打开被调用者(及其被调用者)中的$ WhatIf,无论$ foo是什么.
  • 当您指定-WhatIf"for real"(不将其约束为现有变量)时,它会隐式传播给callees.不需要passthru或splatting.
  • 与-WhatIf不同,显式-Verbose不会隐式地级联到callees.
  • 当您尝试手动passthru -Verbose:$ foo时,您确实看到的行为类似于-WhatIf:$ foo.但它只影响手动测试$ psCmdlet.ShouldProcess()的脚本 - 内置cmdlet不受影响.

注意:确认行为与WhatIf相同.为简洁起见,我省略了它.

搜索网络和连接,我几乎没有看到任何与高级功能有关的ShouldProcess行为(pro或con)的深入讨论.最近的事情是James O'Neill发布的一篇文章,建议在整个调用堆栈中传递$ psCmdlet的单个实例.但是,他这样做是为了解决一个完全不同的问题(避免多个确认提示).同时,当你坚持使用为每个函数提供的标准$ psCmdlet时,我看不到有什么期望的文档...更少的设计模式,最佳实践等......

Kei*_*ill 11

您无法真正引用$ WhatIf或$ Verbose,因为这些是为您合成的,即您的函数中不存在这些变量.如果用户指定了它们,那么你可以通过$ PSBoundParameters获取它们,但是如果用户没有指定那么显然它们不会在这个哈希表中.

将值传递给开关时,PowerShell将执行典型的强制过程以尝试将指定值转换为bool.由于没有定义$ whatif,因此这将导致$ null,从而导致switch值设置为$ true.这可能是因为它看到切换被明确指定为实际上没有值,这相当于仅指定-Whatif没有值.跟踪参数绑定时可以看到这一点:

function Foo
{
    [CmdletBinding(SupportsShouldProcess=1)]
    param()

    Process
    {
        $PSBoundParameters
    }
}

Trace-Command -name ParameterBinding -expr {Foo -whatif:$xyzzy} -PSHost
DEBUG: BIND NAMED cmd line args [Foo]
DEBUG:   BIND arg [] to parameter [WhatIf]
DEBUG:     COERCE arg to [System.Management.Automation.SwitchParameter]
DEBUG:       Arg is null or not present, type is SWITCHPARAMTER, value is true.
DEBUG:         BIND arg [True] to param [WhatIf] SUCCESSFUL
DEBUG: BIND POSITIONAL cmd line args [Foo]
DEBUG: MANDATORY PARAMETER CHECK on cmdlet [Foo]
DEBUG: CALLING BeginProcessing
DEBUG: CALLING EndProcessing
Run Code Online (Sandbox Code Playgroud)

$ WhatIfPreference和$ VerbosePreference在外部根据外部是用-verbose还是-whatif调用来适当设置.我可以看到那些值传播到内部就好了.似乎有一个带有$ pscmdlet.ShouldProcess的PowerShell错误.在这种情况下,它似乎没有兑现$ VerbosePreference的值.您可以尝试将-Verbose传递给内部,如下所示:

inner VerbosePassthru -Verbose:($VerbosePreference -eq 'Continue')
Run Code Online (Sandbox Code Playgroud)

另一种选择是使用Get-Variable -Scope,如下所示:

function Outer
{
    [CmdletBinding(SupportsShouldProcess=1)]
    param()

    Process
    {
        $pscmdlet.ShouldProcess("Outer process", '') > $null
        inner
        #inner -Verbose:($VerbosePreference -eq 'Continue')
    }
}

function Inner
{
    [CmdletBinding(SupportsShouldProcess=1)]
    param()

    Process
    {
        $pscmdlet = (Get-Variable -Scope 1 -Name PSCmdlet).Value
        $pscmdlet.ShouldProcess("Inner process", '') > $null
        "Inner $VerbosePreference"
    }
}

Outer -Verbose
Run Code Online (Sandbox Code Playgroud)

我不确定我是否喜欢这个,因为它暗示你知道外层比内层高1级.您可以"遍历"作用域堆栈,在堆栈中查找下一个PSCmdlet变量.这有效地摆脱了必须传递PSCmdlet(这是粗略的),但它仍然是一个黑客.您应该考虑在MS Connect上提交有关此问题的错误.