alx9r
alx9r

Reputation: 4251

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

Consider the following script:

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

Running the script results in the following output:

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

This seems to reveal that both $PSBoundParameters and $PSCmdlet.MyInvocation.BoundParameters contain the cumulation of all the parameters bound so far.

I'm fairly sure that $x and $z were bound on the first step, and $x and $y were bound on the second step, but I haven't found a way to retrieve that detail programmatically.

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


Why do I care about this? Some kinds of parameter validation are more complex than can be achieved using language features like parameter sets, ValidateScript(), etc. That validation has to be performed inside the function body. Occasionally it is desirable to consider an unbound parameter to be semantically different from passing $null to that same parameter. Detection of bound parameters is customarily achieved by interrogating $PSBoundParameters. This works fine if you pass only a single parameter object on the pipeline. However, if you pipe a list of parameter objects using the pipeline, that detection is foiled because of the problem demonstrated by the script above. This is violates the principle-of-least surprise because a function that worked fine inside a foreach loop behaves dramatically differently when the caller happens to invoke it by piping those same objects to it.

I can work around this by calling the affected functions from within a foreach, but I'd rather solve the problem for all invocations rather than abandon the pipeline altogether.

Upvotes: 2

Views: 885

Answers (1)

user4003407
user4003407

Reputation: 22132

You can remember all parameters bounded by command line in begin block, then in the end of process block you can clean up $PSBoundParameters from parameters bounded by current input object:

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($_) }
    }
}

Upvotes: 2

Related Questions