PowerShell How-To

How To Implement Dynamic Parameters in Your PowerShell Functions

Choosing the correct input for your functions will save time and energy in the long run.

Allowing for the right kind of input into PowerShell functions is important. It can mean the difference between writing a function once and forgetting about it constantly having to refactor it to account for situations that you never thought of. Taking a step back before building that function and clearly defining what parameters should be added is an important part of function design.

There are numerous ways this can happen. You can create lots of parameters in one big parameter set, or you can create different parameter sets to account for different input scenarios. These are the "typical" ways to create parameters. Even though you can do a lot with advanced parameters with parameter attributes, there comes a time when you need the function parameters to be a little more dynamic.

For example, have you ever had a time when you needed one parameter to depend on another? Or, perhaps, you wanted to implement the ValidateSet parameter attribute but needed it to be some dynamic list rather than a set of static strings. These and many scenarios can be addressed by using dynamic parameters.

Dynamic parameters are different. Usually, a parameter is defined in code and is immediately available to be used. Dynamic parameters, on the other hand, are only available at run time. They are actually built on the fly when calling the function. This allows you some opportunities to fine-tune parameters.

Let's start off with a simple function with a single parameter. This is a simple function that I might use to quickly find some kind of configuration file.

function Get-ConfigurationFile
{
[OutputType([System.IO.FileInfo])]
[CmdletBinding()]
param
(
[Parameter(Mandatory)]
[string]$FileName
)

            $configFileFolder = 'C:\ConfigurationFiles'
Get-ChildItem -Path $configFileFolder -Filter "$FileName.txt"
}

Perhaps I've got some configuration files in my folder called COMPUTER1.txt and COMPUTER2.txt.

[Click on image for larger view.] Figure 1.

I can now find these configuration files by executing Get-ConfigurationFile.

Get-ConfigurationFile –FileName COMPUTER1

[Click on image for larger view.] Figure 2.

Perhaps I've got hundreds of these configuration files in this folder and try to get the configuration file for COMPUTER3? It will just not return anything. To make this easier, I'd like to be able to "browse" all of the files that I could choose that are valid. I could use the ValidateSet parameter attribute on the FileName parameter, but that'd mean I have to have a static list of all configuration files. By the time I'm done, there could be more files placed into that folder. This isn't going to work. Let's make FileName a dynamic parameter instead allowing us to "browse" (in real-time) all of the files I'm capable of using.

To do this, I'll first remove the FileName parameter I added early and add the DynamicParam block to my function.

function Get-ConfigurationFile
{
[OutputType([System.IO.FileInfo])]
[CmdletBinding()]
param
()
DynamicParam {

            }
            process {
$configFileFolder = 'C:\ConfigurationFiles'
Get-ChildItem -Path $configFileFolder -Filter "$FileName.txt"
}
}

Also, notice that I had to add a process block as well. This is required when using dynamic parameters.

A dynamic parameter consists of four different objects:

  • Parameter Attribute
  • Attribute Collection
  • Runtime Defined Parameter
  • Runtime Defined Parameter Dictionary

I'll first create the parameter attribute.

$ParamAttrib = New-Object  System.Management.Automation.ParameterAttribute
$ParamAttrib.Mandatory = $true
$ParamAttrib.ParameterSetName = '__AllParameterSets'

Notice that I'm making this parameter mandatory again and since I'm not using any parameter sets, I'm just making it available everywhere. This is default behavior when using static parameters.

Next, I'll create the attribute collection, defined my dynamic ValidateSet and add the parameter attribute I just added to the collection.

$AttribColl = New-Object  System.Collections.ObjectModel.Collection[System.Attribute]
$AttribColl.Add($ParamAttrib)
$configurationFileNames = Get-ChildItem -Path  'C:\ConfigurationFiles' | Select-Object -ExpandProperty Name
$AttribColl.Add((New-Object  System.Management.Automation.ValidateSetAttribute($configurationFileNames)))

I can then set the runtime defined parameter providing the name of the parameter (FileName), it's type (string) and the attribute collection I just built.

$RuntimeParam = New-Object  System.Management.Automation.RuntimeDefinedParameter('FileName', [string],  $AttribColl)

Finally, I'll create a runtime dictionary and add this runtime parameter to it again using the name of the parameter (FileName).

$RuntimeParamDic = New-Object  System.Management.Automation.RuntimeDefinedParameterDictionary
$RuntimeParamDic.Add('FileName', $RuntimeParam)

return $RuntimeParamDic

All of this makes a function that now looks like this:

function Get-ConfigurationFile
{
[OutputType([System.IO.FileInfo])]
[CmdletBinding()]
param
()
DynamicParam
{         
$ParamAttrib = New-Object System.Management.Automation.ParameterAttribute
$ParamAttrib.Mandatory = $true
$ParamAttrib.ParameterSetName = '__AllParameterSets'

$AttribColl = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$AttribColl.Add($ParamAttrib)

                        $configurationFileNames = Get-ChildItem -Path 'C:\ConfigurationFiles' | Select-Object -ExpandProperty Name

                        $AttribColl.Add((New-Object System.Management.Automation.ValidateSetAttribute($configurationFileNames)))

$RuntimeParam = New-Object System.Management.Automation.RuntimeDefinedParameter('FileName', [string], $AttribColl)

$RuntimeParamDic = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
$RuntimeParamDic.Add('FileName', $RuntimeParam)

return $RuntimeParamDic
}
process
{
$configFileFolder = 'C:\ConfigurationFiles'
Get-ChildItem -Path $configFileFolder -Filter "$($PSBoundParameters.FileName).txt"         
}
}

Also, notice that I have to change the $FileName reference to $PSBoundParameters.FileName. This is because a dynamic parameter does not automatically assign a variable to a bound parameter so we're forced to be more explicit.

That's it!  Now if you try to run Get-ConfigurationFile, type –FileName as you normally would but then begin hitting tab immediately after, you'll find that you can cycle through all of the files that existing in the C:\ConfigurationFiles directory.

This was just one use case of dynamic parameters. Inside of the DynamicParam block, you can run any code you'd like here. The sky's the limit! What other use cases do you have for using dynamic parameters?

About the Author

Adam Bertram is a 20-year veteran of IT. He's an automation engineer, blogger, consultant, freelance writer, Pluralsight course author and content marketing advisor to multiple technology companies. Adam also founded the popular TechSnips e-learning platform. He mainly focuses on DevOps, system management and automation technologies, as well as various cloud platforms mostly in the Microsoft space. He is a Microsoft Cloud and Datacenter Management MVP who absorbs knowledge from the IT field and explains it in an easy-to-understand fashion. Catch up on Adam's articles at adamtheautomator.com, connect on LinkedIn or follow him on Twitter at @adbertram or the TechSnips Twitter account @techsnips_io.


comments powered by Disqus
Most   Popular