EDIT
As per The Mad Technician's suggestion, I have submitted a bug report for this on the PowerShell UserVoice site: https://windowsserver.uservoice.com/forums/301869-powershell/suggestions/20034763-dynamic-parameters-and-positional-parameters-do-no
ORIGINAL QUESTION
I want to be able to specify positional parameters within a PowerShell function that include both static and dynamic parameters. e.g. I have
function Test-Positional{
[CmdletBinding(PositionalBinding=$false)]
param(
[Parameter(Mandatory=$false,Position=3)][string]$Param4
)
dynamicparam {
$paramDictionary = new-object -Type System.Management.Automation.RuntimeDefinedParameterDictionary
$paramname1 = "Param1"
$values1 = 'some','list','of','values' #would normally get these dynamically
$attributes1 = new-object System.Management.Automation.ParameterAttribute
$attributes1.ParameterSetName = "__AllParameterSets"
$attributes1.Mandatory = $true
$attributes1.Position = 0
$attributeCollection1 = new-object -Type System.Collections.ObjectModel.Collection[System.Attribute]
$attributeCollection1.Add($attributes1)
$ValidateSet1 = new-object System.Management.Automation.ValidateSetAttribute($values1)
$attributeCollection1.Add($ValidateSet1)
$dynParam1 = new-object -Type System.Management.Automation.RuntimeDefinedParameter($paramname1, [string], $attributeCollection1)
$paramname2 = "Param2"
$values2 = 'another','list','like','before'
$attributes2 = new-object System.Management.Automation.ParameterAttribute
$attributes2.ParameterSetName = "__AllParameterSets"
$attributes2.Mandatory = $true
$attributes2.Position = 1
$attributeCollection2 = new-object -Type System.Collections.ObjectModel.Collection[System.Attribute]
$attributeCollection2.Add($attributes2)
$ValidateSet2 = new-object System.Management.Automation.ValidateSetAttribute($values2)
$attributeCollection2.Add($ValidateSet2)
$dynParam2 = new-object -Type System.Management.Automation.RuntimeDefinedParameter($paramname2, [string], $attributeCollection2)
$paramname3 = "Param3"
$values3 = 'yet','another','list'
$attributes3 = new-object System.Management.Automation.ParameterAttribute
$attributes3.ParameterSetName = "__AllParameterSets"
$attributes3.Mandatory = $true
$attributes3.Position = 2
$attributeCollection3 = new-object -Type System.Collections.ObjectModel.Collection[System.Attribute]
$attributeCollection3.Add($attributes3)
$ValidateSet3 = new-object System.Management.Automation.ValidateSetAttribute($values3)
$attributeCollection3.Add($ValidateSet3)
$dynParam3 = new-object -Type System.Management.Automation.RuntimeDefinedParameter($paramname3, [string], $attributeCollection3)
$paramDictionary.Add($paramname1, $dynParam1)
$paramDictionary.Add($paramname2, $dynParam2)
$paramDictionary.Add($paramname3, $dynParam3)
return $paramDictionary
}
process{
$PSBoundParameters.Param1
$PSBoundParameters.Param2
$PSBoundParameters.Param3
$PSBoundParameters.Param4
}
}
but if I run PS C:\Windows\System32\inetsrv> Test-Positional 'list' 'another' 'yet' 'so'
I get the error:
Test-Positional : Cannot validate argument on parameter 'Param1'. The argument "another" does not belong to the set "some,list,of,values" specified by the ValidateSet attribute. Supply an argument that is in the set and then try the command again. At line:1 char:20 + Test-Positional list another yet so + ~~~~~~~ + CategoryInfo : InvalidData: (:) [Test-Positional], ParameterBindingValidationException + FullyQualifiedErrorId : ParameterArgumentValidationError,Test-Positional
It does not throw this if I remove the Position=3
attribute from the static parameter ($param4), which is fine except then I can't use it as a positional parameter I have to name it directly. I get the same error if I keep Position=3
and remove PositionalBinding=$false
Is it just not possible to have both static and dynamic parameters be positional parameters? Or am I missing something obvious here?
Position works in relation to parameters of the same type. So static parameters will respect the position of other static parameters, and dynamic parameters will respect the position of other dynamic parameters, but the static parameters will consume arguments first, and the dynamic parameters will consume whatever is left. The only exception to this that I know of is if you use the parameter attribute of ValueFromRemainingArguments=$true
, which forces that specific parameter to be the last one. So, if you truly only have 1 static parameter that you want to come after the dynamic parameters, you can set ValueFromRemainingArguments=$true
and it will behave as you desire. If you have other static parameters, they will still come before the dynamic parameters regardless of if you specify a position later than those of the dynamic parameters.
It looks to me like you found a bug, and I would encourage you to file a bug for this on the PowerShell UserVoice site: https://windowsserver.uservoice.com/forums/301869-powershell
In my opinion, they should either update documentation to state that dynamic parameter positioning is evaluated after stat parameter positioning and correct the syntax section when Get-Help
is run against the function/script, or update the behavior accordingly so that positions are respected across both dynamic and static parameters. I would prefer the latter myself, but that may involve engine changes that are not feasible.
If you do create a bug, please provide a link to it so that others can find it and up vote it (so that it gains attention, and gets fixed!).