Validate Anything

Typecasts can be used to easily validate complex input such as IP addresses or dates. You may want to compliment this with simple pattern checks.

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 [email protected]
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 [email protected]
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.