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