25

Effectively my problem comes down to this:

I can't have a function with a mandatory parameter and pass $null to that parameter:

Function Foo
{
    Param
    (
        [Parameter(Mandatory = $true)][string] $Bar
    )

    Write-Host $Bar
}

Foo -Bar $null

This returns foo : Cannot bind argument to parameter 'Bar' because it is an empty string.

Likewise, making it an array also fails:

Function Foo
{
    Param
    (
        [Parameter(Mandatory = $true)][string[]] $Bar
    )

    Write-Host $Bar
}

Foo -Bar 1, 2 

> 1 2

Foo -Bar 1, 2, $null

> foo : Cannot bind argument to parameter 'Bar' because it is an empty string.

In programming terms it is entirely possible to have a function that accepts a mandatory nullable parameter, but I can't find a way of doing that in PowerShell.

How do I do it?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
cogumel0
  • 2,430
  • 5
  • 29
  • 45
  • I cannot reproduce the issue in PS 2.0 - running your code doesn't result in any errors (though I had to comment out Get-Object calls - there's no stock cmdlet with such name). – Alexander Obersht Aug 23 '14 at 10:28
  • You're right, my example does work. I've edited it and the question, I wasn't very clear the first time around (partly because I didn't know exactly where the problem lied either) – cogumel0 Aug 23 '14 at 14:23
  • 1
    If you fee that `Mandatory = $true` is really necessary (but think about it - passing $null to mandatory parameter hardly makes sense) you may want to take a different route. For example, create an PSCustomObject containing your vars as properties pass the object to the function. – Alexander Obersht Aug 23 '14 at 22:47
  • @AlexanderObersht in certain cases it's needed. What if you're calling a .NET method/constructor that accepts a string where the value can be $null but *must* be specified? – cogumel0 Aug 28 '14 at 08:23
  • [related](https://stackoverflow.com/q/45720150/1404637) – alx9r Aug 27 '17 at 22:25

4 Answers4

35

You should be able to use the [AllowEmptyString()] parameter attribute for [string] parameters and/or the [AllowNull()] parameter attribute for other types. I've added the [AllowEmptyString()] attribute to the function from your example:

Function Foo {
    Param
    (
        [Parameter(Mandatory = $true)]
        [AllowEmptyString()]  <#-- Add this #>
        [string] $Bar
    )

    Write-Host $Bar
}

Foo -Bar $null

For more info, check out the about_Functions_Advanced_Parameters help topic.

Be aware that PowerShell will coerce a $null value into an instance of some types during parameter binding for mandatory parameters, e.g., [string] $null becomes an empty string and [int] $null becomes 0. To get around that you have a few options:

  • Remove the parameter type constraint. You can check for $null in your code and then cast into the type you want.
  • Use System.Nullable (see the other answer for this). This will only work for value types.
  • Rethink the function design so that you don't have mandatory parameters that should allow null.
Rohn Edwards
  • 2,499
  • 1
  • 14
  • 19
  • This answer is partially correct. AllowEmptyString() works on strings with either a null or empty value and AllowNull() works for most other things. However, there is a problem with types that are by default non-nullable (int, boolean, switch, to name a few). Had the type of the parameter been [int] and AllowNull() was specified, passing $Null would result in a value of 0. – cogumel0 Aug 23 '14 at 17:11
  • 1
    @cogumel0, you're seeing PowerShell's coercion system in effect. It will automatically coerce $null or an empty string to '0' when you try to use them as [int] types. To get around it, remove the type constraint from the parameter (or make it [PSObject]). – Rohn Edwards Aug 23 '14 at 17:25
  • Apologies for saying this, but either removing the type constraint or making it [PSObject] or [object] are rubbish options for something that can (and should) be handled properly. Making those types nullable and allowing nulls solves the problem as I explained in my answer. – cogumel0 Aug 23 '14 at 18:36
  • Also your answer states that you can use [AllowNull()] and/or [AllowEmptyString()], but it using [AllowNull()] with a mandatory string type you still get an error, because the coercion system will kick in before it evaluates whether it is $null. – cogumel0 Aug 23 '14 at 18:42
  • @cogumel0, both attributes were mentioned because I assumed you wanted to do this with more than just strings. The example used [AllowEmptyString()] for the string parameter. – Rohn Edwards Aug 23 '14 at 18:56
  • I'm also not sure why you think removing the type constraint is such a "rubbish option" but allowing null values for **Mandatory** parameters isn't. – Rohn Edwards Aug 23 '14 at 19:15
  • You are correct, I want it for more than strings, as explained in my answer. However the way it read is that for strings you could use either, and for someone else coming here looking for the answer to the same question it might come across slightly confusing. More importantly, as explained in my answer, for most standard types where coercion applies, [AllowNull()] does absolutely nothing since they are not null by the time it gets evaluated, which is what in my opinion makes your answer slightly more confusing still. – cogumel0 Aug 23 '14 at 19:15
  • What if my function is a helper function to create and populate a .NET class where the constructor for said class accepts a nullable int? I would rather accept a nullable int as well than do if (parameterIntWasPassed) { Constructor($MyVar) } else { Constructor($null) }. Why should I have to build logic to handle both the lack of a parameter and the existence of a parameter when I could accept null and pass it to the constructor knowing it will handle it either way? Just an example out of many situations where you would want a nullable type. – cogumel0 Aug 23 '14 at 19:20
  • Also remember that if I have a constructor that accepts a nullable int and I make my parameter non-mandatory int I can't even do if ($MyVar) { Constructor($MyVar) } because when I call it with the -MyVar 0 it will evaluate to false in that condition. I have to use if ($PSBoundParameters.ContainsKey('MyVar') { Constructor($MyVar) }... It's a very long winded way vs just allowing a nullable int and making it mandatory... – cogumel0 Aug 23 '14 at 19:24
  • I've modified the answer to help with the [AllowNull()] and [AllowEmptyString()] confusion. – Rohn Edwards Aug 23 '14 at 19:27
  • Thank you. **This will only work for value types.** - what do you mean by value types? – cogumel0 Aug 23 '14 at 19:29
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/59864/discussion-between-cogumel0-and-rohn-edwards). – cogumel0 Aug 23 '14 at 19:31
30

As mentioned in Rohn Edwards's answer, [AllowNull()] and/or [AllowEmptyString()] are part of the solution, but a few things need to be taken into account.

Even though the example given in the question is with a type string, the question on the title is how to pass null to a mandatory parameter, without a mention of type, so we need to expand the answer slightly.

First let us look at how PowerShell handles assigning $null to certain types:

PS C:\> [int] $null
0
PS C:\> [bool] $null
False
PS C:\> [wmi] $null
PS C:\>
PS C:\> [string] $null

PS C:\>

Analyzing the results:

  • Passing $null to an [int] returns an [int] object with value 0
  • Passing $null to a [bool] returns a [bool] object with value False
  • Passing $null to a [wmi] returns ... nothing. It does not create an object at all. This can be confirmed by doing ([wmi] $null).GetType(), which throws an error
  • Passing $null to a [string] returns a [string] object with value '' (empty string). This can be confirmed by doing ([string] $null).GetType() and ([string] $null).Length

So, if we have a function with a non-mandatory [int] parameter what value will it have if we don't pass that parameter? Let's check:

Function Foo {
    Param (
        [int] $Bar
    )
    Write-Host $Bar
}

Foo
> 0

Obviously if it was a [bool] the value with be False and if it was a [string] the value would be ''

So when we have a mandatory parameter of most standard types and we assign it $null, we are not getting $null, but rather the "default value" for that type.

Example:

Function Foo {
    Param (
        [Parameter(Mandatory = $true)][int] $Bar
    )
    Write-Host $Bar
}

Foo -Bar $null
> 0

Notice there is no [AllowNull()] at all there, yet it still returns a 0.

A [string] is slightly different, in the sense that it doesn't allow empty strings on a mandatory parameter, which is why the example in the question fails. [AllowNull()] doesn't fix it either, as an empty string is not the same as $null and so we need to use [AllowEmptyString()]. Anything else will fail.

So, where does [AllowNull()] come in play, and how to pass a "real" $null to an int, bool, wmi, etc?

Neither int nor bool are nullable types, so in order to pass a "real" $null to them you need to make them nullable:

Function Foo {
    Param (
        [Parameter(Mandatory = $true)][AllowNull()][System.Nullable[int]] $Bar
    )
    Write-Host $Bar
}

This allows a "true" $null to be passed to an int, obviously when calling Write-Host it converts the $null to a string meaning we end up with an '' again, so it will still output something, but it is a "true" $null being passed.

Nullable types like [wmi] (or a .NET class) are easier as they are already nullable from the start, so they don't need to be made nullable, but still require [AllowNull()].

As for making a [string] truly nullable, that one still eludes me, as trying to do:

[System.Nullable[string]]

Returns an error. Very likely because a system.string is nullable already, though PowerShell doesn't seem to see it that way.

EDIT

I just noticed that while

[bool] $null

gets coerced into False, doing...

Function Foo {
    Param (
        [bool] $Bar
    )
    $Bar
}

Foo -Bar $null

throws the following error:

Foo : Cannot process argument transformation on parameter 'Bar'. Cannot convert value "" to type "System.Boolean".

This is quite bizarre, even more so because using [switch] in the function above instead of [bool] works.

cogumel0
  • 2,430
  • 5
  • 29
  • 45
7

First, add an AllowNull attribute:

Function Foo
{
    Param
    (
        [Parameter(Mandatory = $true)][AllowNull()][string] $Bar
    )

    Write-Host $Bar
}

Then, when you call the function, be aware that $null when used for [string] becomes an empty string, not a null reference. Therefore (since PowerShell 3 (2012)) use [NullString]::Value like this:

Foo -Bar ([NullString]::Value)

See the documentation for System.Management.Automation.Language.NullString class.


EDIT: From the call site this appears to work, but at soon as you reach the Write-Host cmdlet within the function, $Bar will again have become an empty string. See comments below. We must conclude that PowerShell insists strings must be like that. Note that NullString (which has been useful to me in other cases) in its documentation only promises to be helpful when passing null "into a .NET method".

Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
  • 1
    Theoretically that should work, but it doesn't. The [NullString]::Value is still getting coerced into an empty string. Just replace your `Write-Host $Bar` with `$Bar -eq $null` and it will return `False`. Interestingly, if you just do `([string] [NullString]::Value) -eq $null`, it returns `True`, so the coercion is happening as a result of the function. – cogumel0 Oct 18 '16 at 20:22
  • @cogumel0 Hmm, yeah interesting, because `Foo -Bar ""` will throw with this function because we do not have `[AllowEmptyString()]`. And so will `Foo -Bar $null` because `$null` becomes the empty string as well. Therefore I thought I had the solution when my `Foo -Bar ([NullString]::Value)` did _not_ throw. – Jeppe Stig Nielsen Oct 18 '16 at 22:11
  • Your logic is correct, but alas PS has decided it shall not work. As a result, I still know no way of passing an actual `$null` to a `string` parameter (without it getting coerced into something else). – cogumel0 Oct 19 '16 at 12:00
  • @cogumel0 I agree, I edited my answer. If PS has decided so, it appears you have to make your own `[MyString]` type which contains the normal `[string]` value as a member (property). That should be possible with the new `Class` keyword, or you could use the `Add-Type` cmdlet. – Jeppe Stig Nielsen Oct 19 '16 at 12:33
1

I think you missed the "," to separate the parameters.

Param (
    [string]$ObjectName,
    [int]$ObjectId,
    [wmi]$Object
)
jww
  • 97,681
  • 90
  • 411
  • 885
Patrick
  • 2,128
  • 16
  • 24
  • I did miss it in my question, that's true, but not on my code. I'll edit the question, but this is not the answer (and wouldn't even run if this was the error) – cogumel0 Aug 23 '14 at 09:42
  • Okay. Then I have to less information. I don't understand what you would like to do. The command 'Get-Object' is a function from you? – Patrick Aug 23 '14 at 09:56
  • Yes, though it really doesn't matter... the error occurs before that so it makes absolutely no difference whether that's a function/class/cmdlet/filter or anything in between. I think the question and title are clear enough: how do I pass a null parameter to a function? It all comes down to that. – cogumel0 Aug 23 '14 at 10:08
  • I found this two links: http://technet.microsoft.com/en-us/library/hh847743.aspx and http://social.technet.microsoft.com/Forums/windowsserver/en-US/4df38957-e0f9-490d-849c-1f4e94576c59/mandatory-with-allownull?forum=winserverpowershell If you Use this it should work: Function Foo { Param ( [Parameter(Mandatory = $true)] [AllowNull()] $Bar ) Write-Host $Bar } Foo -Bar $null – Patrick Aug 23 '14 at 14:50
  • You are not assigning a type to your variable, which is something I want to avoid. Read my answer for better understanding of what the problem/solution is. – cogumel0 Aug 23 '14 at 18:46