PowerShell Export-Csv: Save Data to CSV Files

The deliverable at the end of every sysadmin automation task is usually a report — and that report needs to open in Excel. PowerShell Export-Csv saves any pipeline output to a CSV file that opens cleanly in Excel, Sheets, or any spreadsheet tool. It takes one cmdlet at the end of your pipeline. This guide covers the essential flags, appending data, controlling delimiters, selecting columns, and exporting large datasets efficiently.
Quick Answer / TL;DR
# Export running services to CSV
Get-Service | Where-Object { $_.Status -eq 'Running' } |
Export-Csv -Path 'C:\Reports\services.csv' -NoTypeInformation
Basic Export-Csv Syntax
Pipe any object collection to Export-Csv with a target path:
# Export all processes
Get-Process | Export-Csv -Path 'C:\Reports\processes.csv' -NoTypeInformation
# Export sorted results
Get-Process |
Sort-Object WorkingSet -Descending |
Export-Csv -Path 'C:\Reports\processes-by-memory.csv' -NoTypeInformation
# Verify the file was created
Get-Item 'C:\Reports\processes.csv' | Select-Object Name, Length
Name Length
---- ------
processes.csv 385024
The resulting file has a header row with all property names and one data row per object. All values are quoted in the CSV. Without -NoTypeInformation, the first line is a type comment like #TYPE System.Diagnostics.Process — almost always unwanted.
Removing the TypeInfo Header with -NoTypeInformation
Always include -NoTypeInformation. Without it, the first line of the CSV is a type annotation that confuses Excel and breaks Import-Csv:
# Bad — first line is "#TYPE System.ServiceProcess.ServiceController"
Get-Service | Export-Csv 'C:\Reports\bad.csv'
# Good — clean CSV with just headers and data
Get-Service | Export-Csv 'C:\Reports\services.csv' -NoTypeInformation
# PowerShell 6+ equivalent flag
Get-Service | Export-Csv 'C:\Reports\services.csv' -UseQuotes AsNeeded
In PowerShell 7+, -NoTypeInformation is the default behavior, but including it explicitly keeps your scripts compatible with Windows PowerShell 5.1.
Appending to Existing CSV
Use -Append to add rows to an existing CSV without overwriting it:
$reportPath = 'C:\Reports\daily-events.csv'
# First run — creates the file
Get-EventLog -LogName System -Newest 50 |
Select-Object TimeGenerated, EntryType, Source, Message |
Export-Csv -Path $reportPath -NoTypeInformation
# Second run — appends without duplicating headers
Get-EventLog -LogName Application -Newest 50 |
Select-Object TimeGenerated, EntryType, Source, Message |
Export-Csv -Path $reportPath -NoTypeInformation -Append
# Count total rows
(Import-Csv $reportPath).Count
100
When appending, ensure the columns match exactly. If you -Append data with different columns, the CSV becomes misaligned and the extra columns either appear empty or cause format issues.
Custom Delimiter (Semicolon for EU Locale)
European Excel installations often expect semicolons instead of commas. Set this with -Delimiter:
# Semicolon-delimited for EU Excel
Get-Process |
Select-Object Name, CPU, WorkingSet |
Export-Csv -Path 'C:\Reports\processes-eu.csv' -NoTypeInformation -Delimiter ';'
# Tab-delimited
Get-Service |
Export-Csv -Path 'C:\Reports\services.tsv' -NoTypeInformation -Delimiter "`t"
Selecting Columns Before Export
Use Select-Object before Export-Csv to control which columns appear in the output and add computed columns:
# Only export the columns you want
Get-Process |
Sort-Object WorkingSet -Descending |
Select-Object Name, Id,
@{ Name='MemMB'; Expression={ [math]::Round($_.WorkingSet/1MB, 1) } },
@{ Name='CPURounded'; Expression={ [math]::Round($_.CPU, 1) } } |
Export-Csv -Path 'C:\Reports\process-report.csv' -NoTypeInformation
# Service report with computed fields
Get-Service |
Select-Object Name, DisplayName, Status, StartType |
Sort-Object Status, Name |
Export-Csv -Path 'C:\Reports\services.csv' -NoTypeInformation
# C:\Reports\process-report.csv:
# "Name","Id","MemMB","CPURounded"
# "chrome","4892","523.2","1245.3"
# "outlook","7123","187.4","12.1"
Export Large Datasets Efficiently
For very large datasets, streaming through the pipeline avoids loading everything into memory at once:
# Stream large log files to CSV without loading all into memory
Get-Content 'C:\Logs\access.log' |
Where-Object { $_ -match 'ERROR' } |
ForEach-Object {
$parts = $_ -split '\s+', 4
[PSCustomObject]@{
DateTime = $parts[0] + ' ' + $parts[1]
Level = $parts[2]
Message = $parts[3]
}
} |
Export-Csv -Path 'C:\Reports\errors.csv' -NoTypeInformation
# Export result of a slow operation incrementally
Get-ChildItem 'C:\' -Recurse -File -ErrorAction SilentlyContinue |
Select-Object FullName,
@{ Name='SizeMB'; Expression={ [math]::Round($_.Length/1MB,2) } },
LastWriteTime |
Export-Csv -Path 'C:\Reports\file-inventory.csv' -NoTypeInformation
Common Errors and Fixes
-
#TYPE header appearing in output — always use -NoTypeInformation: The
#TYPEline in the first row causesImport-Csvto misread the file and breaks Excel imports. Always include-NoTypeInformationin Windows PowerShell 5.1. In PowerShell 7+, the header is omitted by default. -
Appending with different columns creates misaligned rows: If the appended data has different properties than the original data, the columns don’t align. Always use the same
Select-Objectprojection before everyExport-Csv -Appendcall to ensure column consistency.
Related Cmdlets / See Also
Wrapping Up
Export-Csv is a one-cmdlet report generator. Always use -NoTypeInformation, use Select-Object to control columns, and use -Append to accumulate data across runs. For EU locales, set -Delimiter ';'. Your next step: run your most common system report and export it to CSV so you can share it with a colleague or open it in Excel for analysis.


