So for the benefit of those coming later, just know that these are exactly the same:
Get-PSSession | Write-Output -InputObject {$_}
Get-PSSession | Write-Output
Get-PSSession
E.e., Write-Output is unnecessary. But I get why you’re using it in this example, and it plays into why it’s unnecessary. It’s important to know that $_ isn’t globally defined in PowerShell; it doesn’t always mean ‘the thing that came in from the pipeline’. $_ only works in specific areas where PowerShell has been programmed to look for it.
In the above example, the -InputObject parameter of Write-Output is coded to bind objects of type [object] from the pipeline. As everything inherits from System.Object, that means -InputObject will accept literally anything. That’s why the above works.
Get-PSSession | Invoke-Command -Session {$_} -ScriptBlock {ls}
The -Session parameter doesn’t accept pipeline input, as described in the command’s documentation. So you’re trying to “work around” that, I think.
By enclosing $_ in {}, you’ve not “executed” $_ to see if it contains anything; you’ve instead made a script block, which cannot be coerced into being a session. Thus, the error you got.
Your first example “works,” but it mainly does so by accident; Write-Object isn’t really intended to be used that way, and you’ve just stumbled across a situation where it does.
The “correct” workaround (one of many) might be:
Invoke-Command -Session (Get-PSSession) -ScriptBlock {ls}
In your last example, ForEach-Object is in fact one of the magic places where $_ works. By removing {}, you’ve stopped creating a script block and instead are allowing ForEach-Object to treat $_ as it normally would - to represent the piped-in object.