A neat but often unknown feature is the PowerShell dynamic parameter. These are no ordinary parameters as dynamic parameters only exist when certain conditions are met. Imagine the possibilities!

In this post, you will learn what dynamic parameters are exactly, and I provide a real-life example where I used them recently. Finally, I’ll cover one aspect that isn’t mentioned in the Microsoft documentation.

The Case for Using PowerShell Dynamic Parameters

PowerShell dynamic parameters are useful when you need a parameter to be available only under certain conditions. Take for example creating a channel in a Microsoft Teams group. Teams channels come in two types: standard and private.

Standard channels are available to everyone in the team, and private channels are only available to some team members. When you create a private channel, you name the private channel owner so that person can manage the channel’s membership.

In my custom PowerShell function, accepting the channel name and type can be done with two mandatory parameters. Here I’ve named them $ChannelName and $ChannelType. I’ve limited channel type values using the [ValidateSet()] parameter attribute to Standard and Private.

function New-Channel {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]
        $ChannelName,
        [Parameter(Mandatory)]
        [ValidateSet('Standard','Private')]
        [string]
        $ChannelType
    )
}

So how can you create another parameter for when the $ChannelType is “Private”? You might be tempted to try parameter sets, but I don’t think this will solve the problem.

You could add another non-mandatory parameter to accept a channel owner, but this might be confusing as it’s not needed for a Standard channel. The person using the function might think they are defining an owner for the standard channel when that isn’t true.

What if you had a parameter that was only available when $ChannelType equals “Private”?

Enter PowerShell dynamic parameters!

Defining PowerShell Dynamic Parameters

Dynamic parameters are defined outside the standard param() block. Use the DynamicParam keyword and enclose its code in curly brackets, like this:

function New-Channel {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]
        $ChannelName,
        [Parameter(Mandatory)]
        [ValidateSet('Standard','Private')]
        [string]
        $ChannelType
    )
    DynamicParam {}
}

Within the DynamicParameter keyword, enter the condition that the parameter(s) should appear. In this case, I want an additional private channel owner parameter when $ChannelOwner equals “Private.” I accomplish this using an if statement:

DynamicParam {
    if ($ChannelType -eq 'Private') {}
}

Within the if statement, you start defining the parameter; however, you don’t define the parameter like a typical function parameter. First, you create a new object of type System.Management.Automation.ParameterAttribute.

$paramAttributes = New-Object -Type $System.Management.Automation.ParameterAttribute

Next, you define parameter properties, such as a parameter set name, if it is mandatory, its position, or if it takes value from the pipeline. These properties are all the standard ones you’d use when defining a regular parameter. For my private owner channel parameter, I’m only specifying that it is mandatory.

$paramAttributes.Mandatory = $true

Next, you create another object of type System.Collections.ObjectModel.Collection[System.Attribute], which is a collection of type System.Attribute. You then add your parameter attributes object to this collection.

$paramAttributesCollect = New-Object -Type `
    System.Collections.ObjectModel.Collection[System.Attribute]
$paramAttributesCollect.Add($paramAttributes)

You then create another object of type RuntimeDefinedParameters with the parameter name, the parameter type, and the attribute collection. Here, the parameter name is PrivateChannelOwner of type string.

$dynParam1 = New-Object -Type `
    System.Management.Automation.RuntimeDefinedParameter("PrivateChannelOwner", [string],
 $paramAttributesCollect)

Finally, you create yet another object and store the defined parameter in the dictionary object and return it.

$paramDictionary = New-Object -Type `
System.Management.Automation.RuntimeDefinedParameterDictionary
$paramDictionary.Add("PrivateChannelOwner", $dynParam1)
return $paramDictionary

Quite a bit of code to accomplish this! Here is the full code of the dynamic parameter block:

Things Left Undocumented in Dynamic Parameters

With the dynamic parameter defined, here’s one thing the Microsoft documentation doesn’t specify: the rest of your code has to be in begin/process/end blocks. When I created my first dynamic parameter, I couldn’t figure out why VS Code gave me a syntax issue about an unexpected token in expression or statement. Once I put my code into begin/process/end blocks, the error went away.

You can not reference the parameter using just a dollar sign ($) and the parameter name. You use the automatic variable $PSBoundParameters and the name of the dynamic parameter. You can make your own variable and assign the dynamic parameter value to it for easier reference. Here are both of the above concepts put into action with some placeholder comments:

begin {
    $PrivateChannelOwner = $PSBoundParameters['PrivateChannelOwner']
}
process {
    if ($PrivateChannelOwner) {
        # Code to create private channel with owner
    }
    else {
        # Code to create standard channel, no owner needed
    }
}

Closing

While dynamic parameters are a neat trick, they take quite a bit of code to generate. They can also be difficult for function runners to discover if they do not know the condition to make it appear. I would suggest using them sparingly and if no other reasonable option exists.

If you enjoyed this article, check out my other PowerShell posts.

Questions or comments? If so, leave a comment below or find me on Twitter or LinkedIn to discuss further.

Here is the full function code for reference:

function New-Channel {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]
        $ChannelName,
        [Parameter(Mandatory)]
        [ValidateSet('Standard','Private')]
        [string]
        $ChannelType
    )
    DynamicParam {
        if ($ChannelType -eq 'Private') {
            # Define parameter attributes
            $paramAttributes = New-Object -Type System.Management.Automation.ParameterAttribute
            $paramAttributes.Mandatory = $true
            $paramAttributesCollect = New-Object -Type `
                System.Collections.ObjectModel.Collection[System.Attribute]
            $paramAttributesCollect.Add($paramAttributes)
            $dynParam1 = New-Object -Type `
            System.Management.Automation.RuntimeDefinedParameter("PrivateChannelOwner", [string],
                $paramAttributesCollect)
            $paramDictionary = New-Object `
                -Type System.Management.Automation.RuntimeDefinedParameterDictionary
            $paramDictionary.Add("PrivateChannelOwner", $dynParam1)
            return $paramDictionary
        }
    }
    begin {
        $PrivateChannelOwner = $PSBoundParameters['PrivateChannelOwner']
    }
    process {
        if ($PrivateChannelOwner) {
            # Code to create private channel with owner
        }
        else {
            # Code to create standard channel, no owner needed
        }
    }
}