As Lee_Dailey points out, you're adding references to the custom object ([pscustomobject]
[1]) instance stored in $a
to array $b
(given that [pscustomobject]
is a reference type), and you're updating the same custom object, so that you ended up with:
- 2 array elements that point to the very same object...
- ... whose property values are the last ones you assigned.
The most immediate fix is to create a copy of $a
before assigning new property values, which can be done with .psobject.Copy()
, which creates a - shallow - clone:
...
# Create a copy of $a to create the next array element.
$a = $a.psobject.Copy()
$a.first = "Charlie"
$a.last = "Delta"
$b += $a
Of course, you could avoid the problem and use custom-object literals (PSv3+) instead, which creates a new instance every time:
$b = @()
$b += [pscustomobject] @{
first = "Alpha"
last = "Bravo"
}
$b += [pscustomobject] @{
first = "Charlie"
last = "Delta"
}
As an aside: growing arrays with +=
is inefficient, because a new array must be created behind the scenes every time; this may not be noticeable with only a few additions / iterations, but with a larger number it's better to use a list data type such as [System.Collections.Generic.List[object]]
:
$b = New-Object System.Collections.Generic.List[object]
$b.Add([pscustomobject] @{
first = "Alpha"
last = "Bravo"
})
$b.Add([pscustomobject] @{
first = "Charlie"
last = "Delta"
})
[1] Custom objects in PowerShell:
[pscustomobject]
(which is effectively the same as [psobject]
) is PowerShell's "property bag" type that allows you to construct objects ad hoc without requiring a distinct .NET type declared ahead of time; from the perspective of .NET, a given custom object's type is always System.Management.Automation.PSCustomObject
, though the specific, dynamically attached properties can differ, by design.
Select-Object
(select
) too outputs [pscustomobject]
instances, though they report a custom type name, via PowerShell's ETS (Extended Type System), as their primary type name:
PS> ("" | select First,Last).pstypenames
Selected.System.String # custom type name to reflect the type of the *input*
System.Management.Automation.PSCustomObject # the true type name
System.Object # the name of the base type
The above method of creating custom objects is obsolete, however, and PSv3+ supports literal [pscustomobject] @{ ... }
syntax for direct construction, which has the added advantages of being able to initialize the properties as part of the same statement and being faster than Select-Object
(and also New-Object
):
# PSv3+ equivalent of the above
# (except for the custom 'Selected.System.String' type name),
# allowing you to also initialize the properties.
[pscustomobject] @{
First = $null
Last = $null
}
Note: In PSv2 you could use New-Object PSCustomObject -Property @{ ... }
to similar effect, but the order in which the properties are attached will typically not reflect the definition order.
Note that you can always instantiate regular .NET types in PowerShell as well, with the New-Object
cmdlet or, in PSv5+, alternatively with syntax [<type>]::new([...])
.
You use the same methods to instantiate PSv5+ custom classes declared with the class
keyword.