Tag Archives: Validation

Variable Validation

In PowerShell v3 there is a new feature you might not be aware of that could save you pain and headaches. This is something you could use in scripting as well as the console. In fact, I think using it in the console is an especially smart idea.

In PowerShell v2 scripts and functions we had the ability to add validation tags to parameters. These tags could perform different validation tests on a parameter value. If the value failed, the script or function would throw an exception. I’d rather have the command fail to start than to fail halfway through. Now in PowerShell v3 we can use these same tags on variables in our scripts and even the console. I’ve written about a number of these validation tags in the past on my blog. Here’s an example of what we can do in v3.


PS C:\> [validateRange(1,10)]$i=5

In this example I’ve added a validation test to verify that any value for $i is between 1 and 10. I can use the variable as I normally would. Even change the value.


PS C:\> $i*2
10
PS C:\> $i+=1
PS C:\> $i*2
12

But watch what happens when I try to use a value outside of the accepted range:


PS C:\> $i=30
The variable cannot be validated because the value 30 is not a valid value for the i variable.
At line:1 char:1
+ $i=30
+ ~~~~~
+ CategoryInfo : MetadataError: (:) [], ValidationMetadataException
+ FullyQualifiedErrorId : ValidateSetFailure

I get an exception. This is much better than setting a value that will cause another error later. The sooner you can detect potential problems the better.

Perhaps this example isn’t compelling. But there are other validation tests you might want to take advantage of. Maybe a regular expression pattern.


PS C:\> [validatepattern({\\\\\w+\\\w+})]$unc="\\server01\share"

If I later decide to change the value, the validation will help me catch typos.


PS C:\> $unc="\server02\data"
The variable cannot be validated because the value \server02\data is not a valid value for the unc variable.
At line:1 char:1
+ $unc="\server02\data"
+ ~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : MetadataError: (:) [], ValidationMetadataException
+ FullyQualifiedErrorId : ValidateSetFailure

The bottom line is that if your variable values will change, using a validation tag will ensure new values will work. If you don’t want to sift through the blog learning more about the validation tags, take a look at my Scripting Help module.

PowerShell Scripting with [ValidateNotNullorEmpty]

I’ve been writing about the different parameter validation attributes that you can use in your PowerShell scripting. One that I use in practically every script is [ValidateNotNullorEmpty()]. This validation will ensure that something is passed as a parameter value. I’m not talking about making a parameter mandatory; only that if the user decides to use the parameter that something is passed. Let’s look at my demo.


#requires -version 2.0

Param (
[Parameter(Position=0,Mandatory=$True,HelpMessage="Enter a process name like lsass")]
[ValidateNotNullorEmpty()]
[string]$Name,
[Parameter(Position=1)]
[ValidateNotNullorEmpty()]
[string]$Computername=$env:computername

)

Try {
Get-Process -Name $name -ComputerName $computername -errorAction "Stop" | Select *,
@{Name="Runtime";Expression={(Get-Date)-$_.StartTime}}
}

Catch {
Write-Warning $_.Exception.Message
}

I’ve used the attribute on both parameters. The first parameter I’ve also made mandatory which makes it more likely that something will be entered. But if not, then the script will fail.

I would get a similar message if the user forgot to complete the command.

Even though I’m using a default value for Computername, as soon as the parameter is detected PowerShell assumes I’m going to use a different value.

Validation should work for missing values, a variable that might be null, or in general anything that is meaningless. However, it won’t prevent something quirky like this:


PS S:\> $p=" "
PS S:\> .\demo-ValidateNotNull.ps1 -name $p
WARNING: Cannot find a process with the name " ". Verify the process name and
call the cmdlet again.

The variable, while semantically meaningless to us, is not null or empty but a string of 1 space. If there’s the chance you might run into this situation then you can add additional validation checks to the parameter.

If you’d like, feel free to download Demo-ValidateNotNull and see for yourself.

PowerShell Scripting with [ValidateCount]

Here’s another parameter validation attribute you might want to use in your PowerShell scripting and functions. If your parameter can take an array of values, you might want to limit that array to a certain size. For example, your parameter can take an array of computer names but you don’t want to process more than 5 for some reason. This is where [ValidateCount()] comes in to play.

This attribute takes two values, the minimum number of accepted parameter values and the maximum.


[ValidateCount(1,10)]
[string[]]$Computername

If used, this would mean I would need at least one computername but no more than 10. You could also set both values the same if you wanted an exact number:


[ValidateCount(2,2)]
[int[]]$Numbers

Now, I’d have to pass exactly 2 numbers as parameter values. Let’s look at a more complete example.


#requires -version 2.0

Param (
[Parameter(Position=0,Mandatory=$True)]
[ValidateCount(1,5)]
[string[]]$Name
)

Foreach ($item in $name) {

#display the name in a random color
Write-Host $item -ForegroundColor ([system.consoleColor]::GetValues("system.consolecolor") | get-random)

}

This simple script writes each name in a random color, assuming I pass no more than 5 names.

If I exceed that count, PowerShell will throw a tantrum (I mean exception).

When you use this validation test, be sure your parameter is set to accept an array of values, e.g. [string[]]. If you’d like to try out my sample code feel free to download Demo-ValidateCount.

PowerShell Scripting with [ValidateLength]

In continuing the exploration of parameter validation attributes, today we’ll look at [ValidateLength()]. You can use this attribute in your PowerShell scripting to validate that a parameter value is at least a certain length and no more and a certain length. In other words, it has to be just right. Here’s what it looks like:


[ValidateLength(2,10)]
[string]$Fooby

In this example any value for -Fooby must be at least 2 characters long and no more than 10. Anything outside of that range and PowerShell will raise an exception. You have to supply both values in the validation attribute. An alternative would be to use [ValidateScript()].


[ValidateScript({ $_.Length -ge 5})]
[string]$Name

Now, any value for -Name must be at least 5 characters and there is no upper limit. Here’s a more complete example.


#requires -version 2.0

[cmdletbinding(SupportsShouldProcess=$True)]

Param (
[Parameter(Position=0,Mandatory=$True,HelpMessage="Enter name between 5 and 15 characters")]
[ValidateLength(5,15)]
[string]$Name,
[Parameter(Position=1,Mandatory=$True,HelpMessage="Enter password between 7 and 64 characters")]
[ValidateLength(7,64)]
[ValidatePattern({^\S+$})]
[string]$Password,
[string]$Computername=$env:computername,
[switch]$Passthru
)

Write-Host "Creating $name with password of $Password on $computername" -ForegroundColor Green
[ADSI]$Server="WinNT://$computername"
$User=$server.Create("User",$Name)

if ($pscmdlet.ShouldProcess($User.Path)) {
Write-Host "Committing new account changes" -ForegroundColor Green

<#
#uncomment the next lines if you really, really want to do this
$User.SetInfo()
Write-Host "Setting password" -ForegroundColor Green
$User.SetPassword($Password)
If ($passthru) {
Write-Output $User
}
#>
}

This script will create a local user account. I’m asking that the user name be between 5 and 15 characters and that the password be between 7 and 64 characters. I’ve also added a second validation check on the password using [ValidatePattern()] to verify it doesn’t contain any spaces.

The exception message you see with [ValidateLength()] depends on where you fall short.


PS S:\> .\Demo-ValidateLength.ps1 Jeff Password123
C:\scripts\Demo-ValidateLength.ps1 : Cannot validate argument on parameter 'Nam
e'. The number of characters (4) in the argument is too small. Specify an argum
ent whose length is greater than or equal to "5" and then try the command again
.
At line:1 char:26
+ .\Demo-ValidateLength.ps1 <<<< Jeff Password123
+ CategoryInfo : InvalidData: (:) [Demo-ValidateLength.ps1], Para
meterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Demo-ValidateLe
ngth.ps1

PS S:\> .\Demo-ValidateLength.ps1 JeffJeffJeffJeff Password123
C:\scripts\Demo-ValidateLength.ps1 : Cannot validate argument on parameter 'Nam
e'. The argument length of 16 is too long. Shorten the length of the argument t
o less than or equal to "15" and then try the command again.
At line:1 char:26
+ .\Demo-ValidateLength.ps1 <<<< JeffJeffJeffJeff Password123
+ CategoryInfo : InvalidData: (:) [Demo-ValidateLength.ps1], Para
meterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Demo-ValidateLe
ngth.ps1

If you'd like, you can download Demo-ValidateLength and try it out for yourself.

PowerShell Scripting with [ValidatePattern]

I’ve been writing about a number of parameters attributes you can include in your PowerShell scripting to validate parameter values. Today I want to cover using a regular expression pattern to validate a parameter value. I’m going to assume you have a rudimentary knowledge of how to use regular expressions in PowerShell. If not, there is an entire chapter devoted to the topic in Windows PowerShell 2.0: TFM.

The parameter attribute is [ValidatePattern()]. Inside the parentheses you place a scriptblock with the regular expression pattern. For example, in PowerShell we might write a command like this to verify if something is a number of 1 to 3 digits.:


$x -match "^\d{1,3}$"

To use that pattern in a [ValidatePattern()] attribute, you would write it like this:


[ValidatePattern({^\d{1,3}$})]

There is no need to use the -match operator or $_. Sure, I suppose you could write a validation script to achieve the same effect, but this is just as easy. I recommend testing your pattern from the PowerShell prompt, especially testing for failures. Here’s a more complete example.

Param (
[Parameter(Position=0,Mandatory=$True,HelpMessage="Enter a UNC path like \\server\share")]
[ValidatePattern({^\\\\\S*\\\S*$})]
[ValidateScript({Test-Path -Path $_ })]
[string]$Path
)

Write-Host "Getting top level folder size for $Path" -ForegroundColor Magenta
dir $path | measure-object -Property Length -sum

For you regular expression gurus, don’t get hung up on my pattern. It works for my purposes of illustration. Your pattern can be as simple or as complex as you need it to be. In this short script I’m expecting a path value like \\file01\public. If the value is not in this format, the pattern validation will fail, PowerShell will throw an exception and the script will fail.

Notice I’m also using a second parameter validation attribute, [ValidateScript()]. It is possible for the pattern to be correct but invalid so I can combine both validation tests.


PS C:\> S:\Demo-ValidatePattern.ps1 '\\file01\temp'
C:\scripts\Demo-ValidatePattern.ps1 : Cannot validate argument on parameter 'Pa
th'. The "Test-Path -Path $_ " validation script for the argument with value "\
\file01\temp" did not return true. Determine why the validation script failed a
nd then try the command again.
At line:1 char:28
+ S:\Demo-ValidatePattern.ps1 <<<< '\\file01\temp'
+ CategoryInfo : InvalidData: (:) [Demo-ValidatePattern.ps1], Par
ameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Demo-ValidatePa
ttern.ps1

If you'd like to try out my sample script, you can download it here.

PowerShell Scripting with [ValidateSet]

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.

To use, specify a comma separated list of possible values.

[ValidateRange("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.

PowerShell Scripting with [ValidateScript]

The last few days we’ve been looking at parameter validation attributes you might use in a script of function. Yesterday I wrote about [ValidateRange] and demonstrated how you might use it. That attribute works fine for any values that can be evaluated as numbers. But dates are a different story. I got a comment with a suggestion for validating dates and at first glance it appears to work. For a very small range it might, but here’s the code I was testing with.


Param (
[Parameter(Position=0,Mandatory=$True,HelpMessage="Enter a date",ValueFromPipeline=$True)]
[ValidateRange("1/1/2012","4/1/2012")]
$Date
)

Process {
write-host $date -ForegroundColor Green
}

I’m expecting date between 1/1/2012 and 4/1/2012. Anything else should throw an exception. Now watch what happens as I pipe some values to this:


PS S:\> "2/12/2012","5/1/2012","12/1/2011","13/2/2012" | .\Demo-ValidateRange-Da
te.ps1
2/12/2012
C:\scripts\Demo-ValidateRange-Date.ps1 : Cannot validate argument on parameter
'Date'. The 5/1/2012 argument is greater than the maximum allowed range of 4/1/
2012. Supply an argument that is less than 4/1/2012 and then try the command ag
ain.
At line:1 char:79
+ "2/12/2012","5/1/2012","12/1/2011","13/2/2012" | .\Demo-ValidateRange-Date.ps
1 <<<<
+ CategoryInfo : InvalidData: (5/1/2012:String) [Demo-ValidateRan
ge-Date.ps1], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Demo-ValidateRa
nge-Date.ps1

12/1/2011
13/2/2012

Values that are outside the range and even those that are bogus still are accepted. If I cast $date to [DateTime] which is what I would normally do, then nothing works at all. But that's fine because there is another attribute that is better suited to this task: [ValidateScript()].

To use this attribute we'll insert a scriptblock inside the parentheses. The scriptblock can be as complicated as you need it to be, but it must evaluate to either True or False. Use $_ to indicate the parameter value. So using my datetime example, I might use a validation script like this:


Param (
[Parameter(Position=0,Mandatory=$True,HelpMessage="Enter a date",ValueFromPipeline=$True)]
[ValidateScript( {
[datetime]$start="1/1/2012"
$end=Get-Date
($_ -ge $start) -AND ($_ -le $end)
}
)]
[datetime]$Date
)

Process {
write-host $date -ForegroundColor Green
}

With a validation script I have much more flexibility. Now look at the results:


PS S:\> "2/12/2012","5/1/2012","3/15/2012","12/1/2011","13/2/2012" | .\Demo-Vali
dateScript-Date.ps1 | clip
2/12/2012 12:00:00 AM
C:\scripts\Demo-ValidateScript-Date.ps1 : Cannot validate argument on parameter
'Date'. The "
[datetime]$start="1/1/2012"
$end=Get-Date
($_ -ge $start) -AND ($_ -le $end)
" validation script for the argument with value "5/1/2012 12:00:00 AM" did not
return true. Determine why the validation script failed and then try the comma
nd again.
At line:1 char:92
+ "2/12/2012","5/1/2012","3/15/2012","12/1/2011","13/2/2012" | .\Demo-ValidateS
cript-Date.ps1 <<<< | clip
+ CategoryInfo : InvalidData: (5/1/2012:String) [Demo-ValidateScr
ipt-Date.ps1], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Demo-ValidateSc
ript-Date.ps1

3/15/2012 12:00:00 AM
C:\scripts\Demo-ValidateScript-Date.ps1 : Cannot validate argument on parameter
'Date'. The "
[datetime]$start="1/1/2012"
$end=Get-Date
($_ -ge $start) -AND ($_ -le $end)
" validation script for the argument with value "12/1/2011 12:00:00 AM" did no
t return true. Determine why the validation script failed and then try the comm
and again.
At line:1 char:92
+ "2/12/2012","5/1/2012","3/15/2012","12/1/2011","13/2/2012" | .\Demo-ValidateS
cript-Date.ps1 <<<< | clip
+ CategoryInfo : InvalidData: (12/1/2011:String) [Demo-ValidateSc
ript-Date.ps1], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Demo-ValidateSc
ript-Date.ps1

C:\scripts\Demo-ValidateScript-Date.ps1 : The input object cannot be bound to a
ny parameters for the command either because the command does not take pipeline
input or the input and its properties do not match any of the parameters that
take pipeline input.
At line:1 char:92
+ "2/12/2012","5/1/2012","3/15/2012","12/1/2011","13/2/2012" | .\Demo-ValidateS
cript-Date.ps1 <<<<
+ CategoryInfo : InvalidArgument: (13/2/2012:String) [Demo-Valida
teScript-Date.ps1], ParameterBindingException
+ FullyQualifiedErrorId : InputObjectNotBound,Demo-ValidateScript-Date.ps1

The valid dates pass, dates outside the range fail the validation test and the last value which isn't a legal date also fails but with a slightly different error message. As with all of the validation attributes I could have inserted this code into the body of my script and thrown my own errors. That choice is up to you. [ValidateScript()] isn't difficult to use. Just remember to insert your commands into a scriptblock, use $_ for the parameter value, and make sure the scriptblock writes either $True or $False.