PowerShell Import-Csv: Read CSV Files Step by Step

PowerShell Import-Csv: Read CSV Files Step by Step

PowerShell Tips Editor 1 min read
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 $_.Username returns $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.

Send-Item -To