Tired of hidden errors in your PowerShell scripts? Discover how validation and transformation attributes can bring precision and automation to your code!

When you assign information to variables, PowerShell, by default, doesnt do any extra work. It simply takes the value and stores it in the variable:

$test = '1.2.3.4'

$test

While convenient, this can mask errors in larger scripts and make debugging more difficult. If desired, you can ask PowerShell to invoke type conversions, run entire script blocks to validate and transform variable assignments, and emit errors when invalid data is encountered.

Assigning type

By assigning a type to a variable, any new value assigned to it must be of that type, or be convertible to it. If invalid values cannot be converted, an exception is raised:

[int]$id = 123

$id = 12.5

$id # now 12 (rounded)

$id = 'wrong' # raises an error, not convertible to int

Type assignments offer another great benefit: they allow you to select a more suitable type for your data, which can save you a lot of work. Take a look at this example:

$installed = '2.3.4.5'

$available = '10.1.2.3'



$shouldInstall = $installed -lt $available

$shouldInstall

Since PowerShell uses alphanumeric comparison for strings, "2.3.4.5" is considered greater than "10.1.2.3". You would need to invest significant effort to compare the numbers individually.

Alternatively, you can delegate this task to a more suitable type: once you assign the variables to the "[version]" type, they are compared correctly.

[version]$installed = '2.3.4.5'

[version]$available = '10.1.2.3'

$shouldInstall = $installed -lt $available

$shouldInstall

Refining types

Sometimes, there is no perfectly matching type for your data. In such cases, you can use a base type and refine it with a validation attribute.

For example, if you want a variable to store an ID number in the range of 1 to 1000 and be notified (via an exception) when an invalid ID number is encountered, you could use [int] as the base type and add [ValidateRange(1, 1000)]:

[ValidateRange(1,1000)][int]$id = 10



# this works

$id = 1000



# this raises exception

$id = -1

$id = 1001

There are several built-in validation attributes; however, [ValidateScript()] is the most flexible, as it allows you to define the validation criteria yourself.

Heres an example of a variable that can only store file paths to files that actually exist in your Windows folder:

[ValidateScript({ Test-Path -Path $_ -PathType Leaf})][string]$filepath = 'C:\Windows\explorer.exe'



# this works

$filepath = "$env:windir\notepad.exe"



# this fails

$filepath = "c:\test.txt"

Transforming data

By adding transformation attributes, you can instruct a variable on how to interpret an assignment and convert it into any form or shape you desire.

Heres an example of a custom transformation attribute:

class ServerIdAttribute : System.Management.Automation.ArgumentTransformationAttribute

{

    [object] 

Transform([System.Management.Automation.EngineIntrinsics]$engineIntrinsics, [object] $inputData)

    {

        ################## Start of custom code ##################################



        # variable assignment is found in $inputData



        # user input was already a well-formed server name

        if ($inputData -match '^Server\d{4}$')

        {

            return $inputData

        }

        
        # user input was a number, construct server name

            elseif ($inputData -is [int])

        {

            return 'Server{0:d4}' -f $inputData

        }

        
        # user input was a string, can it be converted to a number?

        elseif ($inputData -as [int])

        {

            return 'Server{0:d4}' -f ($inputData -as [int])

        }

        
        # invalid user input

        else

        {

            throw "Server names must be 'ServerXXXX' where 'XXXX' is a number."

        }

        ################## End of custom code ###################################

    }

}

This template remains the same; only the code between the markings changes, determining how $inputData (the assignment) should be converted before being stored in the variable.

The class name defines the name of your transformation attribute and must always end with 'Attribute.' In this example, the class name is ServerIdAttribute, so the transformation attribute is named [ServerId()].

Once you have defined this attribute, you can assign variables like this:

[ServerId()]$serverName = 12 

The variable now contains 'Server0012'. Since variables retain their types and attributes, whenever you assign a number to the variable, it will automatically generate a server name with a four-digit number:

Scriptrunner Image
PS C:\> $serverName

Server0999



PS C:\> $serverName = "98"



PS C:\> $serverName

Server0098



PS C:\> $serverName = 'Server0123'



PS C:\> $serverName

Server0123

Transformation attributes remain active throughout the lifetime of a variable. Whenever new values are assigned, the code inside the transformation attribute is executed.

Your ultimate PowerShell Cheat Sheet

Unleash the full potential of PowerShell with our handy poster. Whether you're a beginner or a seasoned pro, this cheat sheet is designed to be your go-to resource for the most important and commonly used cmdlets.The poster is available for download and in paper form.

Get your poster here!

Related links