PowerShell Read and Write Files: Get-Content and Set-Content

Config files, log files, CSV data, text templates — almost every real PowerShell script eventually needs to read a file or write one. Get-Content reads file text into PowerShell as an array of lines (or a single string with -Raw), and Set-Content writes content back. This guide covers reading, writing, appending, encoding, and processing large files efficiently — everything you need for text file automation.
Reading a File with Get-Content
By default, Get-Content returns each line of the file as a separate string in an array:
# Read all lines — returns an array of strings
$lines = Get-Content 'C:\Logs\app.log'
# Line count
$lines.Count
# Access specific lines by index
$lines[0] # First line
$lines[-1] # Last line
# Process each line in the pipeline
Get-Content 'C:\Logs\app.log' | Where-Object { $_ -like '*ERROR*' }
247
2026-05-04 08:00:01 INFO Application started
2026-05-04 09:14:55 INFO Shutdown complete
2026-05-04 08:15:33 ERROR Database connection failed
Because the output is an array of strings, you can immediately filter with Where-Object, sort, count, or process each line in a loop. Each element of the array is one line, without the newline character.
Reading as a Single String with -Raw
The -Raw flag reads the entire file as a single string, preserving all newlines:
# Without -Raw: array of lines
$lines = Get-Content 'C:\Config\appsettings.json'
$lines.GetType().Name # Object[]
# With -Raw: single string
$content = Get-Content 'C:\Config\appsettings.json' -Raw
$content.GetType().Name # String
# Use -Raw for JSON, regex, or any operation needing the whole file
$json = Get-Content 'C:\Config\appsettings.json' -Raw | ConvertFrom-Json
$json.Server
Object[]
String
localhost
Use -Raw when you need to process the file as a whole: parsing JSON, running a multi-line regex, or doing a whole-file text replacement. Without -Raw, a regex spanning two lines won’t match because each line is a separate string.
Writing a File with Set-Content
Set-Content writes content to a file, overwriting it completely if it exists:
# Write a single line
Set-Content -Path 'C:\Logs\status.txt' -Value 'All systems operational'
# Write multiple lines from an array
$report = @(
"Report generated: $(Get-Date -Format 'yyyy-MM-dd HH:mm')",
"Server: $env:COMPUTERNAME",
"Status: OK"
)
Set-Content -Path 'C:\Reports\daily.txt' -Value $report
# Write pipeline output to file
Get-Service | Where-Object { $_.Status -eq 'Stopped' } |
ForEach-Object { $_.Name } |
Set-Content -Path 'C:\Reports\stopped-services.txt'
# daily.txt contents:
# Report generated: 2026-05-04 09:15
# Server: WORKSTATION01
# Status: OK
Set-Content overwrites by default. Use Add-Content to append. If the file doesn’t exist, Set-Content creates it. If the parent directory doesn’t exist, it throws an error — create the directory first with New-Item.
Appending Lines with Add-Content
Add-Content adds content without erasing what’s already there:
# Append a log entry
$timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
Add-Content -Path 'C:\Logs\script.log' -Value "[$timestamp] Script completed successfully"
# Append a line if the file doesn't exist yet, create it
Add-Content -Path 'C:\Logs\events.log' -Value "Service started"
# Append all error lines from another file
Get-Content 'C:\Logs\app.log' |
Where-Object { $_ -like '*ERROR*' } |
Add-Content -Path 'C:\Logs\errors-only.log'
Specifying File Encoding
Always specify encoding when interoperability matters:
# Read with specific encoding
Get-Content 'C:\Data\export.csv' -Encoding UTF8
# Write with UTF8 (no BOM) — best for cross-platform
Set-Content -Path 'C:\Config\settings.json' -Value $json -Encoding UTF8
# Available encodings in PS5.1: ASCII, BigEndianUnicode, Default, OEM, Unicode, UTF7, UTF8, UTF32
# PS7+ adds UTF8NoBOM and others
# Recommended: use UTF8 for most text files
# Check current file encoding (basic heuristic)
$bytes = [System.IO.File]::ReadAllBytes('C:\Data\file.txt') | Select-Object -First 4
"First bytes: $($bytes -join ' ')" # EF BB BF = UTF8 BOM
Processing Large Files Line by Line
For very large files (hundreds of MB), loading all lines into memory at once is inefficient. Stream through the pipeline instead:
# Stream a large log file without loading it all into memory
Get-Content 'C:\Logs\bigfile.log' | ForEach-Object {
if ($_ -match 'CRITICAL') {
Add-Content 'C:\Logs\critical.log' -Value $_
}
}
# Even more efficient: use a StreamReader for very large files
$reader = [System.IO.StreamReader]::new('C:\Logs\bigfile.log')
while (-not $reader.EndOfStream) {
$line = $reader.ReadLine()
if ($line -match 'ERROR') {
# process line
}
}
$reader.Close()
For files under ~100MB, the pipeline approach is fine. For multi-GB log files, the StreamReader approach uses far less memory.
Common Errors and Fixes
-
Get-Content returns array not string — use -Raw for single string: Operations like
-replaceon the result ofGet-Contentwithout-Rawrun on each line individually, not on the whole file. This means multi-line patterns won’t match. Add-Rawto get the full file as one string. -
Set-Content overwrites by default — use Add-Content to append: Calling
Set-Contenttwice on the same file loses the first write. UseAdd-Contentwhen you need to accumulate entries, or explicitly concatenate: read with-Raw, append, write back withSet-Content.
Related Cmdlets / See Also
Wrapping Up
Get-Content returns an array of lines by default — use -Raw when you need the whole file as one string. Set-Content writes and overwrites; use Add-Content to append. Always specify -Encoding UTF8 for files shared across platforms. For large files, stream through the pipeline rather than loading everything into memory. Your next step: write a log monitoring script that streams a large log file and extracts only ERROR lines.


