Always Explicitly Set Your Parameter Set Variables For PowerShell v2.0 Compatibility
What are parameter sets anyways?
Parameter sets were introduced in PowerShell v2.0 and are useful for enforcing mutually exclusive parameters on a cmdlet. Ed Wilson has a good little article explaining what parameter sets are and how to use them. Essentially they allow us to write a single cmdlet that might otherwise have to be written as 2 or more cmdlets that took different parameters. For example, instead of having to create Process-InfoFromUser, Process-InfoFromFile, and Process-InfoFromUrl cmdlets, we could create a single Process-Info cmdlet that has 3 mutually exclusive parameters, [switch]$PromptUser, [string]$FilePath, and [string]$Url. If the cmdlet is called with more than one of these parameters, it throws an error.
You could just be lazy and not use parameter sets and allow all 3 parameters to be specified and then just use the first one, but the user won’t know which one of the 3 they provided will be used; they might assume that all 3 will be used. This would also force the user to have to read the documentation (assuming you have provided it). Using parameter sets enforces makes it clear to the user which parameters are able to be used with other parameters. Also, most PowerShell editors process parameter sets to have the intellisense properly show the parameters that can be used with each other.
Ok, parameter sets sound awesome, I want to use them! What’s the problem?
The problem I ran into was in my Invoke-MsBuild module that I put on CodePlex, I had a [switch]$PassThru parameter that was part of a parameter set. Within the module I had:
if ($PassThru) { do something... }
else { do something else... }
This worked great for me during my testing since I was using PowerShell v3.0. The problem arose once I released my code to the public; I received an issue from a user who was getting the following error message:
Invoke-MsBuild : Unexpected error occurred while building “[path]\my.csproj”: The variable ‘$PassThru’ cannot be retrieved because it has not been set.
At build.ps1:84 char:25
- $result = Invoke-MsBuild «« -Path “[path]\my.csproj” -BuildLogDirectoryPath “$scriptPath” -Params “/property:Configuration=Release”
After some investigation I determined the problem was that they were using PowerShell v2.0, and that my script uses Strict Mode. I use Set-StrictMode -Version Latest in all of my scripts to help me catch any syntax related errors and to make sure my scripts will in fact do what I intend them to do. While you could simply not use strict mode and you wouldn’t have a problem, I don’t recommend that; if others are going to call your cmdlet (or you call it from a different script), there’s a good chance they may have Strict Mode turned on and your cmdlet may break for them.
So should I not use parameter sets with PowerShell v2.0? Is there a fix?
You absolutely SHOULD use parameter sets whenever you can and it makes sense, and yes there is a fix. If you require your script to run on PowerShell v2.0, there is just one extra step you need to take, which is to explicitly set the values for any parameters that use a parameter set and don’t exist. Luckily we can use the Test-Path cmdlet to test if a variable has been defined in a specific scope or not.
Here is an example of how to detect if a variable is not defined in the Private scope and set its default value. We specify the scope in case a variable with the same name exists outside of the cmdlet in the global scope or an inherited scope.
# Default the ParameterSet variables that may not have been set depending on which parameter set is being used. This is required for PowerShell v2.0 compatibility.
if (!(Test-Path Variable:Private:SomeStringParameter)) { $SomeStringParameter = $null }
if (!(Test-Path Variable:Private:SomeIntegerParameter)) { $SomeIntegerParameter = 0 }
if (!(Test-Path Variable:Private:SomeSwitchParameter)) { $SomeSwitchParameter = $false }
If you prefer, instead of setting a default value for the parameter you could just check if it is defined first when using it in your script. I like this approach however, because I can put this code right after my cmdlet parameters so I’m modifying all of my parameter set properties in one place, and I don’t have to remember to check if the variable is defined later when writing the body of my cmdlet; otherwise I’m likely to forget to do the “is defined” check, and will likely miss the problem since I do most of my testing in PowerShell v3.0.
Another approach rather than checking if a parameter is defined or not, is to check which Parameter Set Name is being used; this will implicitly let you know which parameters are defined.
switch ($PsCmdlet.ParameterSetName)
{
"SomeParameterSetName" { Write-Host "You supplied the Some variable."; break}
"OtherParameterSetName" { Write-Host "You supplied the Other variable."; break}
}
I still prefer to default all of my parameters, but you may prefer this method.
I hope you find this useful. Check out my other article for more PowerShell v2.0 vs. v3.0 differences.
Happy coding!
Comments
Victor Vogelpoel
“Set-StrictMode -Version Latest” throws an error for a variable that you use but is not set. A parameter variable from a non-active parameterset is undefined and thus triggers the error “The variable ‘$PassThru’ cannot be retrieved because it has not been set.”
Using “Test-Path Variable:PassThru” is tricky because you don’t specify a scope. If the function calling your function already has variable $PassTru defined, your function may return the variable from the calling scoped function instead from your local function scope!
if you use [CmdLetBinding()], PowerShell also gives you the dictionary $PSBoundParameters of parameters bound while calling the function. Using “$PSBoundParameters.ContainsKey(“PassThru”), you can check if the parameter PassTrue was passed (regardless of the parameterset).
deadlydog
@Victor Vogelpoel Thanks for the info Victor; I hadn’t thought about scope. Luckily we are able to specify scope using the Test-Path Variable method though, so I’ve updated my code to look for the variable in the Private scope.
I hadn’t heard of $PSBoundParameters before, so it’s nice to know about it. From this article though (http://blogs.msdn.com/b/powershell/archive/2009/04/06/checking-for-bound-parameters.aspx) it looks like it only contains keys for parameters that were actually passed by the caller; so the parameter may actually be defined with a default value, but not exist in $PSBoundParameters. Thanks none-the-less :)
Leave a Comment
Your email address will not be published. Required fields are marked *