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
Bob’s Phone value was lost until Select-Object listed every column (PowerShell 7)

List every column with Select-Object before exporting whenever your objects don’t all have the same properties. The Export-Csv documentation covers -Append and -Force.

Add a calculated property to every object

Select-Object with * keeps the existing properties and adds a new one:

$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 | Select-Object -Property *, @{ Name = 'Cost'; Expression = { '{0:C0}' -f ($_.Licenses * 12) } } |
    Format-Table

Output:

Name          State Licenses Cost
----          ----- -------- ----
Alice Johnson TX           3 $36
Bob Smith     CO           1 $12
Carol Diaz    TX           2 $24

Microsoft’s guide to arrays has more on arrays of objects and member enumeration.

Frequently Asked Questions

How do I create an array of objects in PowerShell?

Put [pscustomobject] entries inside @(): $users = @( [pscustomobject]@{ Name = 'Alice' }, [pscustomobject]@{ Name = 'Bob' } ).

How do I create an empty array of objects?

Use $list = [System.Collections.Generic.List[pscustomobject]]::new() and add items with $list.Add($object).

How do I add an object to an array in a loop?

Assign the loop to a variable: $result = foreach ($x in $items) { [pscustomobject]@{ ... } }. It’s much faster than +=.

How do I get one property from every object in the array?

Use dot notation on the array: $users.Name returns every Name value.

Why is a column missing from my exported CSV?

Export-Csv uses the first object’s properties as columns. Add Select-Object -Property Name, State, Phone before exporting.

Related guides on objects and collections: