PowerShell Import-Csv: Read CSV Files Step by Step

IT automation lives on lists: server inventories, user account tables, patch schedules, license rosters. Almost every list in an enterprise is a CSV file. PowerShell Import-Csv turns those files into arrays of objects instantly — no string parsing, no column-count arithmetic. Each row becomes an object with named properties matching the CSV headers. This guide covers the full syntax, delimiter handling, header customization, and practical bulk processing patterns.
Quick Answer / TL;DR
# Read a CSV and loop through each row
Import-Csv 'C:\Data\users.csv' | ForEach-Object {
Write-Output "User: $($_.Username), Email: $($_.Email)"
}
Basic Import-Csv Syntax
Pass the file path to Import-Csv and it returns one object per row, with column headers as property names:
# Import a CSV file
$users = Import-Csv 'C:\Data\users.csv'
# See what you got
$users.Count # Number of rows
$users[0] # First row as an object
$users[0].Username # Access a specific column
# CSV file contents:
# Username,Email,Department,StartDate
# alice,[email protected],Engineering,2023-03-15
# bob,[email protected],Marketing,2022-07-01
2
@{Username=alice; [email protected]; Department=Engineering; StartDate=2023-03-15}
alice
Every value from a CSV comes in as a string. If you need to compare dates or do arithmetic on numbers, cast them explicitly: [DateTime]$_.StartDate or [int]$_.Port.
Accessing Row Properties
Properties match the column headers. Access them with dot notation:
$servers = Import-Csv 'C:\Data\servers.csv'
# Access specific properties
foreach ($server in $servers) {
Write-Output "Server: $($server.Hostname), IP: $($server.IPAddress)"
}
# Filter and act on specific rows
$servers | Where-Object { $_.Environment -eq 'Production' } |
ForEach-Object {
Write-Output "Production server: $($_.Hostname)"
}
Server: web01, IP: 192.168.1.10
Server: db01, IP: 192.168.1.20
Production server: web01
Custom Delimiter with -Delimiter
Not all CSVs use commas. Use -Delimiter for tab-separated, semicolon-separated, or pipe-delimited files:
# Semicolon-delimited (common in European locale Excel exports)
Import-Csv 'C:\Data\export.csv' -Delimiter ';'
# Tab-delimited
Import-Csv 'C:\Data\export.tsv' -Delimiter "`t"
# Pipe-delimited
Import-Csv 'C:\Data\export.txt' -Delimiter '|'
If your file opens in Notepad and columns look merged rather than separated, the wrong delimiter is almost always the cause.
Providing Headers with -Header
When the CSV file has no header row, supply column names with -Header:
# CSV with no headers:
# alice,[email protected],Engineering
# bob,[email protected],Marketing
$users = Import-Csv 'C:\Data\users-noheader.csv' -Header Username, Email, Department
$users[0].Username # alice
$users[0].Department # Engineering
# Or add headers when the first row IS data
$raw = Import-Csv 'C:\Data\rawdata.csv' -Header Server, Port, Status, LastCheck
alice
Engineering
Filtering Rows with Where-Object
Filter CSV data before processing to work only with relevant rows:
$patches = Import-Csv 'C:\Data\patch-report.csv'
# Only overdue patches
$overdue = $patches | Where-Object { $_.Status -eq 'Overdue' }
# Filter by date (cast the string to DateTime)
$recentUsers = Import-Csv 'C:\Data\users.csv' |
Where-Object { [DateTime]$_.StartDate -gt (Get-Date).AddDays(-90) }
# Multiple conditions
$criticalServers = Import-Csv 'C:\Data\servers.csv' |
Where-Object { $_.Environment -eq 'Production' -and $_.Status -ne 'Online' }
Bulk Action: Loop Through and Process Each Row
The most powerful Import-Csv pattern — read a list and act on every row:
# Create user accounts from a CSV
$newUsers = Import-Csv 'C:\Data\new-users.csv'
foreach ($user in $newUsers) {
$params = @{
Name = $user.Username
DisplayName = "$($user.FirstName) $($user.LastName)"
EmailAddress = $user.Email
Department = $user.Department
AccountPassword = (ConvertTo-SecureString $user.TempPassword -AsPlainText -Force)
Enabled = $true
}
try {
New-ADUser @params
Write-Output "Created: $($user.Username)"
} catch {
Write-Warning "Failed to create $($user.Username): $($_.Exception.Message)"
}
}
Created: alice.smith
Created: bob.jones
WARNING: Failed to create carol.white: The specified user already exists.
This pattern — import CSV, loop, act, handle errors — is the basis of most bulk IT automation in PowerShell. The CSV becomes the declarative spec; the script is the engine that executes it.
Common Errors and Fixes
-
BOM character in UTF-8 files breaks first column header: If the first column name appears as
Username(with a weird leading character), the file has a UTF-8 BOM. The BOM character becomes part of the header name, so$_.Usernamereturns$null. Either save the CSV without BOM, or strip it:(Get-Content $path -Raw).TrimStart([char]0xFEFF) | ConvertFrom-Csv. -
Column names with spaces need quote wrapping: A CSV header like
First Name(with a space) is valid but requires bracket notation:$_.'First Name'. Alternatively, preprocess the CSV to replace spaces with underscores in headers before importing.
Related Cmdlets / See Also
Wrapping Up
Import-Csv turns any CSV file into a PowerShell object collection in one line. Access columns by their header names, filter with Where-Object, and loop with foreach to build bulk automation. Remember all CSV values are strings — cast to [int] or [DateTime] when you need typed comparisons. Your next step: take a server list CSV and build a script that pings each host and reports which ones are down.


