This is an extension to my notes on parameters for scripts or (advanced) functions in Powershell:
Dynamic (or conditional) parameters are an interesting and cool feature, but also a bit tricky and cumbersome to set up.
Introduction
Often times, much simpler things like parameter sets and/or parameter validations may be the better and easier solution – but on the other hand:
Sometimes, those are not the right tools and dynamic parameters are.
Supplement: After playing around with it for some time, I think it’s really a finicky feature (see “Gotchas!”); thus my recommendation now:
Avoid if possible.
(Sometimes it may be the best solution – but think long and hard about it before going down that road!)
Disclaimer: I won’t give here a full introduction or complete tutorial on dynamic parameters; this is primarily supposed to be just a dense reminder about this topic for my future self.
To learn more about dynamic parameters, I recommend these pages:
- Dynamic Parameters in PowerShell
- Mastering PowerShell Parameter Validation with Dynamic Methods
- Tips and Tricks to Using PowerShell Dynamic Parameters
- Microsoft: Learn | PowerShell | about_Functions_Advanced_Parameters: Dynamic parameters
The Challenge
As mentioned before, due to the more complicated nature of setting up and using dynamic parameters, one could (and should) use most of the times other, simpler techniques (e.g. parameter sets).
It also took quite a while until had a real need for these conditional parameters:
I wanted to enable/disable some parameters depending on the value(!) of another parameter, so that not too many (or the wrong)
parameters will be required by the caller of the function.
The function in question is used to create user accounts; and depending on the naming convention of the username (the user ID), only very specific additional parameters are required (others get default values or are irrelevant).
In this case, parameter sets can’t help, because the value of a parameter (i.e. the username) needs to be evalutated, not just whether the parameter itself is mentioned or not (since the parameter -UserID is mandatory anyways).
Update 2025-10-19: The example code below was initially an already trimmed down version of that function, but coming back to this page months later, to refresh my own knowledge about it [as mentiond before: Dynamic Parameters should be used sparingly], I still was overwhelmed by the amount of code: 133 lines and too specific. That’s why the example is now shorter and more generic.
By the way: Another idea from the articles about using dynamic parameters (but which I haven’t used yet):
Generating the values of a ValidateSet dynamically, instead of hardwiring them in the source code; hmmm….
The Set-Up
This is just an overview; details are in the sample code below:
A. To create a dynamic parameter, you will need to use the DynamicParam keyword.
- Unlike the
Paramkeyword, the statements in theDynamicParamblock are enclosed in curly brackets (i.e.DynamicParam { ... }). DynamicParammust be declared afterParamwhen used in a cmdlet.
But note that theDynamicParamblock runs before theParamblock (see Gotchas)!
B. Normal code must be in a Begin{}, Process{} or End{} block (see Gotchas)!
C. Steps to do before one can use the dynamic parameters (see example below for details):
- Create a RuntimeDefinedParameterDictionary object.
- Define the parameter attributes with the ParameterAttribute object.
- Create an Attribute collection object.
- Add the ParameterAttribute (from step 2) to the Attribute collection (from step 3).
- Create a RuntimeDefinedParameter object, with the name and type of the parameter and the attribute collection to which it belongs (see step 3).
- Add the RuntimeDefinedParameter to the RuntimeDefinedParameterDictionary.
- Return the RuntimeDefinedParameterDictionary object.
Gotchas!
Gotcha 1: Unexpected token “…” in expression or statement
If you get an error/exception like the one above, it’s because normal code (after DynamicParam {}) must be in a Begin{}, Process{} or End{} block —
even if you don’t plan to make the function pipeline-ready. (I prefer to put it in the End{} block under such circumstances.)
That rarely gets mentioned (explicitly) in the offical documentation or in some tutorials; but with a little digging, one can find it explained elsewhere, e.g. here: https://stackoverflow.com/questions/39041328/any-code-after-dynamicparam-block-on-the-script-level-is-parsed-as-syntax-error
Gotcha 2: DynamicParam{} runs before Param()
Another peculiarity that is not mentioned often and led to an error when I tested dynamic features first with the parameter specified directly on the command line:
In other words:
This was OK: Script.ps1 -Parameter1 One
This was not: Script.ps1
After some searching, I came accross this explaination:
Your code only works if you pass an argument to your […] parameter directly on the command line.
If you let PowerShell prompt you for a [the] argument, based on the parameter’s mandatory property, your code cannot work, because the DynamicParam block runs before such automatic prompts.
— Source
Because then the mandatory Parameter1 will be prompted too late and thus will not yet be available in the DynamicParam block
(because the DynamicParam block runs before the Param block!).
Workaround A (not recommended)
Don’t make it mandatory in the Param block (to prevent a second inquiry, because that will already happen in the DynamicParam block).
Assign the variable in the
Param block to itself, so it won’t be empty: If available, it will get the value from the DynamicParam block.(Without assigning itself, the parameter variable may become empty again, because it’s optional and Param runs after DynamicParam!)
Test in the DynamicParam block whether the parameter is already provided as a CLI argument; otherwise ask for the user it via Read-Host:
Param ([Parameter(Mandatory = $false)] $Parameter1 = $Parameter1)
DynamicParam { if ($PSBoundParameters['Parameter1']) { $Parameter1 = $PSBoundParameters['Parameter1'] }
else { $Parameter1 = read-host "Parameter1" }
# ...
}
Workaround B
Putting a Read-Host in the middle of a DynamicParam block (Workaround A) leads to a lot of
weird side effects (with regards to autocompletion or code extraction for help and other stuff from a function).
Instead, just assume that the parameter argument required for the DynamicParam block is actually provided directly on the command line; if not: Abort. (Only bad thing: Other mandatory parameters will also first be queried before the function finally aborts.)
Param
(
[Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $Parameter1,
[Parameter(Mandatory=$true)] $Parameter2
)
DynamicParam
{
$ParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
if ((-not $PSBoundParameters['Parameter1']) -or ([string]::IsNullOrEmpty($Parameter1)))
{
# One can't abort from within DynamicParam, so just mark it and return an empty dictionary.
$IsDynamicParamOK = $false
return $ParameterDictionary
}
else
{
# Continue with the normal code path, because everything was OK...
}
$IsDynamicParamOK = $true
return $ParameterDictionary
}
End
{
if ($IsDynamicParamOK -eq $false)
{
Write-Warning "Please provide the Parameter1 directly on the command line: $($MyInvocation.MyCommand.Name) -Parameter1 ..."
return
}
else
{
# Continue with the normal code path, because everything was OK...
}
}
Gotcha 3: The values of dynamic parameters is yet unknown in DynamicParam{}
Because only the set-up work is done in the DynamicParam{} block, there aren’t yet any values known for these dynamic parameters.
That means you can’t get the value of a dynamic parameter A to decide, based on its value, if maybe another dynamic parameter B is needed.
Example
[CmdletBinding()]
Param
(
[Parameter(Mandatory = $true)] $Parameter1,
[Parameter(Mandatory = $false)] [int] $Parameter2
)
DynamicParam
{
$DP_Dictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
if ($PSBoundParameters['Parameter2']) # Act if the optional Parameter2 was provided:
{
$DP_Param2A_Attributes = [System.Management.Automation.ParameterAttribute] @{
Mandatory = $false
ParameterSetName = '__AllParameterSets'
}
# or:
# $DP_Param2A_Attributes = New-Object System.Management.Automation.ParameterAttribute
# $DP_Param2A_Attributes.Mandatory = $false
# $DP_Param2A_Attributes.ParameterSetName = '__AllParameterSets'
$DP_Param2A_Collection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$DP_Param2A_Collection.Add($DP_Param2A_Attributes)
$DP_Param2A = New-Object System.Management.Automation.RuntimeDefinedParameter('Parameter2A',
[int],
$DP_Param2A_Collection)
$DP_Dictionary.Add('Parameter2A', $DP_Param2A)
# Special case: The value of 'Parameter2' is between 10-19:
# Then we ask for an additional parameter (this time mandatory one):
if (($PSBoundParameters['Parameter2'] -ge 10) -and ($PSBoundParameters['Parameter2'] -lt 20))
{
$DP_Param3_Attributes = [System.Management.Automation.ParameterAttribute] @{
Mandatory = $true
ParameterSetName = '__AllParameterSets'
}
$DP_Param3_Collection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$DP_Param3_Collection.Add($DP_Param3_Attributes)
$DP_Param3 = New-Object System.Management.Automation.RuntimeDefinedParameter('Parameter3',
[string],
$DP_Param3_Collection)
$DP_Dictionary.Add('Parameter3', $DP_Param3)
}
}
return $DP_Dictionary
}
# --------------------------------------------------------------------------------------------------
# The actual main part of the script (Dynamic Parameters always require a Begin, Process and/or End block):
End
{
write-host ("Parameter1 : {0}" -f $Parameter1)
write-host ("Parameter2 (optional): {0}" -f $Parameter2)
if ($PSBoundParameters['Parameter2A'])
{
$Parameter2A = $PSBoundParameters['Parameter2A']
write-host ("Parameter2A : {0}" -f $Parameter2A)
}
if ($PSBoundParameters['Parameter3'])
{
$Parameter3 = $PSBoundParameters['Parameter3']
write-host ("Parameter3 : {0}" -f $Parameter3)
}
}
Film & Television (55)
How To (64)
Journal (17)
Miscellaneous (4)
News & Announcements (21)
On Software (12)
Projects (26)