Dynamic Parameter Validation - Part 2
Last time I demonstrated a technique you can use in PowerShell 7 as an alternative to the static [ValidateSet()]
parameter validation attribute. There is an alternative that comes close to the same functionality and it will also work in Windows PowerShell. You'll have to decide if it is appropriate for your situation.
Using an Enum
As I mentioned last time, [ValidateSet([class])]
technique requires PowerShell 7. As an alternative for Windows PowerShell, you can use an enumeration
. An enumeration is a programmatic way of defining a set of values. This is a common thing in PowerShell. The values for Write-Host
colors are defined in a [ConsoleColor]
enumeration.
PS C:\> [enum]::GetValues("ConsoleColor")
Black
DarkBlue
DarkGreen
DarkCyan
DarkRed
DarkMagenta
DarkYellow
Gray
DarkGray
Blue
Green
Cyan
Red
Magenta
Yellow
White
Here is the same example using an enumeration for the FileExtension
class I demonstrated last time.
enum FileExtension {
ps1
ps1xml
txt
json
xml
yml
zip
md
csv
}
You can enter this directly in your PowerShell session if you want to test. I can use my Get-TypeMember
function from the PSScriptTools module to get the values.
PS C:\> Get-TypeMember FileExtension
Type: FileExtension
Name MemberType ResultType IsStatic IsEnum
---- ---------- ---------- -------- ------
csv Field FileExtension True
json Field FileExtension True
md Field FileExtension True
ps1 Field FileExtension True
ps1xml Field FileExtension True
txt Field FileExtension True
xml Field FileExtension True
yml Field FileExtension True
zip Field FileExtension True
GetType Method Type
HasFlag Method Boolean
ToString Method String
This approach using an enum will also work in PowerShell 7. In your function, set the parameter type to the enumeration.
[FileExtension]$Extension = 'ps1'
> Make sure any default values are defined in the enumeration.
Here's the revised function using the enumeration.
Function Measure-FileExtension {
[CmdletBinding()]
param(
[Parameter(HelpMessage = 'Enter the path to analyze')]
[string]$Path = '.',
[ValidateNotNullOrEmpty()]
[FileExtension]$Extension = 'ps1',
[switch]$Recurse
)
$stats = Get-ChildItem -File -Path $Path -Filter "*.$Extension" -Recurse:$Recurse |
Measure-Object -Property Length -Sum -Average -Maximum -Minimum
[PSCustomObject]@{
PSTypeName = 'FileExtensionStats'
Path = Convert-Path $Path
Extension = $Extension
Files = $stats.Count
TotalSize = $stats.Sum
Average = $stats.Average
}
}
We won't use ValidateSet
in this situation. The enumeration will provide the tab-completion and validation. If the user enters a value not defined in the enum, PowerShell will throw an error.

The enum is a static list of values. If I want to change values, I have to edit the code and have the user reload everything.
A Dynamic Enum Option
That said, I have a hack that skirts this limitation. Instead of writing the enum in the script, I can create a C# enum definition and add it to the PowerShell session.
I'm going to revisit my Measure-FileExtension
function. At the beginning of my script file which will eventually be dot-sourced, I'll use code like this:
# Step 1: Define your list of values
$list = Get-Content $PSScriptRoot\ExtensionList.txt
# Step 2: Create the Enum definition string
$enumDefinition = 'public enum FileExtension {'
foreach ($item in $list) {
$enumDefinition += "$item,"
}
# Remove the trailing comma and close the enum definition
$enumDefinition = $enumDefinition.TrimEnd(',') + '}'
# Step 3: Use Add-Type to create the Enum
#You will get an error if you try to dot-source this again in the same session
#AND the enum has changed.
Try {
Add-Type -TypeDefinition $enumDefinition -ErrorAction Stop
}
Catch {
Write-Warning 'The [FileExtension] enum already exists and cannot be updated. Please re-run this script in a new PowerShell session.'
}
The code is creating a text block that defines the enumeration. The values are pulled from the text list. After Step 2, $enumDefinition
will look like this:
public enum FileExtension {ps1,ps1xml,txt,json,xml,yml,zip,md,csv}
Step 3 adds the enumeration to the session using Add-Type
. Due to the way .NET works, you'll get an error if you re-run the code with different values. .NET can't overwrite an existing enum. You'll need to start a new PowerShell session if there is a change.
The script file uses the enum as the type for the parameter.
Function Measure-FileExtension {
[CmdletBinding()]
Param(
[Parameter(HelpMessage = 'Enter the path to analyze')]
[string]$Path = '.',
[ValidateNotNullOrEmpty()]
[FileExtension]$Extension = 'ps1',
[switch]$Recurse
)
$stats = Get-ChildItem -File -Path $Path -Filter "*.$Extension" -Recurse:$Recurse |
Measure-Object -Property Length -Sum -Average -Maximum -Minimum
[PSCustomObject]@{
PSTypeName = 'FileExtensionStats'
Path = Convert-Path $Path
Extension = $Extension
Files = $stats.Count
TotalSize = $stats.Sum
Average = $stats.Average
}
}
The end result is the same as using a static enum. This approach has a little more flexibility and I like that it separates the coded to define the enum from the enum data.