Today we'll continue our exploration of the parameter validation attributes you can use in you PowerShell scripting. We've already looked at [ValidateRange] and [ValidateScript]. Another attribute you are likely to use is [ValidateSet()]. You can use this to verify that the parameter value belongs to a pre-defined set.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
To use, specify a comma separated list of possible values.
[ValidateSet("Apple","Banana","Cherry")] [string]$Fruit
If the person running the script specifies something other than "Apple", "Banana", or "Cherry" as a value for -Fruit, PowerShell will throw an exception and the script will fail to run. And in case you were wondering, this is not case-sensitive.
If you are going to use this attribute, I recommend providing documentation either in comment based help and/or as part of a help message so the user knows what values to expect. The PowerShell help system doesn't automatically detect this attribute and use it in syntax display as you might have seen with other cmdlets.
Here's a more practical example.
#requires -version 2.0 Param ( [Parameter(Position=0)] [ValidateSet("System","Application","Security","Directory Service","DNS Server")] [string]$Log="System", [ValidateRange(10,1000)] [int]$Count=100, [Parameter(Position=1,Mandatory=$True, HelpMessage="What type of export file do you want to create? Valid choices are CSV, XML, CLIXML.")] [ValidateSet("csv","xml","clixml")] [string]$Export, [ValidateNotNullorEmpty()] [string]$Path="C:\Work", [string]$Computername=$env:Computername ) Write-Verbose "Getting last $Count events from $log event log on $computername" #base logname $base="{0:yyyyMMdd}_{1}_{2}" -f (Get-date),$Computername,$Log Try { $data=Get-EventLog -LogName $log -ComputerName $Computername -Newest $Count -errorAction Stop } Catch { Write-Warning "Failed to retrieve $log event log entries from $computername. $($_.Exception.Message)" } If ($data) { Write-Verbose "Exporting results to $($export.ToUpper())" Switch ($Export) { "csv" { $File=Join-path -Path $Path -ChildPath "$base.csv" $data | Export-Csv -Path $File } "xml" { $File=Join-path -Path $Path -ChildPath "$base.xml" ($data | ConvertTo-XML).Save($File) } "clixml" {$File= Join-path -Path $Path -ChildPath "$base.xml" $data | Export-Clixml -Path $File } } #switch Write-Verbose "Results exported to $File" } #if $data
The script will get recent events from a specified log on a specified computer and export the results. My script will only export from a short list of logs which I'm validating.
[ValidateSet("System","Application","Security","Directory Service","DNS Server")] [string]$Log="System",
If an invalid value is detected PowerShell will complain.
PS S:\> .\Demo-ValidateSet.ps1 -comp jdhit-dc01 -log dns -verbose -Export clixml C:\scripts\Demo-ValidateSet.ps1 : Cannot validate argument on parameter 'Log'. The argument "dns" does not belong to the set "System,Application,Security,Dire ctory Service,DNS Server" specified by the ValidateSet attribute. Supply an arg ument that is in the set and then try the command again. At line:1 char:45 + .\Demo-ValidateSet.ps1 -comp jdhit-dc01 -log <<<< dns -verbose -Export clixm l + CategoryInfo : InvalidData: (:) [Demo-ValidateSet.ps1], Paramet erBindingValidationException + FullyQualifiedErrorId : ParameterArgumentValidationError,Demo-ValidateSe t.ps1
The error message displays the expected values, but the better approach might be to include them in a help message, especially if the parameter is mandatory. For example, this script will export results based on -Export.
[Parameter(Position=1,Mandatory=$True, HelpMessage="What type of export file do you want to create? Valid choices are CSV, XML, CLIXML.")] [ValidateSet("csv","xml","clixml")] [string]$Export,
If you'd like to try out my demo script, you can download Demo-ValidateSet.
hi
you can use ignorecase keyword:
PS> gc function:\test-foo
function test-foo {
param(
[Parameter(Position=0)]
[validateset('foo','bar',ignorecase=$false)]
$test)
$test
}
PS> test-foo foo
foo
PS> test-foo FOO
error
That is a very handy tip. Thanks for sharing.
One of the benfits of using ValidateSet (or Enum types) in PowerShell 3.0 is that you get tab expansion on the ValidateSet/Enum values.
Jeff
Nice work on validation. Haveyouthough of rollimg it up into a help file. (about_jh_advanced_validation) or similar.
Thanks for the positive feedback. I’ll have to think about the best way to bundle everything. I still have one or two more topics to cover.
Hey Jeff;
Great article! Maybe you can point me in the right direction…. or maybe I just need to stop looking for ways to be lazy 🙂
I would like to ValidateSet against a scriptblock (similar to a ValidateScript). For example:
function find-processid {
[CmdletBinding()]
param(
[ValidateSet({(Get-Process).Name})]
$ProcessName
)
$Result = (Get-Process -Name $ProcessName)
$Result.id
}
This always bombs because it is reading the scriptblock literally. I know that I can make this work by changing ValidateSet to ValidateScript, the real aim here is to get tab completion to cycle through my choices in a dymanic situation (laziness).
Do you think there a way to make ValidateSet read the scriptblock when the function is called (some funky $([]::{}.()) magic that only you and maybe one other person has ever seen) or should I be looking to use a RuntimeDefinedParameter instead?
ValidateSet really seems to need to be constant and predefined. But I think you can get the same results with ValidateScript.
param (
[Parameter(Position=0,Mandatory=$True)]
[ValidateScript({$(get-process | Select -expand name) -contains $_})]
[string]$name
)
When you do that does tab completion on the -name paramter work for you? Maybe something’s wrong with my shell…
Function Get-ProcessID{
param (
[Parameter(Position=0,Mandatory=$True)]
[ValidateScript({$(get-process | Select -expand name) -contains $_})]
[string]$name
)
$prc = Get-Process -Name $_
$prc.id
}
get-processid -name
Returns files in the local directory in my shell, or am I doing something else wrong?
No that won’t work. What you want will work in PowerShell v3. You could type Get-Process -name press space and then tab and it would cycle through the all the current processes. You are misunderstanding what a function does. I think you should step back and regroup. Feel free to use the forum at http://bit.ly/AskJeffHicks
I think the example was just too simple. Thanks for your time Jeff, I’ll figure it out.