Archive

Posts Tagged ‘Version’

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!

PowerShell Code To Ensure Client Is Using At Least The Minimum Required PowerShell Version

October 25th, 2013 3 comments

Here’s some simple code that will throw an exception if the client running your script is not using the version of PowerShell (or greater) that is required; just change the $REQUIRED_POWERSHELL_VERSION variable value to the minimum version that the script requires.

# Throw an exception if client is not using the minimum required PowerShell version.
$REQUIRED_POWERSHELL_VERSION = 3.0	# The minimum Major.Minor PowerShell version that is required for the script to run.
$POWERSHELL_VERSION = $PSVersionTable.PSVersion.Major + ($PSVersionTable.PSVersion.Minor / 10)
if ($REQUIRED_POWERSHELL_VERSION -gt $POWERSHELL_VERSION)
{ throw "PowerShell version $REQUIRED_POWERSHELL_VERSION is required for this script; You are only running version $POWERSHELL_VERSION. Please update PowerShell to at least version $REQUIRED_POWERSHELL_VERSION." }

— UPDATE {

Thanks to Robin M for pointing out that PowerShell has the built-in #Requires statement for this purpose, so you do not need to use the code above. Instead, simply place the following code anywhere in your script to enforce the desired PowerShell version required to run the script:

#Requires -Version 3.0

If the user does not have the minimum required version of PowerShell installed, they will see an error message like this:

The script ‘foo.ps1’ cannot be run because it contained a "#requires" statement at line 1 for Windows PowerShell version 3.0 which is incompatible with the installed Windows PowerShell version of 2.0.

} UPDATE —

So if your script requires, for example, PowerShell v3.0, just put this at the start of your script to have it error out right away with a meaningful error message; otherwise your script may throw other errors that mask the real issue, potentially leading the user to spend many hours troubleshooting your script, or to give up on it all together.

I’ve been bitten by this in the past a few times now, where people report issues on my Codeplex scripts where the error message seems ambiguous.  So now any scripts that I release to the general public will have this check in it to give them a proper error message.  I have also created a page on PowerShell v2 vs. v3 differences that I’m going to use to keep track of the differences that I encounter, so that I can have confidence in the minimum powershell version that I set on my scripts.  I also plan on creating a v3 vs. v4 page once I start using PS v4 features more.  Of course, the best test is to actually run your script in the minimum powershell version that you set, which I mention how to do on my PS v2 vs. v3 page.

Happy coding!

PowerShell 2.0 vs. 3.0 Syntax Differences And More

October 22nd, 2013 1 comment

I’m fortunate enough to work for a great company that tries to stay ahead of the curve and use newer technologies.  This means that when I’m writing my PowerShell (PS) scripts I typically don’t have to worry about only using PS v2.0 compatible syntax and cmdlets, as all of our PCs have v3.0 (soon to have v4.0).  This is great, until I release these scripts (or snippets from the scripts) for the general public to use; I have to keep in mind that many other people are still stuck running older versions of Windows, or not allowed to upgrade PowerShell.  So to help myself release PS v2.0 compatible scripts to the general public, I’m going to use this as a living document of the differences between PowerShell 2.0 and 3.0 that I encounter (so it will continue to grow over time; read as, bookmark it).  Of course there are other sites that have some of this info, but I’m going to try and compile a list of the ones that are relevant to me, in a nice simple format.

Before we get to the differences, here are some things you may want to know relating to PowerShell versions.

How to check which version of PowerShell you are running

All PS versions:

$PSVersionTable.PSVersion

 

How to run/test your script against an older version of PowerShell (source)

All PS versions:  use PowerShell.exe –Version [version] to start a new PowerShell session, where [version] is the PowerShell version that you want the session to use, then run your script in this new session.  Shorthand is PowerShell –v [version]

PowerShell.exe -Version 2.0

Note: You can’t run PowerShell ISE in an older version of PowerShell; only the Windows PowerShell console.

 

PowerShell v2 and v3 Differences:

 

Where-Object no longer requires braces (source)

PS v2.0:

Get-Service | Where { $_.Status -eq ‘running’ }

PS v3.0:

Get-Service | Where Status -eq ‘running

PS V2.0 Error Message:

Where : Cannot bind parameter ‘FilterScript’. Cannot convert the “[PropertyName]” value of the type “[Type]” to type “System.Management.Automation.ScriptBlock”.

 

Using local variables in remote sessions (source)

PS v2.0:

$class = "win32_bios"
Invoke-Command -cn dc3 {param($class) gwmi -class $class} -ArgumentList $class

PS v3.0:

$class = "win32_bios"
Invoke-Command -cn dc3 {gwmi -class $Using:class}

 

Variable validation attributes (source)

PS v2.0: Validation only available on cmdlet/function/script parameters.

PS v3.0: Validation available on cmdlet/function/script parameters, and on variables.

[ValidateRange(1,5)][int]$someLocalVariable = 1

 

Stream redirection (source)

The Windows PowerShell redirection operators use the following characters to represent each output type:
        *   All output
        1   Success output
        2   Errors
        3   Warning messages
        4   Verbose output
        5   Debug messages

NOTE: The All (*), Warning (3), Verbose (4) and Debug (5) redirection operators were introduced
       in Windows PowerShell 3.0. They do not work in earlier versions of Windows PowerShell.

 

PS v2.0: Could only redirect Success and Error output.

# Sends errors (2) and success output (1) to the success output stream.
Get-Process none, Powershell 2>&1

PS v3.0: Can also redirect Warning, Verbose, Debug, and All output.

# Function to generate each kind of output.
function Test-Output { Get-Process PowerShell, none; Write-Warning "Test!"; Write-Verbose "Test Verbose"; Write-Debug "Test Debug"}

# Write every output stream to a text file.
Test-Output *> Test-Output.txt

 

Explicitly set parameter set variable values when not defined (source)

PS v2.0 will throw an error if you try and access a parameter set parameter that has not been defined.  The solution is to give it a default value when it is not defined. Specify the Private scope in case a variable with the same name exists 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 }

PS v2.0 Error Message:

The variable ‘$[VariableName]’ cannot be retrieved because it has not been set.

 

Parameter attributes require the equals sign

PS v2.0:

[parameter(Position=1,Mandatory=$true)] [string] $SomeParameter

PS v3.0:

[parameter(Position=1,Mandatory)] [string] $SomeParameter

PS v2.0 Error Message:

The “=” operator is missing after a named argument.

 

Cannot use String.IsNullOrWhitespace (or any other post .Net 3.5 functionality)

PS v2.0:

[string]::IsNullOrEmpty($SomeString)

PS v3.0:

[string]::IsNullOrWhiteSpace($SomeString)

PS v2.0 Error Message:

IsNullOrWhitespace : Method invocation failed because [System.String] doesn’t contain a method named ‘IsNullOrWhiteSpace’.

PS v2.0 compatible version of IsNullOrWhitespace function:

# PowerShell v2.0 compatible version of [string]::IsNullOrWhitespace.
function StringIsNullOrWhitespace([string] $string)
{
    if ($string -ne $null) { $string = $string.Trim() }
    return [string]::IsNullOrEmpty($string)
}

 

Get-ChildItem cmdlet’s –Directory and –File switches were introduced in PS v3.0

PS v2.0:

Get-ChildItem -Path $somePath | Where-Object { $_.PSIsContainer }	# Get directories only.
Get-ChildItem -Path $somePath | Where-Object { !$_.PSIsContainer }	# Get files only.

PS v3.0:

Get-ChildItem -Path $somePath -Directory
Get-ChildItem -Path $somePath -File

 

 

Other Links

Force your ClickOnce app to update without prompting user – Now on NuGet!

March 8th, 2013 4 comments

A while ago I blogged about a powershell script I made that would automatically update your ClickOnce project file’s Minimum Required Version to the latest version of your application so that users would not be presented with the “There is an update for this application. Do you want to install it?” prompt; instead the application would just automatically download and install the update.  This is nice because it’s one less prompt the end-user has to see, and from a security standpoint it means that your users will be forced to always use the latest version of the app.

There was a bit of setup to get this to work.  You had to make sure the powershell script was in the proper directory and add a post-build event to your project.  I’m happy to announce that I’ve significantly updated the powershell script with more features and bug fixes, and you can now install it from Nuget, which will handle all of the setup for you.  So now making sure that your end-users are always using the latest version of your application is as easy as adding the AutoUpdateProjectsMinimumRequiredClickOnceVersion NuGet package to your ClickOnce project.

ManageNuget

Install

FileAdded

As you can see in the last screenshot, it adds a new “PostBuildScripts” folder to your project that contains the powershell script that is ran from the project’s post-build event.

A couple caveats to note.  Because this uses PowerShell it means that it can only be ran on Windows machines (sorry Mac and Unix).  Also, because the powershell script modifies the .csproj/.vbproj file outside of Visual Studio, the first time you do a build after creating a new ClickOnce version, if you have any files from that project open you will be prompted to reload the project.  In order to prevent this from closing your open tabs, I recommend installing Scott Hanselman’s Workspace Reloader Visual Studio extension.

I’ve also created a CodePlex site to host this open source project.

I hope you find this helpful.  Feel free to leave a comment.