Simplify Your PowerShell Script with Parameter Validation

Simplify Your PowerShell Script with Parameter Validation

  • Comments 21
  • Likes

Summary: Learn how to simplify your Windows PowerShell Script by using parameter validation attribute.

Weekend Scripter

Microsoft Scripting Guy, Ed Wilson, is here. I thought I would ask Glenn Sizemore to write today’s blog about parameter validation attributes.

Photo of Glenn Sizemore

Glenn Sizemore is a technical marketing engineer in the Microsoft business unit at NetApp, where he combines Microsoft technologies with NetApp hardware by using Windows PowerShell. Glenn started scripting early in his IT career, and he has made a living off it ever since. Along the way, he started a blog, wrote a book, and is the proud father of two beautiful children.

The 2011 Scripting Games have come and gone, and almost 2000 scripts were written in this year’s contest. This resulted in a hundreds of thousands of lines of Windows PowerShell code, some better than others, but all executed to the authors best ability. Fortunately with Windows PowerShell, almost all mishaps were a result of the author simply not knowing about or how to use a feature within the Windows PowerShell language. Today, we will try to shed light on a feature that was introduced in Windows PowerShell 2.0: parameter validation.

Simply put, parameter validation is a means for Windows PowerShell to validate a parameter’s value before the body of the script or function is run. Often parameter validation can significantly clean up one’s code, while increasing performance. So let’s start by examining how you would validate a parameter in Windows PowerShell 1.0.

In this example, you have three parameters Name, Age, and Path. For the script to function correctly, you need the Name to be Tom, Dick, or Jane. The Age parameter must be between 21 and 65, and the Path parameter must point to a valid folder. In Windows PowerShell 1.0, your code would look something like this:

Function Foo
{
    Param(
        [String]
        $Name
    ,
        [Int]
        $Age
    ,
        [string]
        $Path
    )
    Process
    {
        If ("Tom","Dick","Jane" -NotContains $Name)
        {
            Throw "$($Name) is not a valid name! Please use Tom, Dick, Jane"
        }
        If ($age -lt 21 -OR $age -gt 65)
        {
            Throw "$($age) is not a between 21-65"
        }
        IF (-NOT (Test-Path $Path -PathType 'Container'))
        {
            Throw "$($Path) is not a valid folder"
        }
        # All parameters are valid so New-stuff"
        write-host "New-Foo"
    }
}

When executed, the end result appears to work perfectly, as shown here.

Image of command output

There are several drawbacks to this approach. First of all, you had to dedicate a chunk of the script to validating the value of each parameter. The validation is only as good as the script written to check it. There is also the issue of the error messages. These too, are only as good as written—and we all know how much we like to write good verbose messages. Even if you do write a good descriptive error message, it does not point to the problem in the code. The error points to the throw statement in the code, so it is not very intuitive. Finally, if you ever want to make any modifications to the accepted parameter values, you would have to change those values in multiple places within the script.

For all these reasons and more, the Windows PowerShell team introduced parameter validation. Let us take a look at the previous example with parameter validation.

Function Foo
{
    Param(
        [ValidateSet("Tom","Dick","Jane")]
        [String]
        $Name
    ,
        [ValidateRange(21,65)]
        [Int]
        $Age
    ,
        [ValidateScript({Test-Path $_ -PathType 'Container'})]
        [string]
        $Path
    )
    Process
    {
        write-host "New-Foo"
    }
}

First of all, you replace over 10 lines of code with three markup tags! More importantly, look at the output that is received when using the built-in validation tools:

Image of command output

Clean, verbose, and meaningful error messages are generated for free. You do not have to write anything! More importantly, if you wanted to adjust the accepted values, you only have to change them in one place. The toolbox available to you for parameter validation is vast and flexible. Currently, there are eleven dedicated parameter validation attributes that cover just about everything.

However, like everything in life, there are flaws in the system. For instance, if you assign a default value to a parameter, that value will not be validated because only passed parameters are validated. This is shown in the following example:

Function Foo
{
    Param(
        [Parameter(Mandatory=$false,ValueFromPipeline=$true)]
        [ValidateRange(21,65)]
        [Int]
        $Age = 95
    )
    Process
    {
        $age
    }
}

Intuition would imply that this code would throw an error if we attempt to use the default value—but remember, only user-supplied values are validated.

Image of command output

Although inconsistent, this is understandable because the validation attributes are only run during parameter binding, and the default value is processed after parameter binding has occurred. Of course, this really is not that big of a deal. You just have to know about the limitation, and script around it.

For example, if you want to test for the presence of a local registry key, you have two choices. You can validate the path again in the body of the script, or you can plan to encounter an error, as shown in the following example.

Function Foo
{
    Param(
        [Parameter(Mandatory=$false,ValueFromPipeline=$true)]
        [ValidateScript({Test-Path $_})]
        [String]
        $Key = 'HKLM:\Software\DoesNotExist'
    )
    Process
    {
        Try
        {
            Get-ItemProperty -Path $Key -EA 'Stop'
        }
        Catch
        {
            write-warning "Error accessing $Key: $($_.Exception.Message)"  
        }
    }
}

Of course, you should always assume that you will encounter an error, and write code that can survive the error. But the lesson here is if you implement a default value, remember that it is not being validated.

Another scenario where you would implement an additional check is if you needed to combine several parameters. For instance, if you had a script that took two parameters, Directory and FileName, and you combine them later in the script, you can’t test for their combined value by using the current validation attributes.

At first glance, one would think that the ValidateScript tag could simply access the values in sister parameters to perform more advanced validation. Alas, this is not possible. Under the covers, ValidateScript is a Where-Object script block that isn’t scoped to access any other parameters. These limitations aside, I highly encourage you to read about_Functions_Advanced_Parameters and start using these incredibly powerful attributes in your day-to-day scripting.

Remember that the Scripting Games are all about honing your scripting technique to make you better at your real job. If you don’t take what you learned this year and start using it, you will have to learn it again next year. If you do apply all the tips and tricks from this year, and the year after, and the year after that… well, that’s how you become a script ninja!

Happy scripting!

Thank you, Glenn. I love your comparison example of the v1 methods and the v2 methodology. Parameter validation attributes rock—and so do you! Great blog.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • Excellent! Best one page desciption of validation I have seen.  It reads easy and is instantly useful.  Link to help also a good idea.

  • Hello Glenn,

    a great articel about one of my next learning points!!!

    I've got to tell you, that we would be best friend, because of

    1) the scripting games 2010, of course :-)

    2) this sentence: "Of course, you should always assume that you will encounter an error, and write code that can survive the error"

    3) and that sentence "If you don’t take what you learned this year and start using it, you will have to learn it again next year"

    So I donÄ't have to say anything about the scripting games, error handling and what I'll try to do this time!

    kind regards, Klaus

  • Thanks a lot, Glenn. An important topic described wonderfully simply :-)

  • Aside from altering the $ErrorActionPreference is there a way to handle errors that results from failed validation?  For instance, if I have a non-terminating error, how would I be able to continue processing and just note the issue to an ErrorVariable?

  • @Will Steele You can also set -erroractionpreference (parameter alias -EA) on each PowerShell cmdlet. You also have the Try / Catch / Finally construction that can be used for structured error handling. Look at the PowerShell / Error Handling tags on the Hey Scripting Guy blog for some articles around these topics.

  • Don't forget ErrorVariable!

    help common

    # list non-existent folder causing an error

    Get-Content xxxx -ea SilentlyContinue -ev +MyError

    $MyError

    Look Ma! No red lines anymore.  My errors have turned white.

    Good Luck and have fun.

  • @JRV Error variable is a good one. Great suggestion.

  • This is great.  Can you additional add in text to decribe what the parameters are for?

    ie.

    0 - extracts data for people data

    1 - extracts data for vendor data

  • function Test-Msg{

        Param(

             [Parameter(

                  Mandatory=$true,

                  HelpMessage='Count of things in the world'

         )][string]$count

         )

       # body here

    }

    Test-Msg # and follow instructions on the screen

  • Is that example for PS2? As am geting error if I use [ValidateRange(21,65)]

  • #requires -version 2.0

    function t{

      param(

         [ValidateRange(21,65)]$x

      )

    $x

    }

    t 3 # fails

    t 21 # succeeds

  • Great post - your notes came in handy today :)

  • Awesome for param.

  • Why does the following produce a warning?

    Set-StrictMode -Version 3.0

    Param (

       [Parameter()]$i = 1,

       [Parameter()]$j = 2

    )

    Write-Host "i = $i; j = $j"

    +     [Parameter()][int]$i = 1,

    +                            ~

    Assignment expression is not valid. The left hand side of an assignment operator needs to be something that can be assigned to like a variable or a property.

    Is multiple assignment in Param expressions illegal?

  • @Anon

    Param (

      [Parameter()]$i = 1,

      [Parameter()]$j = 2

    )

    Set-StrictMode -Version 3.0

    Write-Host "i = $i; j = $j"

    You cannot place any code before a 'Param' statement except "#requires -version n.n" or other comments