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.
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($_) }
}
}
来源:https://stackoverflow.com/questions/34032902/how-can-i-determine-the-parameters-that-were-bound-in-just-the-current-pipeline