Validating Parameter Input

In earlier articles in the Practical PowerShell series on Practical 365, I talked about advanced functions and using parameters in advanced functions. In another article, I discussed utilizing dynamic parameters for more complex scenarios. In this first article of 2025, I will dive in a bit deeper on a topic we touched on lightly in those articles, which is ways to validate parameter input.

Parameter validation, in general, is essential for several reasons:

  • It improves reliability and consistency for scripts or advanced functions by requiring parameters to meet specific criteria. For example, you can allow passed strings to particular values, ‘User’ or ‘Group’. Or, if the parameter should be a number, does it fall within a number range?
  • It allows you to provide feedback to the user when criteria are not satisfied, offering guidance for users of your script or function. These criteria can also help the reader to understand how to use the script or function.

Let us see how PowerShell can accomplish these and how to add parameter input validations in your code.

Defining Criteria

PowerShell can utilize several built-in validation options. These validations need to be added before the parameter definition in the same brackets, just after any parameter attribute definition, e.g.

[parameter([[parameter attribute];[parameter attribute]..)]
[validation]
[type]<parameter name>

Note: In that exact definition, you can also add one or more aliases for your parameter, which might be helpful when you want to offer a shorter alternative or when changing the parameter name and want to provide compatibility, e.g.

[Alias('UserName')]
[string]$Identity

The possibilities for parameter input validation are listed in the table below, accompanied by an example.

Parameter Input ValidationDescription and Example
ValidateSet(x[,y..])Restricts input to a set of predefined values.  

For example, you can only specify UserMailbox or SharedMailbox as MailboxType:  

[ValidateSet(‘UserMailbox’, ‘SharedMailbox’)]
[string]$MailboxType  

Note that you can cycle through these predefined values when using tab completion.
ValidatePattern(x)Restricts input to match a pattern. For example, to match a pattern of 2 digits followed by an arbitrary length of alphabetic characters. The regular expression (regex) for that could be something like:
^ indicates the start of the start of the matching pattern.
\d matches a digit
{n} can be used to specify the number of desired occurences of the pattern. So, {2} means 2 numbers.
[..] matches a character in this set. For any letter in any casing is [a-zA-Z].+ is used to match one or more occurences of the pattern, making [a-zA-Z]
– + match one or more letters.
$ marks the end of the pattern.  

This leads to the following pattern validation:  

[ValidatePattern(‘^\d{2}[a-zA-Z]+$’)]
[string]$Name  

Note: To create and test your own regex, you can use websites such as this, or you could try asking your AI assistant.
ValidateRange(n,m)Restricts input to fall within a predefined range.

For example, accept an age that lies between 30 and 0.  

[ValidateRange( 0, 30 )]
[datetime]$LogAge  

Note: The range values need to be constants at compile time when PowerShell reads your code. You must use a script block if you want date ranges based on the current date.
ValidateLength(n,m)Restricts length of string input.  

For example, check if the input length of a string is 36 characters, which normally is the length of a Guid:  

[ValidateLength(36, 36)]
[string]$Guid  

Note: Pattern validation would be more appropriate for Guids, as it enables the creation of more restrictive criteria.
ValidateCount(n,m)Restricts the number of items a parameter accepts. This restriction works for collections and validates their count property. Note that you may use 0 as well.

For example, you can accept an array of strings, and that array can contain 1 up to 5 elements:  

[ValidateCount(1, 5)]
[string[]]$Name
ValidateScript(x)Restricts input using script block, which should return $true for acceptance. You can use a single cmdlet or utilize helper functions available at runtime. Runtime is when PowerShell executes the code. The input value is available as an automatic variable through $PSItem or its more common alias, $_  

For example, you can only provide data within the last 30 days. For readability, we use two variables in the script block to define the criteria: minimum date and maximum date:

  [ValidateScript({
  $minDate = (Get-Date).AddDays(-30)
  $maxDate = Get-Date
  $_ -ge $minDate -and $_ -le $maxDate })]
[datetime]$DateParam
ValidateNotNull()Restricts input not to be null. Typically, $null is an acceptable value when passing a mandatory parameter. Use this validation to prevent this.  

For example, accept any string, including empty ones:

[ValidateNotNull()]
[string]$Item

Note: Empty collections will be accepted, as empty collections –  array @() or hashtable@{} – are not $null and thus undefined.
ValidateNullOrEmpty()Restricts input from being null or empty. In addition to $null, empty strings (”) can also be acceptable values for strings. Use this validation to prevent accepting $null values or empty strings.  

For example, check if the input length of a string is 36 characters, which normally is the length of a Guid:  

[ValidateNullOrEmpty()]
[ValidateLength(36, 36)]
[string]$Guid

Note: As shown in this example, you can combine criteria by specifying multiple validation criteria.

Usage Guidance

When you ask Get-Help to return usage information for your script or function, the information will also return criteria set by ValidateSet as it is part of the shown syntax. Information related to the criteria defined by the other options is not. You can add usage information by adding help messages via the HelpMessage attribute in the parameter declaration. They can provide guidance when reading or running code. The need for guidance is especially true for ValidateScript or ValidatePattern, where the default error message will complain about validation X not resulting in $true or input not matching regular expression Y, which is not user-friendly. Instead, you could provide messages like the following:

param(
  [Parameter(HelpMessage="Enter a string that starts with 3 digits followed by a dash followed by any string")]
  [ValidatePattern('^\d{3}-.+$')]
  [string]$Name
)

Note that these help messages are also displayed when PowerShell asks you to provide input for a mandatory parameter you omitted.

Note: Starting with PowerShell 6, you can provide custom Error Messages displayed when parameter input validation fails. To specify these, specify ErrorMessage with the validation criteria, e.g.

[Parameter(Mandatory)]
[ValidateScript(
  { $_ -ge (Get-Date).AddDays(-30).Date },
  ErrorMessage = "{0:d} can only be up to 30 days ago."
  )]
[DateTime]$StartDate

Now, adding HelpMessage to existing parameters can be a less exciting task. This is where a little bit of (GitHub) Copilot might be of assistance to perform some of the labor. You can ask Copilot to enhance your parameters by adding HelpMessage to existing parameter definitions. Just ask Copilot to “Add helpmessage for the parameters” and then paste your param section code. The generated output can be something like the code below:

Function Get-SomethingMore {
  [CmdletBinding()]
  param(
    [Parameter(HelpMessage="Specify 'User' or 'Group'.")]
    [ValidateSet('User', 'Group')]
    [string]$SetParam,

    [Parameter(HelpMessage="Enter a string that matches the pattern '###-...'")]
    [ValidatePattern('^\d{3}-.+$')]
    [string]$PatternParam,

    [Parameter(HelpMessage="Enter an integer between 1 and 10.")]
    [ValidateRange(1,10)]
    [int]$RangeParam,

    [Parameter(HelpMessage="Enter a string with a length between 0 and 10 characters.")]
    [ValidateLength(0,10)]
    [string]$LengthParam,

    [Parameter(HelpMessage="Enter one or two strings.")]
    [ValidateCount(1,2)]
    [string[]]$CountParam,

    [Parameter(HelpMessage="Enter a valid path.")]
    [ValidateScript( { Test-Path -Path $_ -PathType Leaf  } )]
    [string]$ScriptParam
  )
}

As shown in the example, the criteria received helpful messages. Running Get-Help -Detailed will now also include the help messages in the information displayed, e.g.

PS >Get-Help Get-SomethingMore -Detailed

NAME
    Get-SomethingMore

SYNTAX

    Get-SomethingMore [[-SetParam] {User | Group}] [[-PatternParam] <string>] [[-RangeParam] <int>] [[-LengthParam] <string>] [[-CountParam] <string[]>] [[-ScriptParam] <string>] [<CommonParameters>]

PARAMETERS

    -CountParam <string[]>

     Enter one or two strings.

#Etc. (you get the idea)

Be Careful with Copilot

A word of caution on using Copilot: As often with the current state of “helpful AI assistants,” check the generated output for any mistakes or other issues. In my case, Copilot did the most heavy lifting, adding HelpMessage attributes, but it also introduced two issues. First, Copilot created a duplicate declaration of the SetParam parameter. A simple removal of the redundant code solved this. Second, Copilot decided that all my parameters should become mandatory by adding a mandatory=$true attribute to every parameter. This change could be acceptable in your situation, but I did not ask for it. Instead of removing all the added Mandatory=$true attributes that Copilot inserted in every parameter attribute declaration just before HelpMessage, I explicitly requested Copilot not to make optional parameters mandatory. The code above is the generated output.

I suppose it is one of those things that happens when fiddling with prompts to assist you. Unfortunately, it also shows why mindlessly running generated code by these AI assistants is still risky. However, the strengths and weaknesses of (GitHub) Copilot in its current state might be something for future articles to consider. Until then, start improving your code by adding input validation.

About the Author

Michel De Rooij

Michel de Rooij, with over 25 years of mixed consulting and automation experience with Exchange and related technologies, is a consultant for Rapid Circle. He assists organizations in their journey to and using Microsoft 365, primarily focusing on Exchange and associated technologies and automating processes using PowerShell or Graph. Michel's authorship of several Exchange books and role in the Office 365 for IT Pros author team are a testament to his knowledge. Besides writing for Practical365.com, he maintains a blog on eightwone.com with supporting scripts on GitHub. Michel has been a Microsoft MVP since 2013.

Leave a Reply