This all started because I wanted to use System.IO.FileNotFoundException
in a function. Specifically, this constructor, which allows you to set the
error message and the file that caused the exception:
public FileNotFoundException (string? message, string? fileName);
The documentation states that null
(the C# null) can be passed as the first
parameter to use the default message, which allows me to avoid reinventing the
wheel or caring about things like localization of that message. Great.
PowerShell "helpfully" does automatic type conversion when needed to make any
operation work (e.g. 'a' + 2
becomes the string 'a2'
). It also does this
during parameter binding, meaning my attempt to pass $null
(the null
automatic variable) results in it getting converted to ''
(the empty string)
before being bound instead, since PowerShell reads the method signature and
assumes a string must be passed as the first parameter.
So this:
[System.IO.FileNotFoundException]::new($null, $InputFile)
becomes this:
[System.IO.FileNotFoundException]::new('', $InputFile)
Great. :/
Note: Were an integer required here,
$null
would be cast to0
instead.
Enter System.Management.Automation.Internal.AutomationNull
for the fix.
Powershell uses this for commands that return nothing, to differentiate from,
say, a collection of $null
on the pipeline. Even though it's supposed to be
internal, it can be accessed in several ways. So for example: $aNull = & {}
(here $aNull
now holds an AutomationNull.Value
, not $null
).
The nice thing about AutomationNull, is that PowerShell won't convert it like
it does with $null
, which means we can use it to force a null parameter:
# [System.IO.FileInfo]$InputFile = 'foo.bin'
if (!$InputFile.Exists) {
throw [System.IO.FileNotFoundException]::new(
[System.Management.Automation.Internal.AutomationNull]::Value,
$InputFile)
}
Bit of a mouthful right? You could create $aNull
and use it instead, but lets
cut out the middle man here and just create an AutomationNull directly:
if (!$InputFile.Exists) {
throw [System.IO.FileNotFoundException]::new((& {}), $InputFile)
}
And there we go. The FileNotFoundException
is called with the first parameter
null, and it uses the default message, as we intended. I might wrap that into a
new function or exception to save typing later.
If you want to detect the difference between $null
and AutomationNull.Value
,
just know that the latter inherits from PSObject
, so the following test works:
$pNull = $null # PowerShell null
$aNull = & {} # AutomationNull
$null -eq $pNull -and $pNull -is [PSObject] # false
$null -eq $aNull -and $aNull -is [PSObject] # true
For further details on the AutomationNull, check out this answer on StackOverflow and this issue on GitHub which goes into detail on why it exists and is in a "pubternal" (internal yet still public) state.