如何确定仅在当前管道步骤中绑定的参数?

How can I determine the parameters that were bound in just the current pipeline step?

考虑以下脚本:

function g
{
    [CmdletBinding()]
    param
    (
        [parameter(ValueFromPipelineByPropertyName = $true)]$x,
        [parameter(ValueFromPipelineByPropertyName = $true)]$y,
        [parameter(ValueFromPipelineByPropertyName = $true)]$z
    )
    process
    {
        $retval = @{psbp=@{};mibp=@{};x=$x;y=$y;z=$z}
        $PSBoundParameters.Keys |
            % { $retval.psbp.$_ = $PSBoundParameters.$_ }
        $PSCmdlet.MyInvocation.BoundParameters.Keys |
            % { $retval.mibp.$_ = $PSCmdlet.MyInvocation.BoundParameters.$_} 
        return New-Object psobject -Property $retval
    }
}

$list = (New-Object psobject -Property @{x=1;z=3}),
        (New-Object psobject -Property @{x=$null;y=2} ) 
$list | 
    g |
    Select bp,x,y,z |
    ft -AutoSize

运行 脚本产生以下输出:

psbp      mibp      x y z
----      ----      - - -
{z, x}    {z, x}    1   3
{y, z, x} {y, z, x}   2 

这似乎表明 $PSBoundParameters$PSCmdlet.MyInvocation.BoundParameters 都包含迄今为止绑定的所有参数的 累积

我相当确定 $x$z 在第一步绑定,$x$y 在第二步绑定,但是我还没有找到以编程方式检索该详细信息的方法。

如何确定仅在当前管道步骤中绑定的参数?


我为什么关心这个?某些类型的参数验证比使用参数集、ValidateScript() 等语言功能实现的更复杂。该验证必须在函数体内执行。有时希望将未绑定参数视为在语义上不同于将 $null 传递给同一参数。绑定参数的检测通常是通过询问 $PSBoundParameters 来实现的。如果您在管道上只传递一个参数对象,这会很好地工作。但是,如果您使用管道对参数对象列表进行管道传输,则由于上述脚本所展示的问题,该检测将失败。这违反了最小意外原则,因为在 foreach 循环中运行良好的函数在调用者恰好通过管道将相同的对象传递给它来调用它时表现截然不同。

可以通过从foreach中调用受影响的函数来解决这个问题,但我宁愿解决所有调用的问题而不是放弃管道一共

您可以记住 begin 块中命令行限制的所有参数,然后在 process 块的末尾您可以从当前输入对象限制的参数中清除 $PSBoundParameters

function g
{
    [CmdletBinding()]
    param
    (
        [parameter(ValueFromPipelineByPropertyName = $true)]$x,
        [parameter(ValueFromPipelineByPropertyName = $true)]$y,
        [parameter(ValueFromPipelineByPropertyName = $true)]$z
    )
    begin
    {
        $CommandLineBoundParameters=@($PSBoundParameters.Keys)
    }
    process
    {
        ...

        @($PSBoundParameters.Keys) |
            ? { $CommandLineBoundParameters -notcontains $_ } |
            % { [void]$PSBoundParameters.Remove($_) }
    }
}