Simplify Your PowerShell Script with Parameter Validation

ScriptingGuy1

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