I traced the differences between $items | forEach-Object { Write-host "hello"}
and $null | ForEach-Object { Write-Host "hello"}
via Trace-Command
.
PS C:>Trace-Command -Name parameterbinding -Expression { $items | ForEach-Object { write-host "hello" } } -PSHost
DEBUG: ParameterBinding Information: 0 : BIND NAMED cmd line args [Out-Null]
DEBUG: ParameterBinding Information: 0 : BIND POSITIONAL cmd line args [Out-Null]
DEBUG: ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet [Out-Null]
....
PS C:> Trace-Command -Name parameterbinding -Expression { $null | ForEach-Object { write-host "hello" } } -PSHost
DEBUG: ParameterBinding Information: 0 : BIND NAMED cmd line args [ForEach-Object]
DEBUG: ParameterBinding Information: 0 : BIND POSITIONAL cmd line args [ForEach-Object]
DEBUG: ParameterBinding Information: 0 : BIND arg [ write-host "hello" ] to parameter [Process]
DEBUG: ParameterBinding Information: 0 : Binding collection parameter Process: argument type [ScriptBlock], parameter type [System.Management.Automation.ScriptBlock[]], collection type Array, element type [System.Management.Automation.ScriptBlock], no
It seems that $items
points to the Out-Null
cmdlet, which is shown via:
DEBUG: ParameterBinding Information: 0 : BIND NAMED cmd line args [Out-Null]
So it seems that Get-ChildItem
returns a reference to Out-Null
in case of an error. If you compare this to $null | ForEach-Object ...
you'll that the ForEach-Object
will be invoked directly:
DEBUG: ParameterBinding Information: 0 : BIND NAMED cmd line args [ForEach-Object]
What's also interesting, if you use ForEach-Object
with the -InputObject
parameter the code works as requested:
PS C:> Trace-Command -Name parameterbinding -Expression { ForEach-Object -InputObject $items -Process { write-host "hello" } } -PSHost
DEBUG: ParameterBinding Information: 0 : BIND NAMED cmd line args [ForEach-Object]
DEBUG: ParameterBinding Information: 0 : BIND arg [] to parameter [InputObject]
So my "guess" is the following. In case of an error (of Get-ChildItem
) you won't that below code prints an output:
PS C:\> Get-ChildItem -Path "notExisting" | ForEach-Object { Write-Host "found" }
That makes perfectly sense, Get-ChildItem
"calls" Out-Null
which will clear the pipeline, which will break the pipeline chain (= if nothing is found, nothing shall be printed).
Base on that, this call statement $items = Get-ChildItem -Path "someNotExistingPath"
is invoked, but Get-ChildItem
returns a null type that is not equal to $null
. When performing this code if($null -EQ $items)
PowerShell will more or less perform a implicit cast of the Get-ChildItem
-null-type to $null
. When it comes to this call $items | ForEach-Object
, nothing else should be send to the pipeline since $items
contains the reulst of Out-Null
.
UPDATE:
In the meanwhile @iRon also added a duplicate link, which explains the details. I'll keep the answer since this link doesn't show the usage of Trace-Command
. Hope thats ok for the community.
Hope that helps.