User Rating: 5 / 5

Star ActiveStar ActiveStar ActiveStar ActiveStar Active
 

Validating input to PowerShell commands is critical. When I teach folks how to create PowerShell GUIs, we always discuss validating input that the user enters in a textbox. Something about that big empty box and its varied audience reminds PowerShell scripters that they should validate input.

image001

 

But, it's just as important in a script.

Set-CriticalService -ComputerName "(#$%^&@**(#&)"

 

Validating input is an elementary security measure, but it also helps authorized users to recognize mistakes. We'd all prefer to endure an error than to take the wrong server offline.

And that's where the PowerShell validation attributes come in. You can read about them in about_Functions_Advanced_Parameters.

PowerShell's validation attributes are "pithy" (a Snoverism) and easy to use. A simple validation attribute replaces a conditional test, But most importantly, they're easy to find. Stacked up there in the Param block or sitting right beside the variable, they're imminently discoverable.

Screenshot 2017 01 25 10.11.24

 

Mike Roberts just wrote a great post on the ValidateSet parameter attribute. It explains all of the good reasons for using ValidateSet. I agree. I use it often, along with ValidateRange and ValidateScript.

But I don't use ValidatePattern.

 

What is ValidatePattern?

ValidatePattern is a PowerShell validation attribute that validates a parameter or a variable. It takes a regular expression and throws an exception if the value of the parameter or variable does not match the regular expression pattern.

Syntax:

[ValidatePattern('<regex>')]     #Don't forget the quotes

For example:

[ValidatePattern('^SAPIEN-\d{6}$')]

Or, ValidatePattern can take a regular expression and one or more RegexOption enumerated values.
(Thanks to Friedrich Weinmann and Lee Holmes for help with the syntax.)

For example:

[ValidatePattern('A\b\(?((?>\w+)', Options='IgnorePatternWhitespace')]

By default, PowerShell is case-insensitive, so even if the regular expression in your ValidatePattern attribute is case-sensitive, PowerShell doesn't enforce it.

[ValidatePattern('^SAPIEN-\d{6}$')]
[string]$ComputerName

Test-ValidatePattern -ComputerName saPIen-003300   #Works

To make the ValidatePattern attribute case-sensitive, use the 'None' RegexOption (or any RegexOption other than 'IgnoreCase').

[ValidatePattern('^SAPIEN-\d{6}$', Options = 'None')]

Seems easy enough. What's the problem?

 

What's Wrong with ValidatePattern?

The problem with the validation attributes (except for ValidateScript) is that you can't specify the error message. (To specify an error message for ValidateScript, use an IF statement in the script block.) The error messages for ValidateSet and ValidateRange are actually very good, although you still might want to customize them.

But, the error message for ValidatePattern is not. It tells too much to unauthorized users and tells too little to authorized users.

ERROR: Test-ValidatePattern : Cannot validate argument on parameter 'ComputerName'. The argument "SAP-123456" does not match the "^SAPIEN-\d{6}$" pattern. Supply an argument that matches "^SAPIEN-\d{6}$" and try the command again.

If you've wrapped your script in an executable file or distributed a GUI app, you might not want unauthorized users to know the pattern that you're enforcing, although any determined foe could figure this out rather quickly.

More importantly, the default error message might not help an authorized user who's not a regular expressions guru. Your parameter help is a good way to help them, but you might prefer an error message that is better suited to the audience.

ERROR: Test-ValidatePattern : "SAP-123456" is not a valid computer name in this enterprise.

-or-

ERROR: Test-ValidatePattern : "SAP-123456" is not valid. Enter a name that begins with 'SAPIEN-' and ends with a six-digit number.

To avoid the error message, I replace the ValidatePattern attribute with the less elegant, less pithy, less discoverable conditional match statement. Or, -cnotmatch, which is case-sensitive.

For example:

if ($ComputerName -cnotmatch '^SAPIEN-\d{6}$') {
    throw "ERROR: SAP-123456 is not valid. Enter a name that begins with 'SAPIEN-' and ends with a six-digit number."
}

Rather than avoiding the validation attributes, I'd prefer a way to specify a custom error message, such as an ErrorMessage named parameter. Something like:

[ValidatePattern('^SAPIEN-\d{6}$', ErrorMessage="$_ is not valid. Enter a name that begins with 'SAPIEN-' and ends with a six-digit number.")]

But, until that happpens, I'll avoid the ValidatePattern attribute.

June Blender is a technology evangelist at SAPIEN Technologies, Inc and a Microsoft Cloud & Datacenter MVP. You can reach her at This email address is being protected from spambots. You need JavaScript enabled to view it. or follow her on Twitter at @juneb_get_help.

If you have questions about our products, please post in our support forum.
For licensed customers, use the forum associated with your product in our Product Support Forums for Registered Customers.
For users of trial versions, please post in our Trial Software Questions forum.
Copyright © 2017 SAPIEN Technologies, Inc.