I'm seeing some strange behavior in a custom function I've written, and so I wrote some quick test functions with different characteristics to exhibit these behaviors. The problem arises when parameter sets are similar enough that the only differentiating factor is the type of an object received through the pipeline.
First, I made a simple type that serves only to be different than a string.
Add-Type @"
public class TestType {
public string Prop1;
}
"@
Next, I created a test function and ran it with string and TestType inputs.
function Test-ParameterSets1
{
[CmdletBinding()]
param (
[Parameter(Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="Str")] [string] $StringInput,
[Parameter(Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="Test")] [TestType] $TestInput
)
begin {
$result = New-Object Object | Select-Object –Property @{n='FunctionName';e={$PSCmdlet.MyInvocation.InvocationName}},@{n='ParameterSetName';e={$PSCmdlet.ParameterSetName}}
}
process {
$result | Add-Member -MemberType NoteProperty -Name StringInput -Value $StringInput -PassThru | Add-Member -MemberType NoteProperty -Name TestInput -Value $TestInput
}
end {
$result
}
}
'string' | Test-ParameterSets1
New-Object TestType | Test-ParameterSets1
FunctionName ParameterSetName StringInput TestInput
------------ ---------------- ----------- ---------
Test-ParameterSets1 __AllParameterSets string
Test-ParameterSets1 __AllParameterSets TestType
This is the core of the problem. The ParameterSetName evaluates to __AllParameterSets
even though as seen by the values, the parameters are set as expected. My function has many parameter sets and does lots of switching based on the parameter set to control logic flow.
Next I tried adding a parameter unique to one parameter set and as expected, the ParameterSetName was correct for the calls in which it was specified only.
function Test-ParameterSets2
{
[CmdletBinding()]
param (
[Parameter(Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="Str")] [string] $StringInput,
[Parameter(Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="Test")] [TestType] $TestInput,
[Parameter(ParameterSetName="Test")] [string] $TestName
)
begin {
$result = New-Object Object | Select-Object –Property @{n='FunctionName';e={$PSCmdlet.MyInvocation.InvocationName}},@{n='ParameterSetName';e={$PSCmdlet.ParameterSetName}}
}
process {
$result | Add-Member -MemberType NoteProperty -Name StringInput -Value $StringInput -PassThru | Add-Member -MemberType NoteProperty -Name TestInput -Value $TestInput
}
end {
$result
}
}
'string' | Test-ParameterSets2
New-Object TestType | Test-ParameterSets2 -TestName MyName
New-Object TestType | Test-ParameterSets2
FunctionName ParameterSetName StringInput TestInput
------------ ---------------- ----------- ---------
Test-ParameterSets2 __AllParameterSets string
Test-ParameterSets2 Test TestType
Test-ParameterSets2 __AllParameterSets TestType
Next, I tried adding a parameter that is mandatory for both parameter sets, and this time the ParameterSetName evaluated to an empty string, which was especially confusing.
function Test-ParameterSets5
{
[CmdletBinding()]
param (
[Parameter(Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="Str")] [string] $StringInput,
[Parameter(Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="Test")] [TestType] $TestInput,
[Parameter(Mandatory=$true, ParameterSetName="Str")] [Parameter(Mandatory=$true, ParameterSetName="Test")] [string] $Mandatory,
[Parameter(ParameterSetName="Test")] [string] $TestName
)
begin {
$result = New-Object Object | Select-Object –Property @{n='FunctionName';e={$PSCmdlet.MyInvocation.InvocationName}},@{n='ParameterSetName';e={$PSCmdlet.ParameterSetName}}
}
process {
$result | Add-Member -MemberType NoteProperty -Name StringInput -Value $StringInput -PassThru | Add-Member -MemberType NoteProperty -Name TestInput -Value $TestInput
}
end {
$result
}
}
'string' | Test-ParameterSets5 -Mandatory mandatoryParam
New-Object TestType | Test-ParameterSets5 -Mandatory mandatoryParam -TestName MyName
New-Object TestType | Test-ParameterSets5 -Mandatory mandatoryParam
FunctionName ParameterSetName StringInput TestInput
------------ ---------------- ----------- ---------
Test-ParameterSets5 string
Test-ParameterSets5 Test TestType
Test-ParameterSets5 TestType
It seems as though PowerShell does in fact know how to set these parameters correctly, and yet the ParameterSetName isn't evaluating properly. Is there some way to get this working? I'd like to avoid having unnecessary switches such as -String and -TestType that are unique to their own parameter sets just so that PowerShell can do its job. Thanks!
The problem with your code is that you read the ParameterSetName
property in the begin
block. When a command accepts a pipeline input, then the input object can affect the selected ParameterSetName
. And if your command has multiple input objects, then each of them can result in different parameter set will be selected:
class a { }
class b { }
class c { }
function f {
param(
[Parameter(ParameterSetName='a', ValueFromPipeline)][a]$a,
[Parameter(ParameterSetName='b', ValueFromPipeline)][b]$b,
[Parameter(ParameterSetName='c', ValueFromPipeline)][c]$c
)
begin {
"ParameterSetName in begin block: $($PSCmdlet.ParameterSetName)"
}
process {
"ParameterSetName in process block: $($PSCmdlet.ParameterSetName)"
}
}
[a]::new(), [b]::new(), [c]::new() | f
# Result:
# ParameterSetName in begin block: __AllParameterSets
# ParameterSetName in process block: a
# ParameterSetName in process block: b
# ParameterSetName in process block: c
Thus, if you want to know which parameter set was selected after input object was bound to your command, then you should read ParameterSetName
in the process
block.