Archive

Posts Tagged ‘Variable’

Always Explicitly Set Your Parameter Set Variables For PowerShell v2.0 Compatibility

October 28th, 2013 2 comments

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 : Unexpect error occured 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" -Pa

    rams "/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!

Accessing PowerShell Properties and Variables with Periods (and other special characters) in their Name

September 5th, 2013 No comments

TL;DR

If your PowerShell variable name contains special characters, wrap it in curly braces to get/set its value.  If your PowerShell property name contains special characters, wrap it in double quotes:

# Variable name with special characters
$VariableName.That.Contains.Periods			# This will NOT work.
${VariableName.That.Contains.Periods}		# This will work.

$env:ProgramFiles(x86)			# This will NOT work, because parentheses are special characters.
${env:ProgramFiles(x86)}		# This will work.

# Property name with special characters
$SomeObject.APropertyName.That.ContainsPeriods		# This will NOT work.
$SomeObject.{APropertyName.That.ContainsPeriods}	# This will work.
$SomeObject.'APropertyName.That.ContainsPeriods'	# This will also work.
$SomeObject."APropertyName.That.ContainsPeriods"	# This will work too.

# Property name with special characters stored in a variable
$APropertyNameWithSpecialCharacters = 'APropertyName.That.ContainsPeriods'
$SomeObject.$APropertyNameWithSpecialCharacters		# This will NOT work.
$SomeObject.{$APropertyNameWithSpecialCharacters}	# This will NOT work.
$SomeObject.($APropertynameWithSpecialCharacters)	# This will work.
$SomeObject."$APropertynameWithSpecialCharacters"	# This will also work.
$SomeObject.'$APropertynameWithSpecialCharacters'	# This will NOT work.

 

More Information

I was recently working on a powershell script to get the values of some entries in the registry.  This is simple enough:

Get-ItemProperty -Path 'HKCU:\Software\Microsoft\VisualStudio\11.0_Config\TeamFoundation\SourceControl\Checkin Policies' -Name 'TF.iQmetrix.CheckinPolicies'

If we run this command, this is what we get back:

TF.iQmetrix.CheckinPolicies : C:\Users\Dan Schroeder\AppData\Local\Microsoft\VisualStudio\11.0\Extensions\mwlu1noz.4t5\TF.iQmetrix.CheckinPolicies.dll
PSPath                      : Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\11.0_Config\TeamFoundation\SourceControl\Checkin Policies
PSParentPath                : Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\11.0_Config\TeamFoundation\SourceControl
PSChildName                 : Checkin Policies
PSDrive                     : HKCU
PSProvider                  : Microsoft.PowerShell.Core\Registry

So the actual value I’m after is stored in the “TF.iQmetrix.CheckinPolicies” property of the object returned by Get-ItemProperty; notice that this property name has periods in it.  So let’s store this object in a variable to make it easier to access it’s properties, and do a quick Get-Member on it just to show some more details:

$RegistryEntry = Get-ItemProperty -Path 'HKCU:\Software\Microsoft\VisualStudio\11.0_Config\TeamFoundation\SourceControl\Checkin Policies' -Name 'TF.iQmetrix.CheckinPolicies'
$RegistryEntry | Get-Member

And this is what Get-Member shows us:

   TypeName: System.Management.Automation.PSCustomObject

Name                        MemberType   Definition                                                                                                                                                          
----                        ----------   ----------                                                                                                                                                          
Equals                      Method       bool Equals(System.Object obj)                                                                                                                                      
GetHashCode                 Method       int GetHashCode()                                                                                                                                                   
GetType                     Method       type GetType()                                                                                                                                                      
ToString                    Method       string ToString()                                                                                                                                                   
PSChildName                 NoteProperty System.String PSChildName=Checkin Policies                                                                                                                          
PSDrive                     NoteProperty System.Management.Automation.PSDriveInfo PSDrive=HKCU                                                                                                               
PSParentPath                NoteProperty System.String PSParentPath=Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\11.0_Config\TeamFoundation\SourceControl           
PSPath                      NoteProperty System.String PSPath=Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\11.0_Config\TeamFoundation\SourceControl\Checkin Policies
PSProvider                  NoteProperty System.Management.Automation.ProviderInfo PSProvider=Microsoft.PowerShell.Core\Registry                                                                             
TF.iQmetrix.CheckinPolicies NoteProperty System.String TF.iQmetrix.CheckinPolicies=C:\Users\Dan Schroeder\AppData\Local\Microsoft\VisualStudio\11.0\Extensions\mwlu1noz.4t5\TF.iQmetrix.CheckinPolicies.dll 

 

So in PowerShell ISE I type “$RegistryEntry.” and intellisense pops up showing me that TF.iQmetrix.CheckinPolicies is indeed a property on this object that I can access.

PowerShell ISE Intellisense

So I try and display the value of that property to the console using:

$RegistryEntry = Get-ItemProperty -Path 'HKCU:\Software\Microsoft\VisualStudio\11.0_Config\TeamFoundation\SourceControl\Checkin Policies' -Name 'TF.iQmetrix.CheckinPolicies'
$RegistryEntry.TF.iQmetrix.CheckinPolicies

But nothing is displayed Sad smile

While PowerShell ISE does color-code the line “$RegistryEntry.TF.iQmetrix.CheckinPolicies” to have the object color different than the property color, if you just look at it in plain text, something clearly looks off about it.  How does PowerShell know that the property name is “TF.iQmetrix.CheckinPolicies”, and not that “TF” is a property with an “iQmetrix” property on it, with a “CheckinPolicies” property on that.  Well, it doesn’t.

I did some Googling and looked on StackOverflow, but couldn’t a solution to this problem.  I found slightly related posts involving environmental variables with periods in their name, but that solution did not work in this case.  So after some random trial-and-error I stumbled onto the solution.  You have to wrap the property name in curly braces:

$RegistryEntry.TF.iQmetrix.CheckinPolicies		# This is WRONG. Nothing will be returned.
$RegistryEntry.{TF.iQmetrix.CheckinPolicies}	# This is RIGHT. The property's value will returned.

 

I later refactored my script to store the “TF.iQmetrix.CheckinPolicies” name in a variable and found that I couldn’t use the curly braces anymore.  After more trial-and-error I discovered that using parentheses instead works:

$EntryName = 'TF.iQmetrix.CheckinPolicies'

$RegistryEntry.$EntryName		# This is WRONG. Nothing will be returned.
$RegistryEntry.{$EntryName}		# This is WRONG. Nothing will be returned.
$RegistryEntry.($EntryName)		# This is RIGHT. The property's value will be returned.
$RegistryEntry."$EntryName"		# This is RIGHT too. The property's value will be returned.

 

So there you have it.  If for some reason you have a variable or property name that contains periods, wrap it in curly braces, or parenthesis if you are storing it in a variable.

Hopefully this makes it’s way to the top of the Google search results so you don’t waste as much time on it as I did.

Happy coding!