Reputation: 75
I have an advanced function that can accept two kinds of pipeline data:
Here's my function:
function Test-PowerShell {
[CmdletBinding(DefaultParameterSetName = "ID")]
param (
[Parameter(
Mandatory = $true,
ParameterSetName = "InputObject",
ValueFromPipeline = $true
)]
[PSTypeName('MyType')]
$InputObject,
[Parameter(
Mandatory = $true,
ParameterSetName = 'ID',
ValueFromPipelineByPropertyName = $true
)]
[int]
$ID
)
process {
if ($InputObject) {
$objects = $InputObject
Write-Verbose 'InputObject binding'
}
else {
$objects = Get-MyType -ID $ID
Write-Verbose 'ID binding'
}
# Do something with $objects
}
}
I can use this function like this:
$obj = [PSCustomObject]@{
PSTypeName = 'MyType'
ID = 5
Name = 'Bob'
}
$obj | Test-PowerShell -Verbose
Note that this object satisfies both of the above conditions: It is a MyType, and it has an ID property. In this case, PowerShell always binds to the ID property. This isn't ideal performance-wise because the piped object is discarded and I have to re-query it using the ID. My question is this:
How do I force PowerShell to bind the pipeline to $InputObject if possible?
If I change the default parameter set to InputObject, PowerShell binds on $InputObject. I don't want this, however, because when run without parameters, I want PowerShell to prompt for an ID, not an InputObject.
Upvotes: 5
Views: 605
Reputation: 19684
Simple answer: remove the Mandatory
argument to the Parameter
attribute on $InputObject
to get the functionality you want. I don't have enough knowledge on how parameter binding works to explain why this works.
function Test-PowerShell {
[CmdletBinding(DefaultParameterSetName = 'ID')]
param(
[Parameter(ParameterSetName = 'InputObject', ValueFromPipeline)]
[PSTypeName('MyType')]
$InputObject,
[Parameter(ParameterSetName = 'ID', Mandatory, ValueFromPipelineByPropertyName)]
[int]
$ID
)
process {
$PSBoundParameters
}
}
$o = [pscustomobject]@{
PSTypeName = 'MyType'
ID = 6
Name = 'Bob'
}
PS> $o | Test-PowerShell
Key Value
--- -----
InputObject MyType
PS> [pscustomobject]@{ID = 6} | Test-PowerShell
Key Value
--- -----
ID 6
Here's a workaround to your problem (defining your own type):
Add-Type -TypeDefinition @'
public class MyType
{
public int ID { get; set; }
public string Name { get; set; }
}
'@
And then you would tag your parameter as [MyType]
, creating objects like you would from [pscustomobject]
:
[MyType]@{ ID = 6; Name = 'Bob' }
In hindsight, this method does not work. What you're running into is the behavior of the DefaultParameterSet
. I'd suggest changing what you take as pipeline input. Is there a use-case for taking the ID as pipeline input versus a user just using Test-PowerShell -ID 5
or Test-PowerShell
and being prompted for the ID?
Here's a suggestion that may work as you intend from my testing:
function Test-PowerShell {
[CmdletBinding(DefaultParameterSetName = 'ID')]
param(
[Parameter(ParameterSetName = 'InputObject', Mandatory = $true, ValueFromPipeline = $true)]
[PSTypeName('MyType')]
$InputObject,
[Parameter(ParameterSetName = 'ID', Mandatory = $true, ValueFromPipeline = $true)]
[int]
$ID
)
process {
$PSBoundParameters
}
}
To take an example from an existing built-in cmdlet, they don't use the same name or properties on an object for multiple parameters. In Get-ChildItem
, both the LiteralPath
and Path
take pipeline input, but LiteralPath
only takes it by PropertyName LiteralPath
or PSPath
(aliased). Path
is ByValue and PropertyName, but only as Path
.
Upvotes: 3