DEV Community

Cover image for PowerShell Out-File vs Set-Content: Encoding Is the Real Difference
arnostorg
arnostorg

Posted on

PowerShell Out-File vs Set-Content: Encoding Is the Real Difference

Set-Content and Out-File both put text into a file. On a quiet English UTF-8 machine they look interchangeable. On a Czech log, a CSV opened in Excel, or a schedule that later runs under powershell.exe -File, they are not. The bug is almost never the path. It is the encoding you never asked for.

The shortest honest comparison

Out-File Set-Content
Default job Capture pipeline output (what would print on screen) Write string content into a file
Default encoding (Windows PowerShell 5.1) Unicode (UTF-16 LE) Default (often ANSI / system code page)
Default encoding (PowerShell 7+) utf8NoBOM utf8NoBOM
Overwrite vs append -Append supported Use Add-Content to append
Great at Redirecting formatted cmdlet output Writing known strings you control

If you memorize one row, memorize the 5.1 defaults. That is where most "my script worked on my laptop" tickets start.

Recipe 1: capture a command's screen output

Get-ChildItem C:\Logs -File |
    Sort-Object Length -Descending |
    Select-Object -First 20 |
    Out-File C:\Reports\top-logs.txt
Enter fullscreen mode Exit fullscreen mode

Out-File is the file version of what you see. Objects still go through formatting first, so you get a readable table (or list), not a serialized object dump. That is exactly what you want for a human-readable report, and exactly what you do not want if the next step is another script parsing fields.

Need the same idea as > / >>?

Get-Service | Out-File C:\Reports\services.txt
Get-Process | Out-File C:\Reports\services.txt -Append
Enter fullscreen mode Exit fullscreen mode

Recipe 2: write the text you mean

Set-Content -Path C:\Apps\config.env -Value @(
    'APP_ENV=prod'
    'LOG_LEVEL=info'
) -Encoding utf8
Enter fullscreen mode Exit fullscreen mode

Set-Content replaces the whole file with the values you pass. No formatting layer. No surprise table headers. Pass an array and you get one line per element (plus a trailing newline, which is usually what you want).

Always name -Encoding when the file will leave your machine. Prefer:

Set-Content .\note.txt 'Příliš žluťoučký kůň' -Encoding utf8
Enter fullscreen mode Exit fullscreen mode

On PowerShell 7, utf8 is fine. On Windows PowerShell 5.1, utf8 still writes a BOM. If a downstream tool hates BOMs, write bytes yourself or move the write to pwsh 7 with utf8NoBOM.

Recipe 3: the encoding trap in one demo

Run this on Windows PowerShell 5.1 and open both files in a hex editor or Notepad++:

'café' | Out-File .\via-outfile.txt
Set-Content .\via-setcontent.txt 'café'
Enter fullscreen mode Exit fullscreen mode

Typical result on 5.1:

  • via-outfile.txt → UTF-16 LE (often starts with FF FE)
  • via-setcontent.txt → system ANSI (on Czech Windows, usually Windows-1250)

Neither is "wrong." Both are wrong if you assumed UTF-8. A later Get-Content without -Encoding, a Python script that opens as UTF-8, or Git treating the file as binary will all look like random corruption.

Force the contract:

'café' | Out-File .\via-outfile.txt -Encoding utf8
Set-Content .\via-setcontent.txt 'café' -Encoding utf8
Enter fullscreen mode Exit fullscreen mode

Same string, same encoding, same future.

Recipe 4: when Out-File is the wrong tool

This pattern looks neat and silently loses structure:

# Fragile: formatting depends on console width and host
Get-ChildItem | Out-File .\files.txt
Get-Content .\files.txt | ForEach-Object { $_.Substring(0, 10) }  # will break
Enter fullscreen mode Exit fullscreen mode

If another command must read the file, write data, not a screenshot of data:

Get-ChildItem -File |
    Select-Object Name, Length, LastWriteTime |
    Export-Csv .\files.csv -NoTypeInformation -Encoding utf8
Enter fullscreen mode Exit fullscreen mode

Or, for simple line lists:

(Get-ChildItem -File).Name | Set-Content .\names.txt -Encoding utf8
Enter fullscreen mode Exit fullscreen mode

Rule of thumb: Out-File for eyes, Set-Content / Export-Csv / ConvertTo-Json for machines.

Three traps worth knowing

  1. Host width changes Out-File layout. Wide tables wrap differently when the window is narrow. A report that looked fine in an expanded console can become unreadable when a scheduled task runs with a default width. Fix with Out-File -Width 200 (or higher) when you care about columns.

  2. > is Out-File with the same defaults. In Windows PowerShell 5.1, > creates UTF-16 files. Redirecting a UTF-8-looking string does not make a UTF-8 file. Prefer the cmdlet form with -Encoding whenever the file is shared.

  3. Set-Content overwrites; it does not append. To add lines, use Add-Content (and still pass -Encoding so the first write and later appends agree). Mixing Out-File -Encoding utf8 with a later bare Add-Content is a classic way to glue two encodings into one "corrupted" log.

Pick one default and stick to it

For new scripts on mixed 5.1 / 7 fleets:

$utf8 = if ($PSVersionTable.PSVersion.Major -ge 7) { 'utf8NoBOM' } else { 'utf8' }
Set-Content .\app.log "started $(Get-Date -Format o)" -Encoding $utf8
Enter fullscreen mode Exit fullscreen mode

Or standardize on PowerShell 7 for anything that writes text other tools will read. The command names stay the same. The encoding defaults finally stop fighting you.

Practice both without nuking a real config

Encoding bugs are ugly to debug on production shares. In CMD Master's interactive PowerShell lessons you can try Out-File, Set-Content, and -Encoding in a live browser terminal with instant feedback — no install, no VM. The same free Windows command line practice site also covers CMD and Bash when you hop between shells.

Your turn: have you been bitten by a UTF-16 Out-File landing in a UTF-8 pipeline? Drop the weirdest encoding mix-up you've hit in the comments.

Top comments (0)