Reputation: 23623
A while ago I changed my Join-Object
cmdlet which appeared to cause a bug which didn’t reveal in any of my testing.
The objective of the change was mainly code minimizing and trying to improve performance by preparing a custom PSObject and reusing this in the pipeline.
As the Join-Object
cmdlet is rather complex, I have created a simplified cmdlet to show the specific issue:
(The PowerShell version is: 5.1.16299.248
)
Function Test($Count) {
$PSObject = New-Object PSObject -Property @{Name = $Null; Value = $Null}
For ($i = 1; $i -le $Count; $i++) {
$PSObject.Name = "Name$i"; $PSObject.Value = $i
$PSObject
}
}
Directly testing the output gives exactly what I expected:
Test 3 | ft
Value Name
----- ----
1 Name1
2 Name2
3 Name3
Presuming that it shouldn't matter whether I assign the result to a variable (e.g. $a
) or not, but it does:
$a = Test 3
$a | ft
Value Name
----- ----
3 Name3
3 Name3
3 Name3
So, apart from sharing this experience, I wonder whether this is programming flaw or a PowerShell bug/quirk?
Upvotes: 2
Views: 317
Reputation: 437111
Your original approach is indeed conceptually flawed in that you're outputting the same object multiple times, iteratively modifying its properties.
The discrepancy in output is explained by the pipeline's item-by-item processing:
Outputting to the console (via ft
/ Format-Table
) prints the then-current state of $PSObject
in each iteration, which gives the appearance that everything is fine.
Capturing in a variable, by contrast, reflects $PSObject
's state after all iterations have completed, at which point it contains only the last iteration's values, Name3
and 3
.
You can verify that output array $a
indeed references the very same custom object three times as follows:
[object]::ReferenceEquals($a[0], $a[1]) # $True
[object]::ReferenceEquals($a[1], $a[2]) # $True
The solution is therefore to create a distinct [pscustomobject]
instance in each iteration:
PSv3+ offers syntactic sugar for creating custom objects: you can cast a hashtable (literal) to [pscustomobject]
. Since this also creates a new instance every time, you can use it to simplify your function:
Function Test($Count) {
For ($i = 1; $i -le $Count; $i++) {
[pscustomobject] @{ Name = "Name$i"; Value = $i }
}
}
Here's your own PSv2-compatible solution:
Function Test($Count) {
$Properties = @{}
For ($i = 1; $i -le $Count; $i++) {
$Properties.Name = "Name$i"; $Properties.Value = $i
New-Object PSObject -Property $Properties
}
}
Upvotes: 3