How can I prevent a string argument changing from null to empty when bound to a parameter?

To summarize and complement the information from the question, answers, and comments:

tl;dr:

It's best not to fight PowerShell's design of not allowing [string] variables to be $null, and to limit use of [NullString]::Value to calls to .NET methods.


  • PowerShell converts $null to '' (the empty string) when it is assigned to [string]-typed [parameter] variables, and parameter variables also default to ''.

    • The only exception is the use of uninitialized [string] properties in PSv5+ custom classes, as alxr9 (the OP) points out: class c { [string] $x }; $null -eq ([c]::new()).x indeed yields $True implying that property .x contains $null. However, this exception is likely accidental and probably a bug, given that when you initialize the property with $null or assign $null to it later, the conversion to '' again kicks in; similarly, using return $null from a [string]-typed method outputs ''.

    • The exception aside, PowerShell's behavior differs from C# string variables / parameters, to which you can assign / pass null directly, and which default to null in certain contexts. string is a .NET reference type, and this behavior applies to all reference types.
      (Since reference type instances can inherently contain null, there is no need for a separate nullable wrapper via System.Nullable`1, which is indeed not supported (it works for value types only).)

  • As noted in the question (update 5), PowerShell's departure from C#'s behavior is by (design, and it's changing it is not an option for reasons of backward compatibility alone.

  • [NullString]::Value was introduced in v3 specifically to allow passing null to string parameters of .NET methods - and while use in pure PowerShell code wasn't explicitly discouraged or prevented, the unexpected behavior in update 4 and the comments by a core PowerShell team member (see below) suggest that such uses weren't anticipated.

    • Caveat: While it is possible to use [NullString]::Value in pure PowerShell code, there may be pitfalls beyond the one discussed below, given that use of [NullString]::Value was never intended outside the context of calling .NET methods; to quote a core member of the PowerShell team:

Parameters to C# methods was the target scenario for [NullString]::Value, and I will say that might be the only reasonable scenario.

  • A workaround is to type your (parameter) variable as [object] or to not type-constrain it at all, which amounts to the same. Such variables happily accept $null, but note that you may have to stringify (convert to [string]) non-$null values yourself (although PowerShell does that for you automatically in explicit or implied string contexts) - see the penultimate code example below.

If, despite the advice above, you do need a [string] parameter variable that you can pass $null to via [NullString]::Value, as in update 4 in your question, there is an - obscure - workaround for the optimization bug that prevents your code from working, thanks to sleuthing by PetSerAl:

function f {
    param (
        [string] $x
    )
    # Workaround; without this, even passing [NullString]::Value 
    # returns '' rather than $null            
    if ($False) { Remove-Variable } 
    return $x
}

$r = f -x ([NullString]::Value)
$r.GetType().Name  # now fails, because $r is $null

Note that when assigning / passing [NullString]::Value to a [string]-typed [parameter] variable, it is instantly converted to $null (in the case of a parameter variable, only if the bug gets fixed or with the workaround in place). However, once $null has been successfully stored in the variable this way, it can apparently be passed around as such (again, only if the bug gets fixed or with the workaround in place).


If you don't want to rely on the workaround / wait for the fix and/or don't want to burden the caller with having to pass [NullString]::Value instead of $null, you can build on the answers by Curios and Jason Schnell, which rely on using an untyped (implicitly [object]-typed) or explicitly [object]-typed parameter, which can accept $null as-is:

function f {
    param (
        [AllowNull()] # Explicitly allow passing $null.
                      # Note: Strictly speaking only necessary with [Parameter(Mandatory=$True)]
        $x # Leave the parameter untyped (or use [object]) so as to retain $null as-is
    )

    # Convert $x to a type-constrained [string] variable *now*:
    if ($null -eq $x) {
        # Make $x contain $null, despite being [string]-typed
        [string] $x = [NullString]::Value
    } else {
        # Simply convert any other type to a string.
        [string] $x = $x
    }

    # $x is now a bona fide [string] variable that can be used
    # as such even in .NET method calls.

    return $x
}

It's somewhat cumbersome, but enables the caller to pass $null directly (or any string, or a type of any other instance that will be converted to a string).

A slight down-side is that this approach doesn't allow you to define positional parameters in the same position via different parameter sets that are selected by the parameters' specific types.


Finally, it's worth mentioning that if it's sufficient to detect when a (non-mandatory) parameter was omitted, you can check $PSBoundParameters:

function f {
    param (
        [string] $x
    )

    if ($PSBoundParameters.ContainsKey('x')) { # Was a value passed to parameter -x?
        "-x argument was passed: $x"
    } else {
        "no -x argument passed."
    }
}

As stated, this only works for the omission case (and therefore doesn't work for mandatory parameters at all). If you pass $null, the usual conversion to '' kicks in, and you won't be able to distinguish between passing $null and ''.
(Though if you added the above workaround / waited for the bug fix, you could again pass [NullString]::Value to effectively pass $null, or even use [NullString]::Value as the parameter default value.)


function f {
    param (
        [AllowNull()]$x
    )
    return $x
}

$r = f -x $null

By removing the [string] and using [AllowNull()] the above function will now allow you to pass in a null object or an empty string. You can check for the type using $x.GetType with an if statement and determining if $x is null or an empty string.


By default, [string] assigns default value as [string]::Empty, so the parameter definition will convert it whenever enters function f.
a. You can change the parameter as [object]$x

[object]$newparamnull -eq $null
[string]$newparamstring -eq [string]::Empty    

b. The previous change will do the job:

function f {
    param (
        [AllowNull()]
        [object]
        $x)
   if($x -eq $null) {
      write-output "null" 
   }
   elseif($x -eq [string]::empty){
      write-output "empty"
   } 
   else {"other"}
}

Test:

f -x $null
f -x [string]::empty
f -x "aaa"