Mastering PowerShell ValidateScript for Better Input

Published:16 October 2019 - 10 min. read

Today’s sponsor is n8n, the AI-native workflow automation tool built for ITOps and DevSecOps. With 100+ templates to get you started quickly and a powerful visual editor, you can automate complex workflows without giving up control. Check it out here.

 

 

 

 

 

When creating PowerShell functions, validating input to parameters is important. Parameter validation allows you to limit what is passed to functions. In this article, you’re going to learn how to catch problems before they become one with the PowerShell ValidateScript validation attribute.

Not a reader? Watch this related video tutorial!
Not seeing the video? Make sure your ad blocker is disabled.

Using parameter validation, you not only ensure problems don’t arise once the function runs but also promotes cleaner code since the validation logic is not placed within the body of the function.

There are several advanced parameter validation attributes available in Powershell. But in this article, you will learn about one of the most flexible parameter validations, PowerShell ValidateScript validation.

You’ll see how it works and also see examples of how to use it.

Parameter Validation (An Analogy)

Parameter Validation
Parameter Validation

I was having lunch and I suddenly felt like having a soda. I walked to the soda machine, pulled out a ₱100 bill from my pocket and inserted it to the bill slot. The machine spits it out immediately.

I then noticed that just right above the slot it says it only accepts ₱20 and ₱50 bills. I ended up not having the soda because I only had that ₱100 bill with me.

And how does the story relate to PowerShell parameter validation? Let’s break it down.

  • The soda machine serves as the function
  • The bill is the parameter
  • The ₱50 and ₱20 bills are the valid parameter values
  • The ₱100 was rejected because it is the wrong parameter value
  • As a result, the machine did not process my request because of the wrong input. Hence, no soda for me. ….and I was thirsty too!

The soda machine scenario above is just one analogy to describe the concept of parameter validation.

Parameter Validation Logic Flow

The concept of parameter validation follows a rough workflow. The image below shows an overview of how parameter validation works.

PowerShell Parameter Validation Flow
PowerShell Parameter Validation Flow
  1. The function is executed in PowerShell by calling its name and providing the parameter values
  2. PowerShell evaluates the provided values.
  3. If the result of the validation is true, PowerShell will allow the function to continue its process, before exiting.
  4. If the result of the validation is false, PowerShell will display an error and the function will terminate.

Walkthrough Requirements

More than just talking about PowerShell function parameter validation, the rest of this article will have examples that you can copy and try on your own. To follow along, you’ll need a few things to follow along.

  • A Windows 10 computer with any of the PowerShell versions below:
  • Windows PowerShell 5.1
  • PowerShell Core 6.2
  • PowerShell Core 7 (latest release of this writing is Preview 4)
  • A script editor of your choice. Like, Notepad++, PowerShell ISE, or Visual Studio Code.

Understanding the ValidateScript Validation Attribute

ValidateScript is one of the parameter validation attributes available for use in PowerShell introduced in PowerShell 3.0. It can be added inside the parameter definition block of a function, or it can also be used directly in the PowerShell console.

ValidateScript is used to validate the value of the parameter you entered. If the validation result is $true, then the script will continue to run. On the other hand, if the result is $false, the function will throw a terminating error.

Let’s dive in and see how it works.

Using ValidateScript in a Function

The most common use of the ValidateScript attribute is attached to a function parameter. In this section, you’ll create a dummy function and apply ValidateScript parameter validation.

The dummy function will perform the following actions:

  1. Accept input for the amount.
  2. Validate if the amount entered is equal to accepted values.
  3. Run the process if the validation passed.
  4. Display an error if the validation failed.

You will learn how to build this function step by step in the next section. Fire up your script editor and begin coding!

Build the Function

First, create a function and give it a name. This can be any name you choose. It is best practice to follow the Verb-Noun naming convention to make your function descriptive. For this example, use the name Get-Soda.

Function Get-Soda {
    [CmdletBinding()]
    param ()
}

Inside the param() block, insert the name of the parameter you will be using which is Bill.

Function Get-Soda {
    [CmdletBinding()]
    param ( 
        $Bill
    )
}

At this point, the function does not perform anything yet but to accept any value for the Bill parameter.

Ensure you’ve copied the Get-Soda function into your PowerShell console. Once the function is imported into the PowerShell session, test the function by running the command: Get-Soda -Bill 100.

When you run the Get-Soda function, you will notice no error is thrown and it does nothing. That’s expected at this point.

Adding Parameter Validation

Perhaps you do not want to allow all values to be passed to the Bill parameter. Using the analogy explained at the top of this article, the function shouldn’t allow ₱100 bills. It should only allow ₱50 and ₱20 bills.

Add the ValidateScript parameter validation by inserting [ValidateScript()]before the Bill parameter. Your function should look like the code below.

Function Get-Soda {
    [CmdletBinding()]
    param (
        [ValidateScript()]
        $Bill 
    )
}

Inside of the [ValidateScript()]block, insert the validation code {$_ -eq 20}. This validation code checks if the value provided to the bill parameter is equal to 20. The below snippet is how the code should look like.

Note: The $_ represents the value of the current parameter in scope. In this example, the value of $_ is the value of the parameter Bill.

Function Get-Soda {
    [CmdletBinding()]
    param (
        [ValidateScript({$_ -eq 20})]
        $Bill
    )
}

Now run this function again to confirm that the parameter validation is working by running:

PS51> Get-Soda -Bill 20
PS51> Get-Soda -Bill 30

Below you can see that when 20 is passed as a value, nothing happens but when anything other than 20 is passed, it throws an error.

Error
Error

When the Get-Soda function doesn’t throw an error, that means the function executed successfully. To demonstrate that, add the following code inside of the function to simply return a message to the console.

Write-Host "$Bill is an accepted value. Please select your soda."

The Get-Soda function will then look like below:

Function Get-Soda {
    [CmdletBinding()]
    param (
        [ValidateScript({$_ -eq 20})]
        $Bill 
    )

    Write-Host "$Bill is an accepted value. Please select your soda."
}

Now pass an accepted value like 20 to the Bill parameter. You should see the result below.

Output of Get-Soda -bill 20
Output of Get-Soda -bill 20

Displaying More Meaningful Error Messages

I’m sure you’ve noticed by now that the error message returned when the parameter value does not pass validation is not very intuitive, and ugly.

Validation error is unclear
Validation error is unclear

Sadly, there is nothing you can do about how the error looks. Until a feature that enables the formatting of the validation errors is released (hopefully), you’re stuck with it.

But, it is possible to improve it a little bit and give more meaning to the errors which can benefit your users. You will see the example of how to do that next.

Adding Custom Validation Error Using Windows PowerShell (5.1)

Expanding upon the Get-Soda function, it’s possible to throw specific error message when the value does not meet validation. To do this, you can add an if/then construct inside of the ValidateScript block.

Create a simple if/then construct like in the below example. In this example, if the Bill parameter value is not equal to 20, it will return an error message of your choosing (X is invalid. Valid value is 20 only.).

You can see an example of what this looks like below:

Function Get-Soda {
    [CmdletBinding()]
    param (
        [ValidateScript({
        if ($_ -eq 20) {
            $true
        } else {
            throw "$_ is invalid. Valid value is 20 only."
        }
    })]
    $bill
    )
    process {
        Write-Host "$bill is an accepted value. Please select your soda."
    }
}

The screenshot below shows the custom error message in action. The error still looks ugly, but this time the message is clear and understandable.

ValidateScript with a custom error message (PowerShell 5.1)
ValidateScript with a custom error message (PowerShell 5.1)

Adding Custom Validation Errors Using Using PowerShell Core (6+)

Starting with PowerShell Core 6, the ability to add custom error messages to ValidateScript parameter validation is already built-in. Let’s call this the ErrorMessage technique.

In the modified Get-Soda function code below, the [ValidateScript()] block now includes an ErrorMessage property which can be used in place throw. Now instead of using the $_ variable, you can use {0} which will represent the parameter value passed.

Function Get-Soda {
    [CmdletBinding()]
    param (
        [ValidateScript({
            $_ -eq 20            
        },
        ErrorMessage = "{0} is invalid. Valid value is 20 only."
        )]
        $Bill
	)
    Write-Host "$Bill is an accepted value. Please select your soda."
}

The screenshot below shows the expected output is exactly the same as with using the if-else and throw technique.

ValidateScript with a custom error message (PowerShell Core 7 preview 4)
ValidateScript with a custom error message (PowerShell Core 7 preview 4)

PowerShell ValidateScript Usage Examples

These are some real-world use case scenarios for which ValidateScript can be applied. Review and test these examples, then try to improve the error message on your own using the techniques you learned in the previous section.

Date Parameter Validation

This snippet is a function that accepts a start and an end date. It performs validation that the dates entered are not in the future and are not older than 90 days.

<#
    Dummy function to search logs.
    Accepts the following parameters.

        * startDate
            - date must not be older than 90 days.
            - date must not be in the future.
        * endDate
            - date must not be older than 90 days.
            - date must not be in the future.
#>
Function Search-Log {
    [CmdletBinding()]
    param (
        [parameter(Mandatory)]
        [ValidateScript({
            ($_ -gt (Get-Date -Hour 0 -Minute 0 -Second 0).AddDays(-90) -and $_ -le (Get-Date))
        })]
        [datetime]$startDate,


        [parameter(Mandatory)]
        [ValidateScript({
            ($_ -gt (Get-Date -Hour 0 -Minute 0 -Second 0).AddDays(-90) -and $_ -le (Get-Date))
        })]
        [datetime]$endDate
    )

    process {
        Write-Host "I will search the logs between [$startDate] and [$endDate"]
    } 
}

Test the function by using these commands. You can specify your own startDate and endDate values

#TEST: Dates are valid
$startDate = (Get-Date).AddDays(-90) #Today - 90 days
$endDate = (Get-Date) # Today
search-log -startDate $startDate -endDate $endDate

This sample output below is when valid date values are entered.

Search-Log result with valid date parameter values
Search-Log result with valid date parameter values

Now test it again with startDate that is older than 90 days. Then confirm that the error message is displayed.

Windows Process Parameter Validation

This next snippet is a function that accepts the name of a process. It confirms that the process is running in memory, then kills all running instances of that process or exit depending on the validation result.

<#
    A function to kill all instances of a process.
    Usage: Kill-Process -name <process name>
    Example: Kill-Process -name notepad
#>

Function Kill-Process {
    [CmdletBinding()]
    param (
        [parameter(Mandatory)]
        [ValidateScript(
            {
                if (Get-Process -Name $_) {
                    $true
                }
                else {
                    throw "A process with name $_ is not found."
                }
            }
        )]
        [string]$Name
    )
    process {
        Write-Host "Killing Process: $Name"
        Stop-Process -Name $Name -Force
    }
}

Test it using this command: Kill-Process -Name <process name>

The sample output below assumes that you are testing with the process named notepad.

  • The first command ran successfully because it found the notepad process was running and proceeded to kill it.
  • The second command failed since notepad is no longer running and the function ended with the custom error message you created.
Parameter validation error
Parameter validation error

Validating Parameters by Another Parameter Value

Now you should have an idea of the value of using ValidateScript in your functions. Bu there is one limitation that is worth mentioning. ValidateScript‘s scope is only within the parameter in use. This means that one parameter cannot use the value of other parameters.

As a workaround, cross-parameter value checking can be done by accessing the $PSBoundParameters automatic variable.

Perhaps you have a function called Send-Spam. This function has three parameters:

  1. From – The sender’s email address. (eg. [email protected]). Not mandatory.
  2. To – The recipient’s email address. (eg. [email protected]). Not mandatory.
  3. SendEmail – A switch parameter that does not require a value. But if used, the From and To parameter values will be required.

If the SendEmail switch is used, a validation code will run to confirm that the From and To parameters are used. Then it will return the value of True. On the other hand, if From and To are not in use, the function will throw an error and exit.

Copy and paste the code below into your PowerShell session.

<#
    Dummy function to send spam
    Accepts the parameters
        
        From
            - sender email address
            - cannot be null or empty
        To
            - recipient email address
            - cannot be null or empty
        SendEmail
            - a switch (on/off) parameter.
            - it will validate if 'To' and 'From' values are present.

    Usage: Send-Spam -From [email protected] -To [email protected] -SendEmail
#>

Function Send-Spam {
    [CmdletBinding()]
    param (
        [parameter()]
        [ValidateNotNullOrEmpty()] #Ensure From is not equal to $null or ""
        [mailaddress]$From,

        [parameter()]
        [ValidateNotNullOrEmpty()] #Ensure To is not equal to $null or ""
        [mailaddress]$To,

        [parameter()]
        [ValidateScript(
            {
                # Check if the From and To parameters are specified

                if ($PSBoundParameters.Keys -contains 'From' -AND
                    $PSBoundParameters.Keys -contains 'To') {
                        $true
                }
                else {
                    throw "From and To parameters are required when using SendEmail"
                }
            }
        )]
        [switch]$SendEmail
    )

    process {
        Write-Host "From: $From" -ForegroundColor Yellow
        Write-Host "To: $To" -ForegroundColor Cyan
        if ($SendEmail) {
            Write-Host "Sending Spam from '$From' to '$To'" -ForegroundColor Green
        }        
    }
}

The command shown below will result in a successful validation. Because the From and To parameters were used along with the SendEmail switch.

PS51> Send-Spam -From [email protected] -To [email protected] -SendEmail
Output from Send-Spam
Output from Send-Spam

Now test it without using the From or the To parameter. This should result in an error because the validation script will fail. See the example result below.

Running Send-Spam without the To parameter
Running Send-Spam without the To parameter

Using ValidateScript in the Console

Although the most common use of the ValidateScript attribute is to used to validate function parameters, it can also be used directly from the PowerShell console. ValidateScript is useful for testing your validation code even before incorporating it inside a script or a function.

Below are some examples of using ValidateScript outside of a function.

Validating Integer Value

This example below validates that a given integer value is greater than five.

[ValidateScript({$_ -gt 5})]$i=4

After running the above code, the expected result fails because the given value is 4 which is less than 5. The example screenshot below is what you’d expect to see.

Validation failed
Validation failed

Validating a Date Value

The next example code below shows how to validate that the given date is newer than or equal to the current date.

[DateTime][ValidateScript({$_ -ge (Get-Date)})]$date = (Get-Date)
[DateTime][ValidateScript({$_ -ge (Get-Date)})]$date = (Get-Date).AddHours(-1)

After running the first line of the code in the above snippet, the expected result is passed (True) since the provided DateTime value is the same or a few seconds greater.

The second line of code will produce a failed result because the DateTime value is one hour less than the current time. See the screenshot below for the sample output.

Date parameter validation failure
Date parameter validation failure

Summary

In this article, you learned what parameter validation is and how to use the ValidateScript parameter validation attribute.

You also learned how to expand its capability to display a more descriptive error message. While following the examples, you have experienced the results of both successful and failed validation.

You should now understand the limitation of ValidateScript in terms of cross-parameter referencing, and how you can workaround that limitation by utilizing the $PSBoundParameters automatic variable.

I hope you learned enough in this article that you can apply in your PowerShell tool-making journey!

Further Reading

Hate ads? Want to support the writer? Get many of our tutorials packaged as an ATA Guidebook.

Explore ATA Guidebooks

Looks like you're offline!