ValidateScript for Beginners

Thereâ€™s been a lot of chatter about in Scripting Games 2013 blog posts about the ValidateScript attribute. The chatter is, appropriately, confined to the advanced events â€“ this sort of thing is not expected in a one-liner. But I thought Iâ€™d take a minute and demystify it â€“ and discuss an issue that it raises about when input should be rejected.

What is ValidateScript?

ValidateScript and its siblings are parameter validation attributes. These attributes are statements that are added to the parameter definition. They tell Windows PowerShell to examine the parameter values that are used when the function is called and determine whether the parameter values meet some specified conditions. In particular, ValidateScript lets you write a script block to test the conditions that the values must satisfy. Windows PowerShell runs the validation script on the parameter values and, if the script returns $False, it throws a terminating error.

Before we get to the details, letâ€™s talk about why youâ€™d want to use something like this. The answer is simplicity. â€œWhat!!?!,â€ you say, incredulously? The syntax of this thing looks like a sampler of Windows PowerShell enclosures. Thereâ€™s a square bracket â€œ[â€œ or two â€œ]â€, a pair of parentheses â€œ( )â€ and even some curly braces â€œ{Â }â€. So it doesnâ€™t look simple.

But once you get over the syntax, you realize that putting the parameter value validation into the parameter definition means that you donâ€™t need to test the parameter value in your script. Instead, the Windows PowerShell engine tests the parameter value and you can use the script to do scripty things.

Using ValidateScript

Hereâ€™s what I mean. Hereâ€™s a silly function that will serve as our example.

The Get-EventDate function has a $EventDate parameter. If the value of the $EventDate parameter is a DateTime object and itâ€™s later than now, the function writes a nice sentence with the date to the console or host program. But, if the value of $EventDate is not a DateTime object, or itâ€™s not a future date, the function generates an error. (To be complete, this info would be in the Help for the function.)

But much of this little function is wrapped around validating the value of the $EventDate parameter. So letâ€™s see if we can get Windows PowerShell to validate it for us.

In this version, we add aÂ parameter value type enclosed in square brackets ([DateTime]) on the line before the parameter name ($EventDate).

But thatâ€™s enough to allow us to delete the â€œif $EventDate â€“is [DateTime]â€ from the If statement and from the error message.

Now, letâ€™s get Windows PowerShell to test the other date condition for us. Hereâ€™s where ValidateScript comes in.

The syntax is a bit wonky. ValidateScript is enclosed in square brackets: [ValidateScript]. Its parameter is enclosed in parentheses: [ValidateScript( )] and the parameter value is a script block, complete with curly braces: [ValidateScript({ Your-script-goes-here })]. I can never remember this, so I use an ISE snippet or copy it from about_functions_advanced_parameters.

But aside from the syntax, ValidateScript is easy to use. I just moved the (-gt (Get-Date)) from the script into the ValidateScript script block. Now, I can eliminate the error message, too.

In the script block, â€œ$_â€ represents the parameter value. If a parameter takes a collection (more than one) of objects, â€œ$_â€ represents each value in the collection, which is tested one at a time â€“ no need for a Foreach-Object command.

When a parameter value fails a test, ValidateScript generates a terminating error. If the parameter value takes a collection, like a list of dates, and any one of the dates fails the test, ValidateScript throws an error that stops the script, even if all other dates pass the test.

Letâ€™s test by sending it a date in the past. (Todayâ€™s date would generate the same error.) The error message explains (in more words that I could use) that the date failed the validation test.

Itâ€™s not a great error message, but itâ€™s the best we could do, because Windows PowerShell just executes the validation script in the script block. It canâ€™t guess your intent.

Weâ€™re not even in the function statements yet, but we already know for sure that the values of the $LogPath, $ApplicationLog, and DestinationPath parameters are folders (not files), and the full path to these folders already exists in the file system. Not bad! Excellent, really. Clever enough to win second prize in the Nobel Prizes of PowerShell scripting. (Congratulations, Toni!)

Should we use ValidateScript?

This is very clever scripting, but is it a good idea? Itâ€™s easier for the author and easier to maintain, because the conditions are in a predictable place.

But, is this the right thing to do for users? I donâ€™t know the answer, but I think that we, as a community, need to consider the question.

Jeffrey Snover, the Windows PowerShell grand architect, wisely proclaims that Windows PowerShell differs from other languages in that scripts should â€œjust work.â€ Windows PowerShell scripts should make the user successful.

The language goes to all ends in its pursuit of this principle. When you send the wrong type of parameter value to a cmdlet, Windows PowerShell tries to convert the value to the right type. It returns an error only when its attempts to convert fail.

In Windows PowerShell 3.0, if you send it a collection of object and ask for a property that the collection doesnâ€™t have, Windows PowerShell checks to see if the objects in the collection have that property and, if they do, it returns the property value. (Try: (Get-Process).Name ).

If you ask Windows PowerShell 3.0 how many items are in an empty object, it tells you 0, even though empty objects donâ€™t have a Count or Length property.

PS C:\> $zoo = $null
PS C:\> $zoo.Count
0

Many scripts, including those weâ€™ve seen in these esteemed Games, have elaborate try-catch syntax to capture errors and create a pleasant user experience.

So, given that background, should we encourage scripting techniques that throw errors to users, instead of making them successful? And, in particular, errors that cannot provide very helpful error messages?

Personally, I prefer scripts that optimize the user experience, instead of the authoring experience. In the Get-EventDate example, where I planned to write an error anyway, ValidateScript is probably a cleaner alternative. But in the Archival Atrocity script, it would have been a much better user experience to create a directory if it didnâ€™t already exist.

On the other hand, if I were writing a script only for myself, I would keep it strict. I would prefer the error message to the risk that I just created a directory structure for a typo.

What do you think? Should we create community guidance for using validation attributes?

About the Author

June Blender was a senior programming writer on the Windows PowerShell team at Microsoft from Windows PowerShell 1.0 â€“ 3.0. You see her work every time you type Get-Help for the core modules. She's now working on the Windows Azure Active Directory SDK team, and she remains an avid Windows PowerShell user and a passionate user advocate. She's a guest blogger for the Scripting Guys and she tweets Windows PowerShell tips on Twitter at @juneb_get_help.
A 16-year veteran of Microsoft, June lives in magnificent Escalante, Utah, where she works remotely when she's not out hiking, canyoneering, taking Coursera classes, or convincing lost tourists to try Windows PowerShell. She believes that outstanding documentation is a collaborative effort, and she welcomes your comments and contributions to Windows PowerShell and Windows Azure Help.

11 Comments

You could attempt to create the directory in the Validate script with the -Confirm switch so you get some notification you're about to create a new directory.

Use Try/Catch to catch errors and check the type. If it's because the directory already exists, return $True so the validation test passes. If it's any other error (access denied, etc) return $False so the validation test will fail.

so yeah the messages are definately not useful. However you can go against the pattern, and with a little more work inside your validate script throw a more meaningful exception yourself (i'm not sure what effects it has on parameterbinding order etc)

That is excellent! I've completely avoided using ValidateScript because the error messages returned to the caller were so unfriendly. I may change my mind on that, with this option for producing better feedback.

Not only for validation attributes, but I think a general guidance on error handling would be great. Not so much to say â€œThis is how to do itâ€ but more along the lines of â€œConsider these things when handling errors.â€

There are just so many things to think about when handling errors in PowerShell. There are terminating errors, non-terminating errors, & $scriptblock, (functionCall), (advancedFunctionCall), error codes from external programs, preference variables, shouldprocess, shouldcontinue, null, empty collections, empty strings, trap, try/catch, throw, write-error, write-warning, validation attributes, errors thrown in the middle of the pipeline, what to expect from a line like $a = if throws an error, etc.

Then when you try to mix those things together, thereâ€™s just a lot of subtlety that are easy to miss.

Great article!
I studied through about_functions_advanced_parameters just before the games began and have been pretty happy with the ideas it gave me.

It seems to me that validating parameters is so awesome because it fills a very special need.

As we've all discovered there's lot's of "semi-appropriate" ways to use Powershell that work until you learn the right way. In some of those cases a good prediction was made during development "The user should be doing THIS... but they might do THAT instead. "

But accurately making that sort of prediction means that we're counting on the user to give us sensible inputs. Parameter Validation is a great way of protecting our code from input that is entirely wrong instead of almost right.

But I think it's smart to preach caution anywhere that we have the power to be too rigid.

I was just reviewing some entries, and it happened to strike me that Adv 4 is a great place to LEARN [validate..] but perhaps a bad place to implement it.

If i gave this to a button monkey I wouldn't want it to fail because he put "filename" rather than "filename.html".
I'd want it to test his input, append .html if it's not present, and then notify him "Hey you asked me to output to C:\windows\explorer.exe which isn't an html file, so your output was saved as C:\windows\explorer.exe.html enjoy!"

However I'd be perfectly happy for it to fail completely for an invalid path:
"Validate script did not return $true {Test-path "The intranet" }"

Thanks to everyone for their thoughtful replies. I think there's probably a proper use of this technique, but we need to be sensitive to our users and maximize their success rate. I agree that this decision, like many of these decisions, depend on the audience.