I had one simple goal: I wanted to provide a default value for a PowerShell parameter, but I wanted to make sure that an operator did not override it with a null or empty value. PowerShell provides a very simple way to accomplish this, and you can find examples all over the Internet. However, if you have conceptualized the PowerShell components in the way that I did, or you haven’t encountered them yet, then you might not recognize that those answers apply to your problem.
TLDR Solution
To provide a default value for a PowerShell parameter but ensure that a user doesn’t override it with a null or empty value, combine the NotNullOrEmpty attribute with a default value. Do not use Mandatory at all. Example:
[CmdletBinding()] param( [ValidateNotNullOrEmpty()] [String]$ComputerName = $env:COMPUTERNAME ) process { Write-Verbose -Message "Supplied computer name is $ComputerName" }
In this script, an operator has no way to provide a null or empty value to $ComputerName.
If run without specifying a value for ComputerName, the script uses the provided default:
If the user provides a value, then the script will attempt to convert it to a String value.
If the user tries to supply an empty string or a null value, the script will not allow it:
Positional and Pipeline Enhancements
For a more useful (and professional) script, add in the ability to use this parameter with the pipeline and unnamed input. Example:
[CmdletBinding()] param( [Parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Position=1)] [ValidateNotNullOrEmpty()] [String]$ComputerName = $env:COMPUTERNAME ) process { Write-Verbose -Message "Supplied computer name is $ComputerName" }
This change helps to illustrate a practical need for this solution.
Practical Uses for ValidateNotNullOrEmpty with Default Value
The real importance of this usage really comes down to the mechanics of all the components. I will explain those next. First, I want to cover what you really get from this solution. This approach has one major purpose: it ensures that a user or script cannot (accidentally or deliberately) supply a null or empty value for a parameter. That has multiple benefits, prioritize as befits your purposes:
- You don’t need to worry about security or code quality implications of an empty or null value. Using ValidateNotNullOrEmpty can insulate you not just against users, but against malicious or improperly built scripts that pipe into yours.
- No one is never prompted for anything. This makes unattended automation much smoother. Your script may throw an error on bad input, but it will not hang waiting for someone to do something.
- Continuing on the “quality” point in the first bullet point, you don’t need to worry if the internals of your script will generate an understandable error message with empty or missing input. The basic part of “quality” is stopping execution on bad input as early as possible. This part is about providing useful, comprehensible error messages.
You do have alternatives, but none that so easily fit all the above points. That comes down to those mechanics I hinted at.
Mechanics of the Involved PowerShell Components
Let’s step through the components of PowerShell that I used or discarded as not solving the problem. Their names tell most of the story, but as you often learn in scripting/programming, you can always find nuances.
Read-Host
A lot of people trying to solve similar problems were counseled to use Read-Host. Read-Host kills unattended automation faster than anything else in the PowerShell stack except things deliberately designed to interrupt progress. I won’t tell you not to use Read-Host because I can envision some valid places, but just seeing it makes me itchy. In places where Read-Host might make sense, I would question the wisdom of using parameters.
Mandatory
Mandatory almost works. Almost. Unfortunately, it cancels out any default value. The parser will allow you to specify a default with Mandatory, which can cause confusion. Example:
[Parameter(Mandatory=$true)][String]$UserName = $env:USERNAME
If you run the owning script without explicitly specifying the UserName parameter, it will not simply supply the default and move on. If run interactively, the script will halt and ask for a value. Otherwise, it will simply halt. This is the same behavior of Mandatory if you had not tried to supply a default value.
More importantly, the mechanical part of Mandatory primarily applies to the parameter, not the parameter’s value. That means that the user must, in all cases, supply the parameter. In the case of a [String] parameter type, that also means that the user must provide a value. I was not able to trick Mandatory into accepting an empty or $null string for a parameter designed as a [String]. However, you cannot guarantee that behavior for all data types. Because that’s the not the purpose of Mandatory, I don’t think it’s fair to fault it for that. If I nitpick, I will also point out that the error that you get from using a null or empty value with Mandatory is not distinguishable from an error that you get when the script body hits an unexpected null or empty value, so it’s not very helpful.
Anyway, this article wants to help you force a script input to have a non-null/non-empty value with a default, and Mandatory negates default values. On we go.
Custom Processing in the Script Body
If you want, you can use simple parameter definitions and write your own validation code in the script.
[CmdletBinding()] param( [Parameter()][String]$UserName = $env:USERNAME ) begin { if([String]::IsNullOrEmpty($UserName)) { throw "UserName is mandatory" } } process { $UserName }
While simple, it’s also a lot of code for nothing more than checking. What I dislike most about this sort of approach is it makes you divide the handling of this single parameter into three separate locations (the param block, your input validation segment, and the place that your script utilizes the variable). In a relatively short script like this demo, that doesn’t look like a big deal. In more complicated scripts, multiple screen-lengths of code will quickly pull them apart. That makes the code more difficult to read, understand, and maintain. If you try to address that by waiting to run the validation code right before the usage code, then the script may do a lot of processing before it realizes that it has bad input and needs to bail. Nothing good comes from that.
Do everything that you can to validate parameters within the param block. It results in more readable, maintainable, and professional script.
Custom Processing in the param Block
PowerShell gives you lots of cool tricks via the ValidateScript attribute. I won’t spend a lot of time on it here, but you can read more in the official PowerShell documentation and the Internet has lots of examples.
What we’re talking about in this article would look something like the following:
[CmdletBinding()] param( [Parameter()][ValidateScript({-not ([String]::IsNullOrEmpty($_)) })][String]$UserName = $env:USERNAME ) process { $UserName }
This is much shorter than the previous script and keeps validation with input, allowing us to work without concern with how far down the first usage might occur. I wouldn’t call it easy to read, but it’s not the worst, either.
Mechanically, ValidateScript expects you to place a script block within its parenthesis. It expects that script block to act as a predicate: a fancy word programmers use for a processing block that emits a single true or false value as output. PowerShell will pass the value of the parameter to your predicate as though it was piped. That means that you will access the value with the $_ special variable. If your predicate emits $true, ValidateScript will allow the script to continue. Otherwise, it will halt with a reasonably clear error message that the parameter failed validation. In my opinion, it overexplains by displaying the validation script, so it might confuse a user more than necessary. However, it’s clearer than what Mandatory tells you about an empty or null value. Even better (at least in my usage), ValidateScript will automatically fail any null value because it can’t pass it into the validation block.
In this example, I used the [String] type’s built in test which simultaneously checks for empty and null, returning $true or $false accordingly. Other types don’t have that kind of helper, so knowing that ValidateScript already fails null values means that you only need to check other types for bad values.
I don’t like ValidateScript in this usage for one reason: PowerShell has a simple attribute that does exactly what I want without any custom script. I showed ValidateScript because I don’t know what conditions brought you to this article. If other solutions don’t help you, ValidateScript might do it.
Mechanics of ValidateNotNullOrEmpty
The ValidateNotNullOrEmpty attribute has a very clear name. For the vast majority of scripting uses, it does precisely what it says it does, which is probably exactly what you think it does. It validates that the value of the attributed parameter is neither null or empty. There were two conditions that the name of the parameter does not cover:
- What does ValidateNotNullOrEmpty do when the user does not supply the parameter at all?
- What does ValidateNotNullOrEmpty do when the scripter provides a default value?
I knew the answer to the first one before I started this journey: if the user does not supply the parameter, then ValidateNotNullOrEmpty does literally nothing. That means that if the user does not specify the parameter, then it does not trigger the validation at all, so the fact that the value is effectively null does not matter. In other words, if the user does specify the parameter, then it does not exist, which means that its validator does not exist either. It is the job of Mandatory to ensure that a parameter exists, and we already figured out that Mandatory won’t do what I want.
So, back to the default value thing. I knew that Mandatory defeats default values, so I worried that ValidateNotNullOrEmpty would do the same. For that reason, I looked for other solutions before I finally just tried it to see what would happen. As demonstrated above, ValidateNotNullOrEmpty combined with a default value ensures that the parameter always exists and has a non-null and non-empty value. The only way to get around that is to modify the script. That makes it difficult for anyone to accidentally or deliberately sneak in an empty value, and doesn’t ask you to do much. Toss in code signing of your script, and someone would have an uphill battle trying to break it. All that remains for this part of this article: a look into the mechanics.
I said that Mandatory deals with the parameter itself. The Validate* attributes care much more about the value. The trick was finding a way to guarantee that there was a parameter/value pair for the validator to care about. Since Mandatory was out of the question, I turned to default values. Mechanically, default values ensure that the parameter exists no matter what and without intervention. Essentially, it makes Mandatory moot. Because a default value guarantees the existence of a parameter, then it also guarantees the existence (and processing) of any validator attached to that parameter.
Leveraging the Understanding of PowerShell’s Validation Mechanics
As with any research writing, this article tries to explain something that the reader cares about. I don’t honestly know how many people really care about the mechanics of PowerShell parameter validation. I write a lot of scripts, and even with all that volume, I rarely have to think much about the parameters. However, realizing the implications of Mandatory‘s focus on parameters as opposed to Validate*‘s focus on values sparked curiosity. My biggest pet peeve with computer languages and software in general is useless error messages. Good parameter validation can really help that. Additionally, as attackers become more sophisticated, we need all the tools we can get to ensure that only clean input makes it into our scripts. This knowledge might not do a lot for you now, but it might save you a great deal of headache in the future.