Defining parameters - which method is better, and why?

by Pat Richard at 2012-09-08 06:51:18

Which method is better when defining parameters in a param block?
[Parameter(Mandatory = $True, HelpMessage = "You forgot something!")]
or
[ValidateNotNullOrEmpty()]
by DonJ at 2012-09-08 07:49:11
Well, those two things each do something different. To provide the more full-form definition for later readers:


[CmdletBinding()]
Param(
[Parameter(Mandatory=$True,HelpMessage="Computer name")][ValidateNotNullOrEmpty()][string]$computername
)


or



[CmdletBinding()]
Param(
[Parameter(Mandatory=$True,HelpMessage="Computer name")]
[ValidateNotNullOrEmpty()]
[string]$computername
)


(I like the latter version better).

If you’re asking, "which is the preferred way to ensure a parameter isn’t left empty," it depends on what you mean by "empty." In PowerShell, "empty" means "lacking a value;" but you can fill a parameter with $null and it won’t be empty ($null is considered a value). ValidateNotNullOrEmpty will throw an error if the parameter is either empty or contains only $null. Mandatory will prompt if the parameter is empty. So "which is better" depends a bit on what you want it to do.

With Mandatory, you get a shell-standard prompt for a missing parameter, but passing in a value of $null will satisfy it. ValidateNotNullOrEmpty will never prompt, will only throw an error, and will disallow $null.

I tend to prefer Mandatory, mainly because it gives someone a change to fill-in the needed information from the shell’s prompt (and I don’t have to code that prompt, which is nice). But I sometimes ALSO use ValidateNotNullOrEmpty, if I need to ensure that the values I got are non-null.
by willsteele at 2012-09-08 10:39:19
I have always found the second version listed above, where each attribute is broken out per line, because I can see, at a glance, exactly what is contained in each parameter. Otherwise, I find myself having to read through a long set of strings with varying formats, standards and patterns. Also, I tend to place a blank line between each [Parameter()] attribute collection. That way I can quickly glance at a large list of parameters and pick through the specifics with much less work. Granted, these lead to much longer scripts, if you are doing big, well-handled parameter attribute sets. However, if you are at the point of making sure it is as robust as possible I suspect the length of the param() section is arbitrary. It is for me anyway.

On a side note, I tend to avoid the pattern where you place the parameters in the function line in parentheses. It can get really ugly for larger functions. If I have to do something in a pinch I might do it that way, but, I can probably count on two hands the number of times I have liked the way that worked for me.
by DonJ at 2012-09-08 10:47:26
[quote]On a side note, I tend to avoid the pattern where you place the parameters in the function line in parentheses. It can get really ugly for larger functions. If I have to do something in a pinch I might do it that way, but, I can probably count on two hands the number of times I have liked the way that worked for me.[/quote]

As an aside, PowerShell internally stores functions with a Param() block, regardless of how you define them.
by Pat Richard at 2012-09-08 18:34:41
Thanks, Don. The second method you show is what I typically use, but started to think that setting mandatory AND using NotNullOrEmpty was redundant, and therefore, not needed.
by poshoholic at 2012-09-08 21:45:42
You are correct that there is a redundancy when you use Mandatory and ValidateNotNullOrEmpty for a parameter. Mandatory implicitly indicates that the parameter is required and that it cannot be null or empty. Personally I always put both of them even if there is a redundancy though, since it is easier to see the intended behaviour this way when reviewing the function.
by DonJ at 2012-09-09 07:41:46
Kirk, I’ve got an instance in front of me where Mandatory accepts an explicit $null.
by poshoholic at 2012-09-09 21:06:09
Are you sure? Look at the following transcript:

PS C:> function Test-Mandatory {
>> [CmdletBinding()]
>> param(
>> [Parameter(Mandatory=$true)]
>> [String]
>> $Value
>> )
>> $Value
>> }
>>
PS C:> Test-Mandatory -Value $null # Try passing in $null directly; fails as expected
test-mandatory : Cannot bind argument to parameter 'Value' because it is an empty string.
At line:1 char:23
+ Test-Mandatory -Value $null
+ ~~~~~
+ CategoryInfo : InvalidData: (:slight_smile: [test-mandatory], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorEmptyStringNotAllowed,test-mandatory

PS C:> Test-Mandatory -Value '' # Try passing in an empty string; this also fails as expected.
test-mandatory : Cannot bind argument to parameter 'Value' because it is an empty string.
At line:1 char:23
+ Test-Mandatory -Value ''
+ ~~
+ CategoryInfo : InvalidData: (:slight_smile: [test-mandatory], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorEmptyStringNotAllowed,test-mandatory

PS C:> Test-Mandatory $null # Do the same without a parameter name; still fails as expected
test-mandatory : Cannot bind argument to parameter 'Value' because it is an empty string.
At line:1 char:16
+ Test-Mandatory $null
+ ~~~~~
+ CategoryInfo : InvalidData: (:slight_smile: [test-mandatory], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorEmptyStringNotAllowed,test-mandatory

PS C:> Test-Mandatory '' # And with an empty string; another failure
test-mandatory : Cannot bind argument to parameter 'Value' because it is an empty string.
At line:1 char:16
+ Test-Mandatory ''
+ ~~
+ CategoryInfo : InvalidData: (:slight_smile: [test-mandatory], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorEmptyStringNotAllowed,test-mandatory

PS C:> Test-Mandatory # What if we don't pass any parameters, and then enter $null?

cmdlet test-mandatory at command pipeline position 1
Supply values for the following parameters:
Value: $null
$null

# That looks like it "worked", but it didn't really accept null. It took our value of $null as a literal string, as if we had passed in '$null', which is not null or empty.
PS C:> Test-Mandatory # Now do it again with an empty string

cmdlet test-mandatory at command pipeline position 1
Supply values for the following parameters:
Value: ''
# Again, it seems like it "worked", but it took our quotes as a literal string containing two single quotes, which is not null or empty.
PS C:> Test-Mandatory # Now let's do it and just press enter for the mandatory parameter; this fails as expected

cmdlet test-mandatory at command pipeline position 1
Supply values for the following parameters:
Value:
test-mandatory : Cannot bind argument to parameter 'Value' because it is an empty string.
At line:1 char:1
+ Test-Mandatory
+ ~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:slight_smile: [test-mandatory], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorEmptyStringNotAllowed,test-mandatory

''


This is the case even if I try to force it to accept null using the AllowNull attribute for the parameter. How are you passing in $null to a mandatory parameter and having it accepted?