Type casts can be an extremely powerful strategy to validate complex data formats. This approach may be much easier than using Regular Expressions.
Operator -as To The Rescue
The operator -as is PowerShells equivalent to a TryCast: the operator either successfully converts data to the desired target type, or it silently returns $null
. If a cast is not successful, it won’t throw an exception, however.
Testing Dates
Let’s assume you need to validate whether a user entered a valid date. Since there are many valid date and time formats, using Regular Expressions can be extremely complex. By trying to cast the user input to [DateTime], you leave the hard part to PowerShell.
Test-Date
tests any raw user input and returns $true
when the user input can be converted to [DateTime].
The sample below keeps prompting the user until a valid date is entered:
function Test-Date($UserInput)
{
return $UserInput -as [DateTime] -ne $null
}
do
{
$value = Read-Host -Prompt 'Enter your birthday'
} until (Test-Date -UserInput $value)
"$value is a valid Date."
$timespan = New-Timespan -Start ($value -as [DateTime])
$days = $timespan.TotalDays
"You are {0:n0} days old." -f $days
The date is then used to calculate the time difference (in days), so if the user entered his birthday, the age in days is returned.
You may be wondering why the code uses the operator -as twice. This is because I wanted to illustrate a generic test function, and
Test-Date
returns a boolean value, telling you whether the entered value was a valid date or not. It does not change the user input.So if you want to actually interpret the user input as date, you again have to convert it, and this time use the converted result. If you did not use -as again and instead submitted the raw user input, PowerShell would use explicit casting. With dates, it would use the culture-neutral conversion (locale en-US) which could yield wrong results if you are using a different locale.
Obviously you could have simplified the code and use -as only once at the expense of a less reusable approach:
do { $value = (Read-Host -Prompt 'Enter your birthday') -as [DateTime] } until ($value) $timespan = New-Timespan -Start $value $days = $timespan.TotalDays "You are {0:n0} days old." -f $days
Testing IP Addresses
As long as you can find a type that adequately represents the format you are after, you are fine. If you wanted to check for IPv4 IP addresses, you might be intrigued to use the type [System.Net.IPAddress]:
function Test-IPv4Address($IPAddress)
{
$IPAddress -as [System.Net.IPAddress] -ne $null
}
At first sight, this seems to work:
PS> Test-IPv4Address -IPAddress 10.10.10.10
True
PS> Test-IPv4Address -IPAddress hello
False
PS> Test-IPv4Address -IPAddress 10.12.3.500
False
However, with extensive testing you’ll soon discover flaws:
PS> Test-IPv4Address -IPAddress 10.12
True
PS> Test-IPv4Address -IPAddress 0
True
You Must Understand The Conversion
If you trust in automatic conversion, you must understand the conversion rules or else you’ll run into situations where the conversion succeeds even though you had expected it to fail. How come the input 10.12 successfully converts to [System.Net.IPAddress]? Find out!
[System.Net.IPAddress]10.12
The result reveals that the internal conversion algorithm happily converts this to the IP address 10.0.0.12:
Address : 201326602
AddressFamily : InterNetwork
ScopeId :
IsIPv6Multicast : False
IsIPv6LinkLocal : False
IsIPv6SiteLocal : False
IsIPv6Teredo : False
IsIPv4MappedToIPv6 : False
IPAddressToString : 10.0.0.12
Fine-Tune Conversion
By combining type conversion with a simple pattern check, in most circumstances you can create powerful and highly specific results. To check for IPv4 addresses, simply check for the typical IPv4 pattern as well:
function Test-IPv4Address($IPAddress)
{
$IPAddress -like '*.*.*.*' -and $IPAddress -as [System.Net.IPAddress] -ne $null
}
Now Test-IPv4Address
is returning $true
only if the user input was convertible to [DateTime] and matched the simple pattern.
PS> Test-IPv4Address -IPAddress 10.12.1.100
True
PS> Test-IPv4Address -IPAddress hello
False
PS> Test-IPv4Address -IPAddress 10.12
False
PS> Test-IPv4Address -IPAddress 0
False
Testing Email Addresses
Generally, the hardest part of type conversions to validate information is to find a suitable data type in the first place that adequately represents the type of information. With a little investigation effort, you’ll find types for almost any IT-related data type, though.
Here is a function that tests valid email addresses:
function Test-EmailAddress($EmailAddress)
{
$EmailAddress -as [System.Net.Mail.MailAddress] -ne $null
}
Again, make sure you test-drive the results and understand the conversion algorithm:
PS> Test-EmailAddress -EmailAddress [email protected]
True
PS> Test-EmailAddress -EmailAddress tobias
False
PS> Test-EmailAddress -EmailAddress tobias@abc
True
Apparently, for the conversion it is ok for the host name to have no extension, and domains like abc are in order. If you need stricter rules, again add simple pattern checks:
function Test-EmailAddress($EmailAddress)
{
$EmailAddress -like '?*@?*.??*' -and $EmailAddress -as [System.Net.Mail.MailAddress] -ne $null
}
Simple patterns are simple because they primarily use only two wildcards: * representing anything (including nothing), and ? representing exactly one character. By cleverly combining both, you can ensure that a minimum number of characters are required.
For example, in
Test-EmailAddress
, there need to be at least one character before and after the @ sign, and the domain part must be at least two characters.
The result is now sufficient for most purposes:
PS> Test-EmailAddress -EmailAddress [email protected]
True
PS> Test-EmailAddress -EmailAddress [email protected]
True
PS> Test-EmailAddress -EmailAddress tobias.weltner@powershell
False
PS> Test-EmailAddress -EmailAddress '@powershell.one'
False
Testing Numerics
Testing for numeric input can be tricky, but using conversion, it is a snap:
function Test-Numeric($Value)
{
$value -as [int64] -ne $null
}
Let’s test-drive:
PS> Test-Numeric hello
False
PS> Test-Numeric 12
True
PS> Test-Numeric 12.798578
True
PS> Test-Numeric +66
True
PS> Test-Numeric -8276
True
PS> Test-Numeric 0xab6f
True
PS> Test-Numeric 8.23MB
True
Try this with Regular Expressions, and you’ll see how hard it is to include all the special edge cases where users enter hex numbers or use units like MB.