Writing functions or scripts require a variety of parameters which have different requirements based on a number of items. It could require a collection, objects of a certain type or even a certain range of items that it should only accept.
The idea of parameter validation is that you can specify specific checks on a parameter that is being used on a function or script. If the value or collection that is passed to the parameter doesn’t meet the specified requirements, a terminating error is thrown and the execution of the code halts and gives you an error stating (usually readable) the reason for the halt. This is very powerful and allows you to have much tighter control over the input that is going into the function. You don’t want to have your script go crazy halfway into the code execution because the values sent to the parameter were completely off of the wall.
You can have multiple unique validations used on a single parameter and the style is similar to this:
[parameter(0] [ValidateSomething()] #Not a legal type; just example [string[]]$Parameter
Another important item is that you cannot use a default value in your parameter. Well, you can but it will never go through the parameter validation unless you specify a new value. While this will probably never apply or happen in your code, it is still something worth pointing out just in case you have something invalid that will not apply to whoever uses the function and wonders why it fails later in the code rather than at the beginning.
I am going to go over some of the validation types and give examples of each as well as discuss potential issues with each approach.
[ValidateNotNullOrEmpty()] and [ValidateNotNull()]
I have both of these listed instead of separately for a reason. Pick one or the other! I have seen some instances where both of these are being used to validate a single parameter and this simply does not need to happen and here is why:
- ValidateNotNull only checks to see if the value being passed to the parameter is a null value. Will still work if it is passed an empty string.
- ValidateNotNullorEmpty also checks to see if the value being passed is a null value and if it is an empty string or collection
Lets check out some examples with ValidateNotNull
Function Test-Something { [cmdletbinding()] Param( [parameter(ValueFromPipeline)] [ValidateNotNull()] #No value $Item ) Process { $Item } }
# Will fail Test-Something -Item $Null
# Will work because it is just an empty string, not a null value Test-Something -Item '' # Will work because we aren't checking for empty collections Test-Something -Item @()
Notice that I didn’t specify a type for the parameter. If you specify a type, then this will not work properly. If you need to use a type for your parameter, then use ValidateNotNullOrEmpty instead.
Function Test-Something { [cmdletbinding()] Param( [parameter(ValueFromPipeline)] [ValidateNotNull()] [string]$Item ) $Item } # Will work Test-Something -Item $Null Test-Something -Item ''
Up next is ValidateNotNullOrEmpty which is great if you are using collections and/or require an object of a specific type.
Function Test-Something { [cmdletbinding()] Param( [parameter(ValueFromPipeline)] [ValidateNotNullOrEmpty()] [string[]]$Item ) Process { $Item } }
It doesn’t matter if the parameter has a type with it, is a collection or is just a simple string, all of the below will fail when attempted.
Test-Something -Item $Null
Test-Something -Item @()
Test-Something -Item ''
As you can see, this does handle all of the possible empty and null values thrown at it. I will again reiterate that you need to choose one or the other with these two validations; no need to duplicate effort if you are just trying to avoid null values being passed into the parameter.
[ValidateLength()]
This is useful if you are expecting values of a certain length; such as usernames.
Some important issues to take note of include that will throw an error with the validation attribute:
- Max length less than min length
- Max length set to 0
- Argument is NOT a string or integer
Function Test-Something { [cmdletbinding()] Param( [parameter(ValueFromPipeline)] [ValidateLength(1,8)] [string]$Item ) Process { $Item } }
The first value given is the minimum value and the second value will always be the maximum value. In this case, I am expecting a string that is at least 1 character and at most 8 characters long. Anything outside of those boundaries will throw an error.
# Works Test-Something -Item Boe
# Will fail Test-Something -Item Thisisalongstring
Note that this tells you the length of the value that was submitted (17).
[ValidateRange()]
This is useful when you want to validate a specific range of integers, such as testing age.
Some important issues to take note of include that will throw an error with the validation attribute:
- Value of MinRange is greater than MaxRange
- Argument is NOT same type as Min and Max Range parameters
Function Test-Something { [cmdletbinding()] Param( [parameter(ValueFromPipeline)] [ValidateRange(21,90)] [int[]]$Age ) Process { $Age } }
# Will work Test-Something -Age 34 21,36 | Test-Something
# Will fail Test-Something -Age 16 Test-Something -Age 100,25 25,115,21 | Test-Something
[ValidateCount()]
This is useful to keep only a certain number of values in a collection for a parameter.
Some important issues to take note of include that will throw an error with the validation attribute:
- Value of MinRange is greater than MaxRange
- Range types must be Int32
- Parameter must be an array type ([string[]])
- If you just use [string] (or similar), then you are bound to only 1 item to pass into the parameter
- Min cannot be less than 0
Function Test-Something { [cmdletbinding()] Param( [parameter(ValueFromPipeline)] [ValidateCount(1,4)] [string[]]$Item ) Process { $Item } }
# Will work Test-Something -Item 9,10
# Will fail Test-Something -Item 9,6,7,8,9
As you can see, it will tell you in the error how many items were being assigned to the parameter as well as how many items are allowed.
Note that it has little effect on items being passed through the pipeline (this is by design as it is how the pipeline is supposed to work).
1,2,5,8,10 | Test-Something
But what if we pass a collection of collections?
@(1,2),@(1,2,5,8,6),@(10,15,6) | Test-Something
It will in fact fail on the collection that had more than the allotted items.
[ValidateSet()]
Useful limiting a certain set of item and allows for case sensitive sets if using $False after defining set. Default value is $True (case insensitive).
[ValidateSet('Bob','Joe','Steve', ignorecase=$False)]
Some important issues to take note of include that will throw an error with the validation attribute:
- Used more than once on a parameter (multiple sets of sets)
- Element of set is in each element of an array being passed or fails completely
- Parameter doesn’t accept array and more than 1 item passed
Function Test-Something { [cmdletbinding()] Param( [parameter(ValueFromPipeline)] [ValidateSet('Bob','Joe','Steve')] [string[]]$Item ) Process { $Item } }
# Will work Test-Something -Item 'joe'
@('Joe',@('Bob','Steve')) | Test-Something
# Will not work Test-Something -Item 'Boe' Test-Something -Item 'Boe','Joe'
#Partial; note how the collection of Bill and Joe doesn't work @('Bob',@('Bill','Joe'),'Boe','Steve') | Test-Something
For more cool stuff you can do with ValidateSet, check out this article from Matt Graeber (Blog | Twitter): http://www.powershellmagazine.com/2013/12/09/secure-parameter-validation-in-powershell/
[ValidatePattern()]
Useful to validate input matches specific regex pattern allows for case sensitive matches; Regex Options flags allow for more customization. Very hard to gather requirements from error message that is thrown if the validation fails unless the individual has some RegEx experience.
Member name |
Description |
Compiled |
Specifies that the regular expression is compiled to an assembly. This yields faster execution but increases startup time. This value should not be assigned to the Options property when calling the CompileToAssembly method. |
CultureInvariant |
Specifies that cultural differences in language is ignored. See Performing Culture-Insensitive Operations in the RegularExpressions Namespace for more information. |
ECMAScript |
Enables ECMAScript-compliant behavior for the expression. This value can be used only in conjunction with the IgnoreCase, Multiline, andCompiled values. The use of this value with any other values results in an exception. |
ExplicitCapture |
Specifies that the only valid captures are explicitly named or numbered groups of the form (?<name>…). This allows unnamed parentheses to act as noncapturing groups without the syntactic clumsiness of the expression (?:…). |
IgnoreCase |
Specifies case-insensitive matching. |
IgnorePatternWhitespace |
Eliminates unescaped white space from the pattern and enables comments marked with #. However, the IgnorePatternWhitespace value does not affect or eliminate white space in character classes. |
Multiline |
Multiline mode. Changes the meaning of ^ and $ so they match at the beginning and end, respectively, of any line, and not just the beginning and end of the entire string. |
None |
Specifies that no options are set. |
RightToLeft |
Specifies that the search will be from right to left instead of from left to right. |
Singleline |
Specifies single-line mode. Changes the meaning of the dot (.) so it matches every character (instead of every character except \n). |
Some important issues to take note of include that will throw an error with the validation attribute:
- Used only once per parameter
- Collection being passed must pass pattern for each item or fails completely if not coming from pipeline
Function Test-Something { [cmdletbinding()] Param( [parameter(ValueFromPipeline)] [ValidatePattern('^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$')] [string[]]$Item ) Process { $Item } }
I am intentionally using a complex RegEx string for the IP address to prove a point on how difficult it could be to understand the error.
# Will work Test-Something -Item 192.168.1.1 Test-Something -Item 192.168.1.1,168.125.12.15
# Will not work; note the error shows the regex, which only helps those that know regex Test-Something -Item 'Joe' Test-Something -Item 1 Test-Something -Item 192.168.1.1,23
As you can see, the error messages are pretty hard to read unless you know RegEx.
One last example showing input from the pipeline.
# Works a little better when using input from pipeline @('192.168.1.1','23') | Test-Something
The error leads me to the last type of validation that we can use to make the error a little better.
[ValidateScript()]
Very powerful to test for various requirements and can do what others can do and provide better (custom) errors based on how you structure the code. Can also slow down your script execution if you have too many checks happening in the scriptblock or long running check.
Some important issues to take note of include that will throw an error with the validation attribute:
- $True and $False values are not allowed to return when the attempt to validate fails or succeeds
- I would highly recommend you not use the return value of $False and instead use Throw with a custom error message so the user knows what should be happening.
Function Test-Something { [cmdletbinding()] Param( [parameter(ValueFromPipeline)] [ValidateScript({If ($_ -match '^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$') { $True } Else { Throw "$_ is not an IPV4 Address!" }})] [string[]]$Item ) Process { $Item } }
# Will work Test-Something -Item 192.168.1.1 Test-Something -Item 192.168.1.1,168.14.12.15
Now we can see a better error message when this fails.
# Will not work Test-Something -Item 'Joe' Test-Something -Item 1 Test-Something -Item 192.168.1.1,23
Now instead of a RegEx error message, this actually tells you that it expects an IPV4 address instead. Same as the example below.
@('192.168.1.1','23') | Test-Something
My last example with this is to check for invalid characters in a given path.
Function Test-Something { [cmdletbinding()] Param ( [parameter(ValueFromPipeline)] [ValidateScript({ If ((Split-Path $_ -Leaf).IndexOfAny([io.path]::GetInvalidFileNameChars()) -ge 0) { Throw "$(Split-Path $_ -Leaf) contains invalid characters!" } Else {$True} })] [string[]]$NewFile ) Process { $NewFile } }
#Works Test-Something -NewFile "C:\Temp\File.txt"
#Fails Test-Something -NewFile "C:\test\temp\File?.txt"
This was just one example of using ValidateScript, but you can pretty much test anything out and as long as you provide either a $True if good and Throw a custom error (or return $False), then you will have a pretty powerful method for validating parameters. I will reiterate again the need to keep this as efficient as possible as to not slow down your function if you are passing a large collection with each item being validated by your script block.
That’s it for working with parameter validation in PowerShell. Hopefully some of these examples and explanations will help you out in a future script/function!
Pingback: How to validate certain input parameters through a Validation API? – Best Java Answer
Pingback: PowerShell snippets - markwilson.it
What can you do when your parameter set includes spaces? Code below throws A positional parameter cannot be found that accepts argument ‘2’.At line:1 char:1 I even tried wrapping the values with spaces in double quotes and get this error Cannot validate argument on parameter ‘Environment’. The argument “Env 2” does not belong to the set.
Interesting enough, leaving off the double quotes from the values with spaces but wrapping the value on the cmd line in double quotes makes powershell happy.
Why can’t this work similar to Get-Service -DisplayName ‘Adaptive Brightness’ which was entered using Tab completion.
Param(
[Parameter(Mandatory=$true)]
[string]$Server,
[Parameter(Mandatory=$true)]
[ValidateSet(‘Env1′,’Env 2′,’Env 3’)]
[string]
$Environment
)
A nice article. But unless I am missing something there is an error with your statement:
“If the value or collection that is passed to the parameter doesn’t meet the specified requirements, a terminating error is thrown and the execution of the code halts”
Certainly for ValidatePattern this does not seem correct. For me a non-terminating error is raised and execution continues. Even passing my function -ErrorAction Stop does not raise a terminating error from ValidatePattern.
Only setting $ErrorActionPreference = Stop before calling my function will cause ValidatePattern to stop execution, which is a bit of a pain. I would expect -ErrorAction Stop to have the desired effect.
It sounds like you are talking about using a function in a script that has parameter validation in which case you are correct in that it is not a terminating error in the script. What I am demonstrating is that it will cause a terminating error within the actual function that has the parameter validation, not the script. Any code that should have run in the function will not run because it was terminating during parameter validation.
Take this example:
You will see that the End block never runs because it fails the parameter validation. Now if I put this into another script, the error will not be terminating unless you specify $ErrorActionPreference=’Stop’ so the error with the function becomes terminating. Hope this helps!
HI Boe, thanks for the explanation. I overlooked that it was indeed terminating the execution in the function.
So if in understand correctly it must throw a terminating error which is caught and handled by Powershell internals in the function scope. The result of handling that error is that it throws a non-terminating error to alert the user.
I guess my expectation is that this should be effected by -ErrorAction in the same way calling “Write-Error” in a function would.
Maybe Powershell has other ideas though =)
Do you have any suggestions about what to do if functions with parameter validation are invoked from scripts? Setting and resetting $errorActionPreference before and after every function doesn’t sound like a good idea. Try-catching function invocation works, but doing it for every function with parameter validation seems too much work for what parameter validation gives.
I’m leaning towards chucking parameter validation altogether. I wonder what was the logic for not letting -ErrorAction Stop kill the whole script.
This is like the tenth time I’ve found one of your articles which helped me out of an issue. Thanks again Boe!
Thanks, Stephen! I appreciate the comment! I try to put out useful things for people.
Pingback: Validate Parameters in Powershell | 24 by 7, 3 6 5
Reblogged this on ParrisFamily.Com and commented:
This is a good article
Pingback: Using PowerShell Parameter Validation to Make Your Day Easier | PowerShell.org