With the following code, $t
equals @(1,2)
.
$t = "before"
1..2 | Tee-Object -Variable t
So why is it that the next code snippet has $t
equal to "before"
instead of @(1)
*?
$t = "before"
1..2 | Tee-Object -Variable t | Select-Object -First 1
I see the same result in Powershell version 5 and version 3.
This has to do with how the pipeline works. If you use a different cmdlet, like Write-Verbose
it will work as expected:
$t = "before"
1..2 | Tee-Object -Variable t | Write-Verbose -Verbose
Consider that during a pipeline, each function or cmdlet can use a Begin
,Process
, and End
block.
All the Begin
blocks run first, then Process
is called once for each pipeline object, and then all the End
s.
The variable assignment necessarily has to happen in the End
block.
Starting in PowerShell v3, cmdlets can interrupt the pipeline.
This is great for something like Select-Object -First 1
because it means the entire pipeline can be stopped after the first object, instead of executing once for each item even though the rest will be discarded.
But it also means that the End
block never runs.
If you start powershell in v2: powershell.exe -Version 2.0
And then run your second example, it will work as expected because the pipeline couldn't be prematurely stopped in that version.
Here's a demonstration:
function F1 {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline)]
$o
)
Begin {
Write-Verbose "Begin" -Verbose
}
Process {
Write-Verbose $o -Verbose
$o
}
End {
Write-Verbose "End" -Verbose
}
}
Then call it:
1..2 | F1
vs.
1..2 | F1 | Select-Object -First 1
You could also demonstrate this with ForEach-Object
:
1..2 | ForEach-Object -Begin {
Write-Verbose "Begin" -Verbose
} -Process {
Write-Verbose $_ -Verbose
$_
} -End {
Write-Verbose "End" -Verbose
}
vs.
1..2 | ForEach-Object -Begin {
Write-Verbose "Begin" -Verbose
} -Process {
Write-Verbose $_ -Verbose
$_
} -End {
Write-Verbose "End" -Verbose
} | Select-Object -First 1
According to the documentation you linked, you can use the -Wait
parameter to turn off this optimization:
$t = "before"
1..2 | Tee-Object -Variable t | Select-Object -First 1 -Wait
This will populate $t
but perhaps not with the value you wanted. It will contain @(1,2)
, presumably since the -OutVariable
was placed on Tee-Object
and not on Select-Object
.
Remember that the "result" of the pipeline is what gets returned from the execution (left side of the =
), and that is correct in all instances.
-OutVariable
is something implemented by some cmdlets, and it most likely has to be implemented in the End
block for that particular cmdlet, so to predict what it will give is highly dependent on understanding the execution flow of the pipeline.
So to answer the question in your comment, it appears to me to be implemented correctly. Am I misunderstanding your assertion?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With