Moving Parameter Validation in PowerShell to Private Functions

While presenting one of my presentations at the PowerShell + DevOps Global Summit last week, I demonstrated why you wouldn't want to use ValidatePattern for parameter validation because of the useless error message that it returns when the input doesn't match the regular expression that's being used for validation.

1function Test-ValidatePattern {
2    [CmdletBinding()]
3    param (
4        [ValidatePattern('^(?!^(PRN|AUX|CLOCK\$|NUL|CON|COM\d|LPT\d|\..*)(\..+)?$)[^\x00-\x1f\\?*:\"";|/]+$')]
5        [string]$FileName
6    )
7    Write-Output $FileName
8}

private-param-validation1a.png

I then demonstrated how ValidateScript could be used to build a better ValidatePattern. I have an older blog article that details this process if that's something you're interested in learning more about.

 1function Test-ValidateScript {
 2    [CmdletBinding()]
 3    param (
 4        [ValidateScript({
 5            If ($_ -match '^(?!^(PRN|AUX|CLOCK\$|NUL|CON|COM\d|LPT\d|\..*)(\..+)?$)[^\x00-\x1f\\?*:\"";|/]+$') {
 6                $True
 7            }
 8            else {
 9                Throw "$_ is either not a valid filename or it is not recommended."
10            }
11        })]
12        [string]$FileName
13    )
14    Write-Output $FileName
15}

private-param-validation2a.png

A great question was asked during this presentation: Can you use a function for parameter validation instead of having some complicated script embedded within your function that's performing the validation? Just to be clear, you don't want your code to continue on a path that it can't possible complete successfully any further than necessary, which is one of the reasons you want to use parameter validation and not write custom validation within the body of the function itself. Another reason to use the parameter validation attributes is so your validation is at least similar to the validation that others would write.

With that said, if you're going to write custom code inside of the ValidateScript block, I think that breaking it out into private functions is a great idea because of several reasons. You could reuse the same validation such as the regular expression in the previous example to validate the input of numerous functions while only having to write the code once (there's no sense in writing redundant code) and if you ever have to update the code that's performing the validation, there's only one copy to update instead of trying to find all of the places you used it.

A private function could be written similar to the following for the example shown in this blog article.

 1function Test-FileName {
 2    param (
 3        [string]$FileName
 4    )
 5
 6    If ($FileName -match '^(?!^(PRN|AUX|CLOCK\$|NUL|CON|COM\d|LPT\d|\..*)(\..+)?$)[^\x00-\x1f\\?*:\"";|/]+$') {
 7        $True
 8    }
 9    else {
10        $false
11    }
12}

And then it could be called within the ValidateScript block to achieve the same result as the first function shown in this blog article.

 1function New-MrFile {
 2    [CmdletBinding()]
 3    param (
 4        [ValidateScript({
 5            If (Test-FileName -FileName $_) {
 6                $True
 7            }
 8            else {
 9                Throw "$_ is either not a valid filename or it is not recommended."
10            }
11        })]
12        [string]$FileName
13    )
14    Write-Output $FileName
15}

I've also created a companion video for this blog article to show what I'm talking about.

This really isn't any different than using built-in commands such as Test-Path within the ValidateScript block to validate a user is providing an existing and valid path as input for a parameter. You wouldn't rewrite all of the code for Test-Path within the ValidateScript block every time you wanted to validate a path.

µ