If you’ve been writing advanced PowerShell 2.0 functions, you’ve probably used some of the Validate* attributes to enforce valid parameter values, and you may have noticed that their error messages leave a lot to be desired. For example, imagine that you have a parameter which takes a 10-digit phone number:
function Test-PhoneNumber {
param( [ValidatePattern('^\(?\d{3}\)?[-\s.]?\d{3}[-\s.]\d{4}$')]$number )
<# do stuff #>
}
The problem
Mark Schill (Meson) brought this up at our virtual PowerShell User Group on IRC tonight: the error messages are confusing and not helpful. For instance, when you try that function and pass an invalid phone number (let’s say you forget to include the area code), you get this error message:
Test-PhoneNumber : Cannot validate argument on parameter 'number'. The argument "555-1212" does not match the "^\(?\d{3}\)?[-\s.]?\d{3}[-\s.]\d{4}$" pattern. Supply an argument that matches "^\(?\d{3}\)?[-\s.]?\d{3}[-\s.]\d{4}$" and try the command again.
There aren’t very many people who can read regular expressions, but only someone who can would really find the entirety of that error message useful. Everyone else is going to get (at most) this out of it: Cannot validate argument on parameter ‘number’. The argument “555-1212” does not match ... yadda, yadda.
That being the case, we’d like to hide the rest of that message … particularly the part that says: “Supply an argument that matches …” which is, frankly, just annoying or insulting depending on who your users are. In fact, as Mark suggested, I’d like to replace it with a custom message … something like: Cannot validate argument on parameter ‘number’. The supplied value is not a valid phone number. Please supply a full 10-digit number like: (123) 555-1212
Custom Validation Properties
So, this evening I decided to do something about it. It’s really pretty simple to write your own Validate*Attribute … you just have to derive from ValidateEnumeratedArgumentsAttribute, and override ValidateElement. Here’s an example ValidatePattern that supports a custom error message. I’ll paste the C# code separately, but basically you can just use Add-Type -TypeDefinition and pass this code as a here-string …
using System;
using System.ComponentModel;
using System.Management.Automation;
using System.Text.RegularExpressions;
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class ValidatePatternExAttribute : ValidateEnumeratedArgumentsAttribute
{
private RegexOptions _options = RegexOptions.IgnoreCase;
private string _pattern;
private string _message;
protected override void ValidateElement(object element)
{
if (element == null)
{
throw new ValidationMetadataException(_message + "\nValidatePatternEx Failure: Argument Is Null");
}
string input = element.ToString();
Regex regex = null;
regex = new Regex(_pattern, _options);
if (!regex.Match(input).Success)
{
throw new ValidationMetadataException(_message + "\nValidatePatternEx failure, the value didn't match the pattern: " + _pattern);
}
}
public RegexOptions Options
{
get
{
return _options;
}
set
{
_options = value;
}
}
public string Pattern
{
get
{
return _pattern;
}
set
{
if (string.IsNullOrEmpty(value))
{
throw new ArgumentException("RegularExpression Pattern is null or empty", "message");
}
_pattern = value;
}
}
public string Message
{
get
{
return _message;
}
set
{
if (string.IsNullOrEmpty(value))
{
throw new ArgumentException("Error Message is null or empty", "message");
}
_message = value;
}
}
}
Using it in PowerShell
When you use that in PowerShell, assuming that you called Add-Type with that as-is, you only have to change your function slightly, using ValidatePatternEx instead of ValidatePattern, and passing named properties, including the message.
function Test-PhoneNumber {
param(
[ValidatePatternEx(Pattern='^\(?\d{3}\)?[-\s.]?\d{3}[-\s.]\d{4}$', Message='Please enter a 10-digit phone number like: (123) 555-1212')]
$number
)
wrote-host $number -fore magenta
}
But once you do, you will get much better error messages:
Test-PhoneNumber : Cannot validate argument on parameter 'number'. Please enter a 10-digit phone number like: (123) 555-1212 ValidatePatternEx failure, the value didn't match the pattern: ^\(?\d{3}\)?[-\s.]?\d{3}[-\s.]\d{4}$
Other Applications
Of course, you may have noticed that this is really a completely custom parameter validator. You aren’t limited to just adding nice error messages to the validation error — you can create whatever validator you can dream up. Let me know what you come up with!
[...] Better error messages for PowerShell ValidatePattern (Joel Bennett) [...]
That’s pretty cool, but being a simple IT guy I have no idea how make my PowerShell know that this kind of Validate* exists. Should I compile it in special way…? Import as binary module…?
Anyway – good to know that error messages don’t have to be so cryptic.. :>
Well, if you compiled a bunch of these into a .dll you would just
Add-Type -Path C:\Path\To\Validations.dllbut you can just use theAdd-Type -TypeDefinitionto compile it in-line, and you could put a bunch of those definitions in a script module and import that to get them defined. The bottom line is that any attribute type in an assembly that’s loaded into PowerShell via Add-Type or ImportModule will “just work.”