Casting Data and Creating Validators

Casting data from one type into another type has a lot of uses. One is data validation. Let's take a look at how that works.

Casting Data: What's that anyway?

Whenever you store information, PowerShell automatically picks the right "container type" for you.

So when you assign text to a variable, the variable will internally use the System.String type. When you assign a whole number, it picks System.Int32. Every .NET object supports the GetType() method which tells you the type PowerShell picked for you:

PS> $a = "Hello"
PS> $a.GetType().FullName
System.String
PS> $a = 100
PS> $a.GetType().FullName
System.Int32

You can override the automatic type selection and convert data into any type you want which is sometimes called "casting".

Why would you do that? There are hundreds of types, and some of them are highly specialized to deal with certain data such as types for XML, DateTimes or IPAddresses. PowerShell always selects a basic or "primitive" type like Integer or String.

To get access to more specialized data, override the internal type selection and choose the best type manually.

How Do I Cast Data?

There are two ways to convert data from one type to another. The first one takes the new target type and puts it in square brackets.

The following line takes a string and converts it into a specialized DateTime. DateTime gives you all kinds of specialized methods to deal with dates so you now can easily add time intervals:

$raw = '1.1.2000'
$raw.GetType().FullName
$raw

$date = [DateTime] $raw
$date.GetType().FullName
$date
$date.AddDays(10)
$date.AddDays(-10)

This simple casting approach will fail if the raw data cannot be converted to the specified target type so you generally need an error handler to catch exceptions if you cannot make sure the raw data meets the requirements.

So for example, if you accept user input, you generally never know what the user enters. If the user enters invalid data that cannot be converted to the target type, you want to make sure the exception is caught.

Here is a simple script. It asks for your birthday and calculates your age in days. If you enter something that cannot be converted to a DateTime, the script handles the error using Trap and outputs an error message:

trap { 'Your input was invalid!'; continue }

& {
$date = Read-Host "Enter your birthday"
$difference = (New-TimeSpan $date).totalDays
'You are {0:0} days old' -f $difference
}

There is a second approach to cast data which is widely unknown: the -as operator.

This operator tries to cast the data to the new type, and if that won't work, it does not raise an exception. Instead, it simply returns $null.

With this approach, you can simplify your script because you no longer need an error handler:

$date = Read-Host "Enter your birthday"
if ($date -as [DateTime]) {
$difference = (New-TimeSpan $date).totalDays
'You are {0:0} days old' -f $difference
} else {
'Your input was invalid.'
}

Creating Custom Validators

When you look at the previous example, you can see that type casting is a great way of automatically validating data. All you need to do is try to cast to the desired target type. If it works, the validation was successful. If it fails, the data was in the wrong format. As long as you find a type that represents a certain date format, you can delegate validation entirely to the type conversion process.

For example, to write a little function that checks for valid date input, try this:

function isDate($object) {
[Boolean]($object -as [DateTime])
}

isDate('1.1.2000')
isDate('hehehe')
isDate('September 12, 2008')

Likewise, if you'd like to check for a valid IP address, don't waste your time writing own validation code or figuring out tricky regular expressions. Instead, use the System.Net.IPAddress like so:

function isIPAddress($object) {
[Boolean]($object -as [System.Net.IPAddress])
}

isIPAddress('10.10.10.10')
isIPAddress('500.10.10.10')
isIPAddress('2001:0:d5c7:a2ca:184a:7bd:a865:5fb4')

As you can see, your validator also accepts V6 IP addresses which is pretty cool. Your own regular expression validator would have had a hard time figuring that out.

With your new validator function, you can easily check user input and make sure only the desired data is entered:

function isIPAddress($object) {
[Boolean]($object -as [System.Net.IPAddress])
}

$prompt = 'Enter IP Address'
do {
$ip = Read-Host $prompt
$prompt = 'Invalid input. Try again and enter IP Address'
} while (-not (isIPAddress($ip)))

"You entered this IP address: $ip"

As Alexander and Shay pointed out to me, validators are only as good as the underlying .NET type converter. As it turns out, .NET is quite liberal and even happily accepts a single number which it turns into a (correct) IP-Address:

[system.Net.IPAddress]'1'

So in real life, you may want to look how liberal .NET conversion is, and if it is too liberal, add a simple manual check like this:

$ip -like '*.*.*.*'

Or, you look at the conversion result and check to make sure it is identical to the user input. If it is not, you know that the conversion mechanism was too liberal:

function isIPAddress($object) { 
($object -as [System.Net.IPAddress]).IPAddressToString -eq $object
}

Call for Action

If you happen to know other interesting .NET types for certain data formats, please add a comment. You have seen how you can use the -as operator in conjunction with any .NET type to validate raw data and make sure it is convertible into that type. Check out PowerShellPlus to discover types and get rich code completion!

See ya soon,

-Tobias


Posted Nov 22 2008, 10:30 PM by Tobias Weltner

Comments

Aleksandar wrote re: Casting Data and Creating Validators
on 11-26-2008 12:05 PM

This function returns $true if a given string is a valid email address:

function isEmailAddress($object) {  

   ($object -as [System.Net.Mail.MailAddress]).Address -eq $object -and $object -ne $null  

}

Concentrated Tech NSoftware Dell Compellent Sponsored by Idera and Concentrated Tech and NSoftware and Dell Compellent
Copyright 2011 PowerShell.com. All rights reserved.