Reading & Writing Files
Note
Read and write text file contents with Get-Content, Set-Content, Add-Content, and Out-File — and know when to reach for each one.
Overview
PowerShell has four core cmdlets for working with file contents, and they overlap just enough to be confusing at first. Get-Content reads. Set-Content and Out-File both write and overwrite. Add-Content appends. The difference that actually matters day to day is encoding defaults and pipeline behavior — get those right and the rest is straightforward.
Basic Syntax
Get-Content -Path "C:\Temp\file.txt"
Set-Content -Path "C:\Temp\file.txt" -Value "New content"
Add-Content -Path "C:\Temp\file.txt" -Value "Appended line"
Get-Process | Out-File -Path "C:\Temp\processes.txt"
Key Points
Get-Contentreturns an array of strings (one per line) by defaultSet-ContentandOut-Fileboth overwrite the file — useAdd-Contentto appendSet-Contentwrites strings as-is;Out-Fileformats objects the way the console would display them- Always specify
-Encodingexplicitly when it matters — defaults have changed between PowerShell versions
Reading Files
Get-Content Basics
# Read entire file as an array of lines
$lines = Get-Content -Path "C:\Temp\log.txt"
$lines.Count
# Read as a single string instead of an array
$text = Get-Content -Path "C:\Temp\log.txt" -Raw
# Read only the first N lines
Get-Content -Path "C:\Temp\log.txt" -TotalCount 10
# Read only the last N lines (tail)
Get-Content -Path "C:\Temp\log.txt" -Tail 20
-Raw vs Default Array Output
Get-Content without -Raw returns one string per line, which is what you want for foreach loops or Select-String-style filtering. -Raw returns the whole file as a single string, which is what you want for ConvertFrom-Json, regex across line breaks, or anything that treats the file as one blob of text.
Live Tail (Follow a Growing File)
# Watch a log file as new lines are written, like tail -f
Get-Content -Path "C:\Logs\app.log" -Tail 10 -Wait
Filtering While Reading
# Only lines containing "ERROR"
Get-Content -Path "C:\Logs\app.log" | Where-Object { $_ -match "ERROR" }
# Skip the header row of a data file
Get-Content -Path "C:\Data\export.txt" | Select-Object -Skip 1
Writing Files
Set-Content — Overwrite
# Overwrite with a single line
Set-Content -Path "C:\Temp\output.txt" -Value "Report generated $(Get-Date)"
# Overwrite with multiple lines (array)
$lines = @("Line 1", "Line 2", "Line 3")
Set-Content -Path "C:\Temp\output.txt" -Value $lines
# Specify encoding explicitly
Set-Content -Path "C:\Temp\output.txt" -Value "Data" -Encoding UTF8
Set-Content Always Overwrites
# BAD - This replaces the entire file every time it runs
Set-Content -Path "C:\Logs\app.log" -Value "New entry"
# GOOD - Use Add-Content if you want to append instead
Add-Content -Path "C:\Logs\app.log" -Value "New entry"
Set-Content — each call replaces the previous contents completely. Add-Content — Append
# Append a single line
Add-Content -Path "C:\Logs\app.log" -Value "$(Get-Date) - Script started"
# Append multiple lines
Add-Content -Path "C:\Logs\app.log" -Value @("Line A", "Line B")
# Common logging pattern
function Write-Log {
param([string]$Message)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
Add-Content -Path "C:\Logs\script.log" -Value "[$timestamp] $Message"
}
Out-File — Redirect Command Output
# Redirect formatted output (like the console would show it) to a file
Get-Process | Out-File -Path "C:\Temp\processes.txt"
# Append instead of overwrite
Get-Date | Out-File -Path "C:\Temp\log.txt" -Append
# Widen the output so table columns don't get truncated
Get-Process | Out-File -Path "C:\Temp\processes.txt" -Width 200
Out-File vs Set-Content for Objects
Out-File runs objects through PowerShell's default formatter first — the same thing that happens when you print to the console. That's great for human-readable reports, but it means Out-File output is text-formatted, not structured. If you need the raw data back later, use Export-Csv or ConvertTo-Json, not Out-File.
Working with CSV, JSON, and XML
# CSV round-trip
$data = Import-Csv -Path "C:\Data\users.csv"
$data | Export-Csv -Path "C:\Data\users-copy.csv" -NoTypeInformation
# JSON round-trip
$config = Get-Content -Path "C:\Config\settings.json" -Raw | ConvertFrom-Json
$config | ConvertTo-Json -Depth 5 | Set-Content -Path "C:\Config\settings.json"
# XML round-trip
[xml]$xmlContent = Get-Content -Path "C:\Data\config.xml" -Raw
$xmlContent.Save("C:\Data\config-copy.xml")
JSON Needs -Raw
ConvertFrom-Json expects a single string, not an array of lines. Always pipe Get-Content -Raw into it — without -Raw, multi-line JSON gets split into separate strings and conversion fails or silently returns wrong results. See Working with JSON for the full reference.
Important Parameters
| Parameter | Type | Description | Example |
|---|---|---|---|
-Path | String | File to read or write | Get-Content -Path "C:\a.txt" |
-Raw | Switch | Get-Content: return one string, not an array | Get-Content -Raw |
-Tail | Int | Get-Content: return only the last N lines | Get-Content -Tail 20 |
-Wait | Switch | Get-Content: keep watching the file for new lines | Get-Content -Tail 5 -Wait |
-Value | Object | Set/Add-Content: content to write | Set-Content -Value "text" |
-Append | Switch | Out-File: append instead of overwrite | Out-File -Append |
-Encoding | String | Character encoding (UTF8, ASCII, Unicode) | Set-Content -Encoding UTF8 |
-Force | Switch | Overwrite read-only files | Set-Content -Force |
Common Patterns
# Pattern 1: Read, transform, write back
$content = Get-Content -Path "C:\Data\input.txt"
$transformed = $content | ForEach-Object { $_.ToUpper() }
Set-Content -Path "C:\Data\output.txt" -Value $transformed
# Pattern 2: Simple logging function
function Write-Log {
param([string]$Message, [string]$Path = "C:\Logs\script.log")
"$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - $Message" | Add-Content -Path $Path
}
# Pattern 3: Read a config file safely with a default
$configPath = "C:\Config\settings.json"
$config = if (Test-Path $configPath) {
Get-Content -Path $configPath -Raw | ConvertFrom-Json
} else {
[PSCustomObject]@{ RetryCount = 3; Timeout = 30 }
}
Real-World Examples
Example: Rotating Error Log Extractor
Scenario: Pull every "ERROR" line from today's log into a separate summary file, tagged with a timestamp.
$logPath = "C:\Logs\app.log"
$summaryPath = "C:\Logs\errors-summary.txt"
$errors = Get-Content -Path $logPath | Where-Object { $_ -match "ERROR" }
if ($errors) {
$header = "=== Errors extracted $(Get-Date) - $($errors.Count) found ==="
Add-Content -Path $summaryPath -Value $header
Add-Content -Path $summaryPath -Value $errors
}
Explanation: Get-Content reads line-by-line so Where-Object -match can filter per line. Add-Content appends rather than overwrites, so the summary file accumulates history across runs instead of losing it.
Tips & Tricks
Reading Large Files
# BAD - loads the entire file into memory at once
$lines = Get-Content -Path "C:\Logs\huge.log"
# BETTER - stream line by line without holding it all in memory
Get-Content -Path "C:\Logs\huge.log" | ForEach-Object {
# process $_ one line at a time
}
Get-Content directly into ForEach-Object processes the file as a stream instead of materializing the whole array first — much lighter on memory for multi-gigabyte logs. Encoding Mismatches
Writing a file with -Encoding UTF8 in Windows PowerShell 5.1 adds a byte-order mark (BOM) that some tools choke on; PowerShell 7+ writes UTF-8 without a BOM by default. If a downstream tool complains about the first few bytes of a file PowerShell generated, check -Encoding explicitly rather than assuming defaults match across versions.
Related Topics
- File & Folder Management - Creating and organizing the files you read and write
- Path Operations - Building safe, reliable paths before reading or writing
- Working with JSON - Structured data round-trips through Get-Content and ConvertFrom-Json