How to Create an Array of Objects in PowerShell

To create an array of objects in PowerShell, put [pscustomobject]@{ ... } entries inside @( ), one per line. Each object’s keys become its properties:

$users = @(
    [pscustomobject]@{ Name = 'Alice Johnson'; State = 'TX'; Licenses = 3 }
    [pscustomobject]@{ Name = 'Bob Smith';     State = 'CO'; Licenses = 1 }
    [pscustomobject]@{ Name = 'Carol Diaz';    State = 'TX'; Licenses = 2 }
)

$users | Format-Table

Output:

Name          State Licenses
----          ----- --------
Alice Johnson TX           3
Bob Smith     CO           1
Carol Diaz    TX           2
PowerShell array of pscustomobject shown as a table
Three objects shown as three rows (PowerShell 7)

PowerShell shows the array as a table, with one row per object. I ran every example in PowerShell 7.6 and Windows PowerShell 5.1 and note the two places they differ.

Access and filter the objects

Index into the array for one object, use dot notation on the array to get a property from every object, and filter with Where-Object:

$users = @(
    [pscustomobject]@{ Name = 'Alice Johnson'; State = 'TX'; Licenses = 3 }
    [pscustomobject]@{ Name = 'Bob Smith';     State = 'CO'; Licenses = 1 }
    [pscustomobject]@{ Name = 'Carol Diaz';    State = 'TX'; Licenses = 2 }
)

"First user:       $($users[0].Name)"
"All names:        $($users.Name -join ', ')"
"Texas users:      $(($users | Where-Object State -eq 'TX').Name -join ', ')"
"Total licenses:   $(($users | Measure-Object -Property Licenses -Sum).Sum)"

Output:

First user:       Alice Johnson
All names:        Alice Johnson, Bob Smith, Carol Diaz
Texas users:      Alice Johnson, Carol Diaz
Total licenses:   6

Getting $users.Name from the whole array is called member enumeration. Where-Object covers filtering in more depth.

Change properties in the array

Objects are passed by reference, so changing a property inside a foreach loop updates the object in the array:

$users = @(
    [pscustomobject]@{ Name = 'Alice Johnson'; State = 'TX'; Licenses = 3 }
    [pscustomobject]@{ Name = 'Bob Smith';     State = 'CO'; Licenses = 1 }
    [pscustomobject]@{ Name = 'Carol Diaz';    State = 'TX'; Licenses = 2 }
)

foreach ($user in $users) {
    if ($user.State -eq 'TX') { $user.Licenses += 1 }
}
$users | ForEach-Object { '{0,-14} {1}' -f $_.Name, $_.Licenses }

Output:

Alice Johnson  4
Bob Smith      1
Carol Diaz     3

That’s different from an array of strings, where the loop variable is only a copy. Replacing values in an array explains that case.

Create an empty array and add objects

For a list you’ll add to and remove from, use a generic List. Its Add and RemoveAt methods change the list in place:

$servers = [System.Collections.Generic.List[pscustomobject]]::new()

$servers.Add([pscustomobject]@{ Name = 'WEB01'; Role = 'Web' })
$servers.Add([pscustomobject]@{ Name = 'SQL01'; Role = 'Database' })

"Servers in the list: $($servers.Count)"
$servers.RemoveAt(0)
"After RemoveAt(0):   $($servers.Name)"

Output:

Servers in the list: 2
After RemoveAt(0):   SQL01

Build the array from a loop instead of +=

Often you don’t need an empty array at all. Assign the loop itself to a variable, and PowerShell collects every object it outputs:

$report = foreach ($state in 'TX', 'CO', 'FL') {
    [pscustomobject]@{
        State   = $state
        Checked = [datetime]'2026-09-28'
        Status  = 'OK'
    }
}

"Type: $($report.GetType().Name), items: $($report.Count)"
$report | ForEach-Object { '{0}  {1:yyyy-MM-dd}  {2}' -f $_.State, $_.Checked, $_.Status }

Output:

Type: Object[], items: 3
TX  2026-09-28  OK
CO  2026-09-28  OK
FL  2026-09-28  OK

Avoid $list += $object in loops. It copies the whole array each time:

$plusEquals = Measure-Command {
    $list = @()
    foreach ($i in 1..20000) { $list += [pscustomobject]@{ Id = $i } }
}
$collect = Measure-Command {
    $list = foreach ($i in 1..20000) { [pscustomobject]@{ Id = $i } }
}

"Collecting loop output was more than 10 times faster: $($plusEquals.TotalMilliseconds / $collect.TotalMilliseconds -gt 10)"

Output:

Collecting loop output was more than 10 times faster: True

With 20,000 objects, += was about 18 times slower in PowerShell 7.6 and about 110 times slower in 5.1 on my PC. Array vs ArrayList explains why.

Use [pscustomobject], not New-Object -Property

Older scripts create objects with New-Object PSObject -Property. The hashtable it takes doesn’t keep your property order, while [pscustomobject] does:

$old = New-Object -TypeName PSObject -Property @{ Name = 'Alice'; State = 'TX'; Id = 1001 }
$new = [pscustomobject]@{ Name = 'Alice'; State = 'TX'; Id = 1001 }

"New-Object -Property: $($old.PSObject.Properties.Name -join ', ')"
"[pscustomobject]:     $($new.PSObject.Properties.Name -join ', ')"

Output in Windows PowerShell 5.1:

New-Object -Property: State, Name, Id
[pscustomobject]:     Name, State, Id
PowerShell New-Object -Property changes the order of properties
New-Object put Id before State, [pscustomobject] kept the order (Windows PowerShell 5.1)

In PowerShell 7, the New-Object order follows the hashtable and can change from run to run. The column order matters for tables and CSV files, so stick with [pscustomobject], as Microsoft’s PSCustomObject guide also shows.

Export an array of objects to CSV

Export-Csv takes its columns from the first object only. If a later object has an extra property, that column is dropped without a warning:

$contacts = @(
    [pscustomobject]@{ Name = 'Alice Johnson'; State = 'TX' }
    [pscustomobject]@{ Name = 'Bob Smith';     State = 'CO'; Phone = '303-555-0187' }
)

$contacts | Export-Csv -Path C:\psfaqs\contacts.csv -NoTypeInformation
"Columns written: $(Get-Content -Path C:\psfaqs\contacts.csv -TotalCount 1)"

$contacts | Select-Object -Property Name, State, Phone | Export-Csv -Path C:\psfaqs\contacts.csv -NoTypeInformation
"With Select-Object: $(Get-Content -Path C:\psfaqs\contacts.csv -TotalCount 1)"

Output:

Columns written: "Name","State"
With Select-Object: "Name","State","Phone"
PowerShell Export-Csv drops properties that the first object doesn't have