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.

Answers


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

Need Your Help

Jquery Auto Pop up without clicks, would like some assistance with code

jquery html popup

I would like for the pop up to appear once the page loads, without clicking anything.

Get referrer URL with parameters in Rails

ruby-on-rails httprequest

I have a site with the basic rails scaffold, when a user deletes a record the default action is to redirect to the home page. However I would like it to return to the list the user was just looking...