Context
Consider the following helper function:
Filter If-Null(
[Parameter(ValueFromPipeline=$true)]$value,
[Parameter(Position=0)]$default
) {
Write-Verbose "If ($value) {$value} Else {$default}"
if ($value) {$value} else {$default}
}
It's basically a null-coalescing operator implemented as a pipeline function. It's supposed to work like so:
PS> $myVar = $null
PS> $myVar | If-Null "myDefault" -Verbose
VERBOSE: If () {} Else {myDefault}
myDefault
However, when I set $myVar
to the first element in an empty array...
PS> $myVar = @() | Select-Object -First 1
...which should effectively be the same as $null
...
PS> $myVar -eq $null
True
PS> -not $myVar
True
...then the piping does not work anymore:
PS> $myVar | If-Null "myDefault" -Verbose
There is not output at all. Not even the verbose print. Which means If-Null
is not even executed.
The Question
So it seems like @() | select -f 1
, although being -eq
to $null
, is a somewhat different $null
that somehow breaks piping?
Can anyone explain this behaviour? What am I missing?
Additional Information
PS> (@() | select -f 1).GetType() You cannot call a method on a null-valued expression. At line:1 char:1 + (@() | select -f 1).GetType() + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: (:) [], RuntimeException + FullyQualifiedErrorId : InvokeMethodOnNull PS> (@() | select -f 1) | Get-Member Get-Member : You must specify an object for the Get-Member cmdlet. At line:1 char:23 + (@() | select -f 1) | Get-Member + ~~~~~~~~~~ + CategoryInfo : CloseError: (:) [Get-Member], InvalidOperationException + FullyQualifiedErrorId : NoObjectInGetMember,Microsoft.PowerShell.Commands.GetMemberCommand
PS> $PSVersionTable Name Value ---- ----- PSVersion 5.0.10586.117 PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...} BuildVersion 10.0.10586.117 CLRVersion 4.0.30319.42000 WSManStackVersion 3.0 PSRemotingProtocolVersion 2.3 SerializationVersion 1.1.0.1
Solution
Ansgar's explanation is correct (a better explanation can be found in mklement0's answer to the duplicate question). I just wanted to share my solution to the problem.
I fixed If-Null
such that it returns the $default
even when nothing is processed:
Function If-Null(
[Parameter(ValueFromPipeline = $true)]$value,
[Parameter(Position = 0)]$default
) {
Process {
$processedSomething = $true
If ($value) { $value } Else { $default }
}
# This makes sure the $default is returned even when the input was an empty array or of
# type [System.Management.Automation.Internal.AutomationNull]::Value (which prevents
# execution of the Process block).
End { If (-not $processedSomething) { $default }}
}
This version does now correctly handle empty pipeline results:
PS> @() | select -f 1 | If-Null myDefault
myDefault