测试脚本:

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 不受影响。

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

在网络和 Connect 中搜索,我几乎看不到任何关于高级功能的 ShouldProcess 行为(赞成或反对)的深入讨论。最接近的是 詹姆斯·奥尼尔的帖子 建议在整个调用堆栈中传递 $psCmdlet 的单个实例。然而,他这样做是为了解决一个完全不同的问题(避免多个 -Confirm 提示)。同时,当您坚持使用为每个函数提供的标准 $psCmdlet 时,我没有看到任何有关预期内容的文档......更不用说设计模式、最佳实践等......

有帮助吗?

解决方案

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

当您将值传递给开关时,PowerShell 将执行典型的强制过程以尝试将指定值转换为布尔值。由于 $whatif 未定义,因此它的值为 $null,这会导致开关值设置为 $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

$WhatIfPreference 和 $VerbosePreference 在外部中根据是否使用 -verbose 还是 -whatif 调用进行适当设置。我可以看到这些值很好地传播到内部。$pscmdlet.ShouldProcess 似乎存在 PowerShell 错误。在这种情况下,它似乎没有尊重 $VerbosePreference 的值。您可以尝试通过 -Verbose 传递到内部,如下所示:

inner VerbosePassthru -Verbose:($VerbosePreference -eq 'Continue')

另一种选择是使用 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

我不确定我喜欢这个,因为它意味着你知道外部比内部高 1 级。您可以“遍历”作用域堆栈,在堆栈中查找下一个 PSCmdlet 变量。这有效地消除了必须传递 PSCmdlet (这很恶心)的情况,但它仍然是一个 hack。您应该考虑在 MS Connect 上提交有关此问题的错误。

其他提示

我一直想写出完全相同的问题,而我在差不多 7 年后才写下这篇文章。令我惊讶的是,微软的 PowerShell 团队还没有解决这个问题。我已使用 PowerShell Version 6 Preview(最新版本)重现了该问题。

我想出了一个简单的解决方法,那就是在 Inner 函数,我们创建并运行一个 scriptblock, ,设置 -Verbose 通过检查标记 $VerbosePreference 正确设置为 Continue, ,尽管它不被尊重 ShouldProcess:


Function Outer {
    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="Medium")]
    param([string]$Name)

    Process {
        Write-Host "Outer called";
        Inner $Name
    }
}

Function Inner {
    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="Medium")]
    param([string]$Name)

    Process {
        if (-not ($PSBoundParameters.ContainsKey('Verbose'))) {
            $PSBoundParameters.Add('Verbose', [bool]$VerbosePreference -eq 'Continue');
        }

        & {
            [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="Medium")]

            param([string]$Name)

            if ($PSCmdlet.ShouldProcess($Name, "Inner")) {
                Write-Host "Inner called";
            }
        } @PSBoundParameters;
    }
}

Export-ModuleMember *
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top