How to Read a CSV File into an Array in PowerShell

To read a CSV file into an array in PowerShell, store the output of Import-Csv in a variable. Each row becomes an object, and the variable holds all of them:

$employees = Import-Csv -Path .\employees.csv

That one line covers most scripts. Below, I’ll show how to grab a single column, why a one-row file trips up Windows PowerShell 5.1, and how to treat numbers, headers, and new rows.

How do I read a CSV file into an array in PowerShell?

My demo file lists four employees. You can create a CSV file with headers from PowerShell or paste this into employees.csv:

EmployeeId,Name,City,State,Salary,StartDate
101,Maria Lopez,Austin,TX,85000,2026-01-12
102,Tom Baker,Denver,CO,92000,2026-02-02
103,Dana Reyes,Seattle,WA,78000,2026-03-16
104,Kevin Price,Chicago,IL,105000,2026-04-06

Import-Csv uses the first line as property names. Here’s what the variable holds once the file is read:

$employees = Import-Csv -Path .\employees.csv
$employees.GetType().FullName
"Rows: $($employees.Count)"
"First: $($employees[0].Name), $($employees[0].City)"
"Last:  $($employees[-1].Name), $($employees[-1].City)"

Output:

System.Object[]
Rows: 4
First: Maria Lopez, Austin
Last:  Kevin Price, Chicago
PowerShell Import-Csv reads a CSV file into an Object array with four rows
Import-Csv returns an Object[] array with one object per row (PowerShell 7)

Indexes start at 0, and -1 gets the last row. Both versions printed the same result.

Get one CSV column as an array

Often you only need one column. Dot notation on the array collects that property from every row, and -ExpandProperty does the same through the pipeline:

$employees = Import-Csv -Path .\employees.csv
$cities = $employees.City
$cities -join ', '
$names = $employees | Select-Object -ExpandProperty Name
"$($names.GetType().Name) with $($names.Count) names"
$names[1]

Output:

Austin, Denver, Seattle, Chicago
Object[] with 4 names
Tom Baker

Both give you plain strings without a header. The pipeline form lets you pick columns with Select-Object in longer commands. To act on each row instead, loop through the CSV with foreach.

Why does a one-row CSV not work like an array?

When the file has only one data row, Import-Csv returns a single object, not an array. That’s where PowerShell 7 and 5.1 split:

$rows = Import-Csv -Path .\contractors.csv
"Type:  $($rows.GetType().Name)"
"Count: [$($rows.Count)]"
$fixed = @(Import-Csv -Path .\contractors.csv)
"Fixed: $($fixed.GetType().Name), Count $($fixed.Count)"

Output:

Type:  PSCustomObject
Count: [1]
Fixed: Object[], Count 1
Windows PowerShell 5.1 shows an empty Count for a one-row Import-Csv result
Count is empty for a single PSCustomObject in Windows PowerShell 5.1

PowerShell 7 reported Count 1, while Windows PowerShell 5.1 printed an empty Count. The same thing happens when Where-Object leaves just one row.

Wrapping the call in @() always gives you an array, so checks like if ($rows.Count -gt 0) work in both. See how to get the array length for more Count details.

Convert CSV values to numbers before sorting

Every value Import-Csv reads is a string, even 85000. That changes how sorting behaves:

$employees = Import-Csv -Path .\employees.csv
$employees[0].Salary.GetType().Name
($employees | Sort-Object -Property Salary).Name -join ', '
($employees | Sort-Object -Property { [int]$_.Salary }).Name -join ', '
"Total: " + ($employees | Measure-Object -Property Salary -Sum).Sum

Output:

String
Kevin Price, Dana Reyes, Maria Lopez, Tom Baker
Dana Reyes, Maria Lopez, Tom Baker, Kevin Price
Total: 360000
PowerShell sorts CSV salaries as text and then as numbers with an int cast
A text sort puts 105000 first; casting to int fixes the order (PowerShell 7)

The plain sort compared text, so 105000 came first. Casting inside a script block gives the right order, which the Sort-Object guide covers in depth. Measure-Object converts the strings itself, so the total is correct.

Import a CSV without a header row

If the file starts with data, pass column names with -Header. Don’t use it on a file that already has headers, or the header line becomes an extra row:

301,Luis Ortiz,Phoenix,AZ
302,Emily Carter,Atlanta,GA
$interns = Import-Csv -Path .\interns.csv -Header 'Id', 'Name', 'City', 'State'
$interns | Format-Table -AutoSize

Output:

Id  Name         City    State
--  ----         ----    -----
301 Luis Ortiz   Phoenix AZ
302 Emily Carter Atlanta GA

CSV text that’s already in a variable doesn’t need a file. ConvertFrom-Csv turns it into the same kind of array:

$text = @'
Product,Price
Laptop,1200
Monitor,300
'@
$products = $text | ConvertFrom-Csv
"$($products.Count) products, first is $($products[0].Product)"

Output:

2 products, first is Laptop

Create an array of arrays from CSV data

Some code expects rows of plain values instead of objects. The unary comma keeps each inner array from being flattened:

$employees = Import-Csv -Path .\employees.csv
$grid = foreach ($row in $employees) { , @($row.Name, $row.City, $row.State) }
"Rows: $($grid.Count), columns: $($grid[0].Count)"
"Row 3, column 2: $($grid[2][1])"

Output:

Rows: 4, columns: 3
Row 3, column 2: Seattle

Row 3, column 2 is Seattle. For grids and true 2D arrays, check out multidimensional arrays in PowerShell.

Add rows to the CSV array and save it

A generic List is faster than += for adding rows, as array vs ArrayList explains. Fill it from the file, add a row, and write it back:

$staff = [System.Collections.Generic.List[object]]::new()
$staff.AddRange(@(Import-Csv -Path .\employees.csv))
$staff.Add([pscustomobject]@{
    EmployeeId = 105; Name = 'Grace Holt'; City = 'Miami'
    State = 'F