PowerShell Export-Csv: Save Data to CSV Files

PowerShell Export-Csv: Save Data to CSV Files

PowerShell Tips Editor 3 min read
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 #TYPE line in the first row causes Import-Csv to misread the file and breaks Excel imports. Always include -NoTypeInformation in 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-Object projection before every Export-Csv -Append call 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.

Send-Item -To