This week, while contributing code to a collaborative project, I wanted to up my game. A lot of my tools over the years have used parameters and validation, but one of the more elusive things is creating parameters that have parameter validation criteria set at run-time. Advanced Parameters covers this nicely, including our friends ValidateSet, ValidatePattern, AllowNullOrEmpty, and ValidateLength (among many others).
But, how do I get generated values into ValidateSet?
That's where we can use a dynamic parameter.
Consider this function, where I want to populate the potential values of a parameter with the available phone numbers in a Skype Tenant:
[CmdletBinding()] Param ( [string]$OtherParameter ) DynamicParam { # Set up the Run-Time Parameter Dictionary $RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary # Begin dynamic parameter definition $ParamName_LineUri = 'LineUri' $AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute $ParameterAttribute.Mandatory = $true $ParameterAttribute.Position = 0 $AttributeCollection.Add($ParameterAttribute) $ValidationValues = Get-CsOnlineTelephoneNumber -IsNotAssigned | Select -ExpandProperty Id $ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($ValidationValues) $AttributeCollection.Add($ValidateSetAttribute) $RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParamName_LineUri, [string], $AttributeCollection) $RuntimeParameterDictionary.Add($ParamName_LineUri, $RuntimeParameter) # End Dynamic parameter definition # When done building dynamic parameters, return return $RuntimeParameterDictionary } begin {} process { Write-Host $PSBoundParameters[$LineUri] } end {}
Now, looking at this, it may seem like a bunch of gibberish, but I assure you, it has meaning. Let's dissect it one line at a time:
$RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
Here, we are creating a new management object ($RuntimeParameterDictionary) that's going to contain all of the run-time (dynamic) parameters and their resultant values. The dictionary is just that--a list of words (in grammatical vernacular, words or terms to define; in scripting, as keys) and their associated definitions (values).
$ParamName_LineUri = 'LineUri'
As the variable name indicates, it's what we're going to name the parameter. $ParamName_LineUri is the variable name containing the name of the dynamic variable, 'LineUri.'
$AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
With this line, we're creating an object to hold the parameter attributes (such as Mandatory and Position) as well as the array that will become the ValidateSet values.
$ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
Defining the Parameter Attribute value object. This is the object that holds the parameter attributes (such as Mandatory, Position, and HelpMessage).
$ParameterAttribute.Mandatory = $true $ParameterAttribute.Position = 0
Here, we are adding the properties we want to define for the Parameter attribute. We've specified the attribute's Mandatory property as well as its position.
$AttributeCollection.Add($ParameterAttribute)
Now that we have the parameter attributes defined, we're going to add them to the $AttributeCollection object. As mentioned earlier, it's the container object that's going to hold the settings for the parameter.
[array]$ValidationValues = Get-CsOnlineTelephoneNumber -IsNotAssigned | Select -ExpandProperty Id
Appropriately named, $ValidationValues is an array object that is array object that is going to hold all of the potential values that we're going to use as our validation set. In this example, I'm running Get-CsOnlineTelephoneNumber -IsNotAssigned to get a list of unassigned numbers in my tenant, and then selecting the property Id (which contains the actual telephone number). The resulting object is just an array of phone numbers.
$ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($ValidationValues)
Now, we're creating an actual ValidateSetAttribute object type, using the array of $ValidationValues (telephone numbers in this case) that we just created.
$AttributeCollection.Add($ValidateSetAttribute)
Once we have created a ValidateSetAttribute object, we can go ahead and add it to the $AttributeCollection object.
$RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParamName_LineUri, [string], $AttributeCollection)
At this point, we're ready to create the parameter: we're going to create a new RuntimeDefinedParameter called $RuntimeParameter (since I'm so very creative), and to it, we're going add $ParamName_LineUri to be the name of the attribute, define the value type as a [string], and then use the values stored in $AttributeCollection (the array of validation values stored in $ValidateSetAttribute and the parameter settings stored in $ParameterAttribute) to finish it out.
$RuntimeParameterDictionary.Add($ParamName_LineUri, $RuntimeParameter)
And finally, we're going to add that to the dictionary (using the value in $ParamName_LineUri as the attribute name, and the object $RuntimeParameter as the parameter settings).
process { Write-Host $PSBoundParameters[$LineUri] }
Once you begin processing, you can reference the dynamic parameters using the $PSBoundParameters automatic variable and the [$Variable] selector to choose the dynamic parameter you built. You can add more dynamic parameters by repeating the section of code between the #Begin and #End dynamic parameter definition tags.
Now that you've created that, you can use it in a script (such as part of a function). Since they function like ValidateSet parameters, you can tab-complete them, making it a very slick-looking function.