问题
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.
回答1:
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