PowerShell ships a built-in diff capability — the Compare-Object cmdlet — that has been part of Windows since PowerShell 1.0 in 2006. If you know how to wire it up correctly, you can PowerShell diff two files in a single line, compare two files in PowerShell by content or hash, diff entire directory trees, validate CSV exports, and pipe results straight into CI/CD pipelines — all without leaving the terminal. This guide walks through every method — from the two-liner quick start to recursive folder diffing and binary integrity checks — so you can pick exactly the right tool for your workflow.

What Is PowerShell Diff? (vs. Unix diff and fc.exe)

Three diff approaches: Unix diff vs PowerShell Compare-Object vs Windows fc.exe Three Native Diff Approaches on Windows Unix diff Scope Line / patch-compatible Platform Linux / macOS / WSL Output style Unified / context text Pipeline Patch-compatible Compare-Object Scope Objects / lines / CSV Platform Win / Mac / Linux (PS7) Output style .NET objects / pipeline Pipeline Full PS pipeline support BEST FOR AUTOMATION fc.exe Scope Line / binary (limited) Platform Windows only Output style Plain text to stdout Pipeline CMD/batch only
Comparison of the three main Windows diff tools: Unix diff (patch-compatible text), PowerShell Compare-Object (object pipeline), and fc.exe (legacy text output).

When sysadmins and developers say "PowerShell diff," they almost always mean Compare-Object — or its built-in alias diff. Introduced in PowerShell 1.0 (2006), Compare-Object compares two collections of objects rather than raw byte streams. That distinction matters enormously for how you use it.

There are three native Windows tools for file comparison, each with a different sweet spot:

Tool Speed File types Recursion Output format Best for
Compare-Object Medium Text, CSV, JSON, objects Yes (manual) PowerShell objects / pipeline Scripted automation, structured data
fc.exe Fast Text, binary (limited) No Plain text to stdout Quick one-off text diffs, batch scripts
Get-FileHash Very fast Any (hash only) Yes (loop) Hash string Identity check, binary integrity
WinMerge Fast (GUI) Text, binary, archives Yes Visual side-by-side Manual review, merge conflicts (3-way from v2.16+)
Diff Checker Instant Text, code No Side-by-side browser view Sharing diffs, non-technical reviewers

Unix diff vs. PowerShell diff. On Linux and macOS, the diff command produces unified or context-format output that can be applied as a patch with patch. PowerShell's Compare-Object does not produce patch-compatible output — it outputs .NET objects with a SideIndicator property. That makes it more powerful for automation inside PowerShell pipelines, but less portable outside them. PowerShell 7+ runs cross-platform (Windows, macOS, Linux), so you can use Compare-Object anywhere if you prefer the object-oriented pipeline model.

fc.exe. The legacy Windows File Compare utility dates to the MS-DOS era and is still bundled with Windows 10 and 11. As a file compare PowerShell alternative it is a compiled binary, so it starts faster and uses less memory than PowerShell for a simple two-file text diff. But it cannot integrate into a PowerShell pipeline, has no structured-data awareness, and its binary mode is rudimentary. Use fc.exe when you just need a quick sanity check from a batch script or command prompt.

Quick Start: Compare Two Text Files in PowerShell

PowerShell terminal showing Compare-Object output with SideIndicator values Windows PowerShell PS C:\> Compare-Object (Get-Content a.txt) (Get-Content b.txt) InputObject SideIndicator ----------- ------------- Hello world => Goodbye world <= PS C:\> Compare-Object ... -IncludeEqual InputObject SideIndicator ----------- ------------- Shared line == => only in file2 (added) <= only in file1 (removed)
PowerShell terminal output from Compare-Object: green => marks lines present only in the right file, red <= marks lines only in the left file, and == marks shared lines (with -IncludeEqual).

The canonical way to PowerShell diff two files — or to file compare in PowerShell from the command line — is a single line:

Compare-Object (Get-Content .\file1.txt) (Get-Content .\file2.txt)

Get-Content reads each file and returns an array of strings, one per line. Compare-Object then performs a set-difference between those two arrays and outputs only the lines that differ. Here is what the output looks like:

InputObject          SideIndicator
-----------          -------------
Line only in file2   =>
Line only in file1   <=

To also see lines that are identical in both files, add -IncludeEqual:

Compare-Object (Get-Content .\file1.txt) (Get-Content .\file2.txt) -IncludeEqual

Output now includes three indicator values:

InputObject          SideIndicator
-----------          -------------
Shared line          ==
Line only in file2   =>
Line only in file1   <=

To get the raw changed lines back as strings (stripping the SideIndicator wrapper), add -PassThru:

Compare-Object (Get-Content .\file1.txt) (Get-Content .\file2.txt) -PassThru

Case sensitivity. By default, string comparisons in Compare-Object are case-insensitive. To treat Hello and hello as different lines, add -CaseSensitive:

Compare-Object (Get-Content .\file1.txt) (Get-Content .\file2.txt) -CaseSensitive

For large files, you may also want to control the -SyncWindow parameter, which sets how many lines ahead PowerShell looks when attempting to re-synchronize after a mismatch. The default is 5; raising it to 50 or 100 gives more accurate diffs on files with large inserted or deleted blocks (at the cost of more memory).

Understanding Compare-Object Output

Every row that Compare-Object emits is a PSCustomObject with two properties:

  • InputObject — the line or object that differs between the two collections.
  • SideIndicator — a string that tells you which side the item came from:
    • <= — present only in the Reference (left) object, i.e., the first argument.
    • => — present only in the Difference (right) object, i.e., the second argument.
    • == — present in both (only emitted when you use -IncludeEqual).

Because the output is a proper PowerShell object, you can filter it with Where-Object. For example, to show only lines that were removed from file1 (i.e., lines that appear in file1 but not file2):

Compare-Object (Get-Content .\file1.txt) (Get-Content .\file2.txt) |
    Where-Object { $_.SideIndicator -eq '<=' }

And to show only lines added in file2:

Compare-Object (Get-Content .\file1.txt) (Get-Content .\file2.txt) |
    Where-Object { $_.SideIndicator -eq '=>' }

You can also pipe these results into Export-Csv, Out-File, ConvertTo-Json, or any other PowerShell cmdlet — a capability that fc.exe and Unix diff simply cannot match without additional shell glue.

Comparing CSV and Structured Data

Raw Get-Content treats every line as a plain string. For structured files like CSV, that means a row reordering will look like every row changed — even if the data is identical. To PowerShell compare two files of CSV data correctly, the fix is Import-Csv, which parses column headers and returns typed objects:

# PowerShell compare two files — CSV edition
$old = Import-Csv .\users-old.csv
$new = Import-Csv .\users-new.csv

Compare-Object $old $new -Property Name, Email, Status

The -Property parameter tells Compare-Object which columns to consider when deciding whether two rows are "equal." Only rows where all specified properties match are treated as identical — so a row with the same Name but a different Email shows up as a change.

To find rows that were added to the new file (not in the old file):

Compare-Object $old $new -Property Name, Email, Status |
    Where-Object { $_.SideIndicator -eq '=>' } |
    Select-Object -ExpandProperty InputObject

This pattern is extremely useful for validating database exports, auditing user lists, or comparing configuration snapshots. If you are comparing JSON instead of CSV, first convert with Get-Content | ConvertFrom-Json to get typed objects, then pipe into Compare-Object with the same -Property approach.

Fast File Comparison with Get-FileHash

Sometimes you do not need a line-by-line diff at all. If the goal is to PowerShell compare two files for byte-level equality — knowing whether they are identical, not how they differ — Get-FileHash is the fastest approach and works on any file type:

# Instant identity check — works on any file size
$hash1 = (Get-FileHash .\file1.txt -Algorithm SHA256).Hash
$hash2 = (Get-FileHash .\file2.txt -Algorithm SHA256).Hash

if ($hash1 -eq $hash2) {
    Write-Host "Files are identical."
} else {
    Write-Host "Files differ."
}

Get-FileHash supports SHA1, SHA256, SHA384, SHA512, and MD5. SHA256 is the recommended choice — it is fast, collision-resistant, and widely used for integrity verification in CI/CD pipelines. MD5 is faster but cryptographically weak; avoid it for security-sensitive comparisons.

Comparing a folder of files by hash. You can pipe Get-ChildItem into Get-FileHash to build a hash table for an entire directory:

Get-ChildItem .\deploy\ -Recurse -File |
    Get-FileHash -Algorithm SHA256 |
    Select-Object Hash, Path

Save this output as a CSV before deployment, then re-run it after deployment and diff the two CSVs with Compare-Object and -Property Hash, Path to detect any files that changed or were added or removed during deployment.

For a deep dive into binary-level comparison beyond hashes, see the binary compare guide, which covers hex editors, cmp, and dedicated binary diff tools.

Binary File Comparison with Chunked Buffering

Compare-Object operates on .NET objects, not raw bytes. For true byte-level binary diffing in PowerShell, you need a FileStream-based approach that reads both files in chunks and compares the buffers:

function Compare-BinaryFiles {
    param(
        [string]$Path1,
        [string]$Path2,
        [int]$BufferSize = 65536
    )

    $stream1 = [System.IO.File]::OpenRead($Path1)
    $stream2 = [System.IO.File]::OpenRead($Path2)

    try {
        if ($stream1.Length -ne $stream2.Length) {
            return $false
        }

        $buf1 = New-Object byte[] $BufferSize
        $buf2 = New-Object byte[] $BufferSize

        do {
            $read1 = $stream1.Read($buf1, 0, $BufferSize)
            $read2 = $stream2.Read($buf2, 0, $BufferSize)

            if ($read1 -ne $read2) { return $false }

            for ($i = 0; $i -lt $read1; $i++) {
                if ($buf1[$i] -ne $buf2[$i]) { return $false }
            }
        } while ($read1 -gt 0)

        return $true
    } finally {
        $stream1.Close()
        $stream2.Close()
    }
}

# Usage
Compare-BinaryFiles -Path1 .\image-v1.png -Path2 .\image-v2.png

This function returns $true if the files are byte-for-byte identical and $false otherwise. The default buffer size of 64 KB is a good balance between memory use and read performance; for very large files (multi-GB), increase it to 1 MB (1048576).

For most binary integrity scenarios, Get-FileHash is simpler and sufficient. Reserve the chunked buffer approach for cases where you need to identify the exact byte offset where files diverge, or when you cannot afford the time to hash the entire file before making a decision.

Recursive Folder and Directory Comparison

Two folder trees compared side-by-side showing matching, missing, and new files Recursive Folder Comparison C:\Backup\v1 (Reference) C:\Backup\v2 (Difference) config.json deploy.ps1 old-readme.md settings.json utils.ps1 config.json deploy.ps1 (not present) settings.json new-changelog.md Present in both (==) Left only (<=) — deleted Right only (=>) — added
Recursive folder comparison: green-bordered files match in both trees, red-bordered exist only in v1 (deleted), blue-bordered exist only in v2 (added). PowerShell's Compare-Object surfaces all three categories.

To compare two folders in PowerShell, combine Get-ChildItem -Recurse with Compare-Object. The key is to compare relative paths (not absolute paths) so files at the same relative location match up correctly:

# Recursive folder diff — find files that exist in one folder but not the other
$folder1 = 'C:\Backup\v1'
$folder2 = 'C:\Backup\v2'

$files1 = Get-ChildItem $folder1 -Recurse -File |
    Select-Object -ExpandProperty FullName |
    ForEach-Object { $_.Replace($folder1, '') }

$files2 = Get-ChildItem $folder2 -Recurse -File |
    Select-Object -ExpandProperty FullName |
    ForEach-Object { $_.Replace($folder2, '') }

Compare-Object $files1 $files2

Lines with <= are files that exist only in v1 (deleted between versions). Lines with => are files added in v2. Files present in both folders (possibly with changed content) do not appear unless you add -IncludeEqual.

Detecting changed content, not just presence. To also catch files that exist in both folders but have different content, combine the folder diff with hash comparison:

$hashes1 = Get-ChildItem $folder1 -Recurse -File | ForEach-Object {
    [PSCustomObject]@{
        RelPath = $_.FullName.Replace($folder1, '')
        Hash    = (Get-FileHash $_.FullName -Algorithm SHA256).Hash
    }
}

$hashes2 = Get-ChildItem $folder2 -Recurse -File | ForEach-Object {
    [PSCustomObject]@{
        RelPath = $_.FullName.Replace($folder2, '')
        Hash    = (Get-FileHash $_.FullName -Algorithm SHA256).Hash
    }
}

Compare-Object $hashes1 $hashes2 -Property RelPath, Hash

Any file that moved, was renamed, or had its content changed will appear in the output. Files that are present and unchanged in both folders will not — keeping the output clean and actionable.

For GUI-based folder comparison on macOS, the macOS directory comparison guide covers FileMerge, Kaleidoscope, and terminal options. For Windows folder diffing without PowerShell, see how to tell which files are different in Windows folders.

Advanced Parameters and Performance Tuning

Compare-Object has several parameters that are rarely documented but matter enormously in production scripts:

Parameter Default Effect
-CaseSensitive Off Treats "Hello" and "hello" as different values
-SyncWindow 5 Lines ahead PS looks to re-sync after a mismatch; raise for large insertions
-IncludeEqual Off Emits == rows for lines present in both collections
-ExcludeDifferent Off Only emit == rows (useful for finding common lines)
-Property (whole object) Compare specific object properties rather than the whole object
-PassThru Off Returns the original object instead of a wrapped PSCustomObject

Memory management for large files. Get-Content loads the entire file into memory as a string array. For files larger than ~100 MB, this can be slow or cause out-of-memory errors. Consider using Get-Content -ReadCount 1000 to stream lines in chunks, or switch to StreamReader for line-by-line processing without loading the whole file:

$reader = [System.IO.StreamReader]::new('.\huge-file.txt')
$lines  = [System.Collections.Generic.List[string]]::new()
while (-not $reader.EndOfStream) {
    $lines.Add($reader.ReadLine())
}
$reader.Close()

For files over 500 MB, skip Compare-Object entirely and use Get-FileHash for an identity check, or reach for a dedicated binary diff tool.

When CLI Isn't Enough: Visual Diff Tools

PowerShell terminal output vs visual browser diff side-by-side CLI Output vs. Visual Diff Tool PowerShell InputObject SideIndicator ----------- ------------- Line A added => Line B removed <= Line C changed => Line C old <= ... 47 more differences ... Hard to read at scale No context around changes Cannot share without terminal Symbols only — no inline highlight Diff Checker (Browser) file1.txt file2.txt Unchanged line Unchanged line - Line B removed + Line A added - Line C old + Line C changed Another shared line Another shared line Side-by-side context Inline character highlighting Shareable link — no terminal Works for non-technical reviewers VS
PowerShell's flat text output (left) vs. a visual browser-based diff tool (right): visual tools add inline character highlighting, side-by-side context, and shareable links that non-technical reviewers can use without opening a terminal.

PowerShell's Compare-Object is excellent for automation — but its output is a flat list of changed lines. When you need to understand a complex diff — see context around changes, spot a subtle whitespace issue, or share results with someone who has never opened a terminal — a visual diff tool is the right companion.

The typical workflow looks like this:

  1. Run Compare-Object in PowerShell to confirm files differ and get a count of changes.
  2. Export the differing content to a temp file or clipboard.
  3. Open both originals in a visual diff tool for side-by-side review.
  4. If changes are approved, use PowerShell to automate the next step (copy, deploy, archive).

For code files in an editor workflow, VS Code's built-in diff viewer is a natural choice — the VS Code file comparison guide walks through every method. For Notepad++ users, the Notepad++ Compare plugin guide covers the ComparePlus plugin.

For browser-based review — particularly when sharing diffs with non-technical reviewers or working in environments where desktop app installs are restricted — the Diff Checker Chrome extension provides instant side-by-side diffing in your browser. It works without installing PowerShell modules or any additional software, processes files locally (no upload), and is well-suited for sharing configuration changes, config file diffs, or document comparisons with teammates who do not use PowerShell.

For Linux-based comparison workflows with the classic diff utility, the Linux compare files guide covers diff, cmp, comm, vimdiff, and Meld.

Common Errors and Troubleshooting

Three common PowerShell diff troubleshooting issues: encoding mismatch, line endings, and memory limits Common Troubleshooting Scenarios Encoding Mismatch UTF-8 vs ANSI / UTF-16 Symptom Every line shows as different even when content looks the same Cause Files encoded in different character sets Fix -Encoding UTF8 on both Get-Content calls Line Endings CRLF (\r\n) vs LF (\n) Symptom All lines differ even when comparing Win and Unix text files Cause Trailing \r left on Windows line endings Fix -replace '\r', '' before passing to Compare-Object Large File Memory 100 MB+ files Symptom Out of memory error or very slow diff on huge log files Cause Get-Content loads entire file into RAM Fix Get-FileHash or StreamReader for line-by-line streaming
Three most common PowerShell diff problems: encoding mismatches (force -Encoding UTF8), CRLF/LF line-ending differences (strip \r before comparing), and out-of-memory errors on large files (use Get-FileHash or StreamReader).

Most file compare PowerShell frustrations come from three sources: encoding mismatches, line-ending differences, and memory limits. Here is how to diagnose and fix each one.

Encoding mismatches (UTF-8 vs. UTF-16)

Get-Content auto-detects encoding, but when one file is UTF-8 and the other is UTF-16 (common for files generated by different Windows tools), every line appears different even if the text content is identical. Fix: force a consistent encoding on both reads:

$ref  = Get-Content .\file1.txt -Encoding UTF8
$diff = Get-Content .\file2.txt -Encoding UTF8
Compare-Object $ref $diff

On PowerShell 5.x, the -Encoding values are named constants (UTF8, Unicode, ASCII). On PowerShell 7+, you can also pass IANA encoding names like 'utf-8'.

CRLF vs. LF line endings

Windows files typically use CRLF (\r\n) line endings; Linux and macOS files use LF (\n). When you compare a Windows-generated file with a Unix-generated file, Get-Content may leave trailing carriage returns (\r) on each line, causing every line to appear different. To strip them before comparison:

$ref  = (Get-Content .\file1.txt) -replace '\r', ''
$diff = (Get-Content .\file2.txt) -replace '\r', ''
Compare-Object $ref $diff

Out-of-memory on large files

Get-Content loads entire files into memory. For files over ~200 MB, you may hit memory pressure or a script timeout. Options:

  • Use Get-FileHash instead if you only need an identity check.
  • Use StreamReader to process line-by-line without full load.
  • Split the file into chunks with -ReadCount and compare chunks.
  • Use fc.exe for a fast first-pass on raw text.

Compare-Object returns nothing (no output)

If Compare-Object produces no output, the files are identical (from PowerShell's perspective). Verify this is expected by checking:

  • Are both files truly the same path? (Check for typos.)
  • Are encoding differences masking real content differences? (See above.)
  • Is one file empty? (Get-Content .\file.txt).Count should be greater than zero.

Automation: Scheduled Diff Reports for CI/CD

One of the strongest use cases for PowerShell diff is building automated change-detection pipelines. Here is a practical pattern: compare a configuration snapshot from yesterday against today's live config, and email the diff report if anything changed.

# config-diff-report.ps1
param(
    [string]$BaselinePath = '.\baseline\config-snapshot.csv',
    [string]$LivePath     = '.\live\config-current.csv',
    [string]$ReportPath   = '.\reports\diff-report.txt'
)

$baseline = Import-Csv $BaselinePath
$live     = Import-Csv $LivePath

$changes = Compare-Object $baseline $live -Property Key, Value

if ($changes.Count -gt 0) {
    $report = $changes | Format-Table -AutoSize | Out-String
    $report | Out-File $ReportPath -Encoding UTF8

    Send-MailMessage `
        -To   'ops-team@example.com' `
        -From 'ci@example.com' `
        -Subject "Config drift detected: $($changes.Count) changes" `
        -Body  $report `
        -SmtpServer 'smtp.example.com'

    Write-Host "Diff report sent. $($changes.Count) change(s) detected."
    exit 1   # non-zero exit signals CI failure
} else {
    Write-Host "No changes detected."
    exit 0
}

In a GitHub Actions or Azure Pipelines workflow, you would run this script as a PowerShell step. If it exits with code 1, the pipeline fails and the team is notified. If it exits with 0, the pipeline continues. This pattern is useful for:

  • Configuration drift detection between environments (dev → staging → prod).
  • Verifying that deployment scripts produced the expected file changes.
  • Auditing user or permission lists for unauthorized additions.
  • Comparing API response snapshots to catch regressions in a data pipeline.

For human review of the flagged diffs, the Diff Checker Chrome extension provides an easy way to paste the two file versions and share a visual side-by-side comparison with teammates — no terminal required on the reviewer's side.

Decision Tree: When to Use What

Decision tree flowchart for choosing the right PowerShell file comparison method Decision Tree: Which Tool to Use? Need to compare files? Binary or text file? Binary Identity check only? Yes Get-FileHash SHA256 hash check No Chunked FileStream Byte-level diff loop Text Scripted automation? Yes Compare-Object Pipeline, CSV, filter No Visual review? Yes Diff Checker / VS Code Side-by-side visual, shareable No (quick check) fc.exe Fast, CMD/batch
Decision flowchart: start with "binary or text?", then branch by use case — identity check (Get-FileHash), byte-level diff (FileStream loop), automated scripting (Compare-Object), visual review (Diff Checker / VS Code), or quick one-off (fc.exe).

Not every file comparison problem has the same shape. Use this decision tree to pick the right tool the first time:

Scenario Best Tool Why
Are two files byte-for-byte identical? Get-FileHash Fastest, works on any file type, no memory concerns
What lines differ between two text files? Compare-Object + Get-Content Object pipeline, filterable, scriptable output
Compare structured rows in two CSVs Compare-Object + Import-Csv + -Property Column-aware; handles row reordering gracefully
Find files added or removed across two folders Get-ChildItem -Recurse + Compare-Object Recursive, scriptable, hash-combinable
Quick one-off text file diff from CMD/batch fc.exe Fastest startup, no PowerShell required
Byte-level binary diff (find exact offset) Chunked FileStream loop Full byte control; Get-FileHash only gives yes/no
Visual review / share diff with teammate VS Code or Diff Checker extension Side-by-side view, inline highlighting, no CLI required
CI/CD automated diff with pass/fail signal Compare-Object script + exit code Pipeline integration, custom exit codes, email/Slack hooks

The general principle: use Get-FileHash when you only need a yes/no answer, use Compare-Object when you need structured, filterable output in a PowerShell pipeline, and use a visual tool when a human needs to review or approve the changes.

Frequently Asked Questions

How do I compare two files in PowerShell?

The standard approach is Compare-Object (Get-Content .\file1.txt) (Get-Content .\file2.txt). Lines only in the first file appear with <=; lines only in the second appear with =>. Add -IncludeEqual to also see lines shared by both files (marked ==). For a faster binary-identical check, use (Get-FileHash file1.txt).Hash -eq (Get-FileHash file2.txt).Hash.

What is the PowerShell equivalent of Unix diff?

Compare-Object — aliased as diff — is the PowerShell equivalent of the Unix diff command. The key difference is that Compare-Object outputs .NET objects rather than patch-compatible text, which makes it more powerful inside PowerShell pipelines but less portable to external tools. PowerShell 7+ runs on Linux and macOS, so you can use it alongside the native diff utility depending on your needs. For a broader look at Linux file comparison methods, see the Linux compare files guide.

Can PowerShell compare binary files?

Not directly with Compare-Object — it compares .NET objects, not raw bytes. For an identity check on binary files, Get-FileHash is the right tool: if both hashes match, the files are identical. For a true byte-level comparison (finding exactly which bytes differ), use a FileStream-based chunked buffer loop as shown in the binary comparison section above. For more on binary diff techniques and tools, the binary compare guide covers hex editors, cmp, and dedicated binary diff utilities.

How do I compare CSV files in PowerShell with Import-Csv?

Load both files with Import-Csv, then run Compare-Object $old $new -Property Col1, Col2, Col3 where the -Property list specifies which columns define row identity. This compares structured rows by column value rather than raw text lines — so reordered rows and reformatted values are handled correctly. Add | Where-Object { $_.SideIndicator -eq '=>' } to isolate only added rows, or '<=' for deleted rows.

Is fc.exe faster than Compare-Object for large files?

Yes, for a simple line-by-line text diff. fc.exe is a compiled C binary with minimal startup overhead, while Compare-Object loads the entire file into memory as a .NET object array before diffing begins. For a 50 MB log file, fc.exe will typically finish faster. However, fc.exe cannot be piped, filtered, or combined with structured data — making Compare-Object the better choice for any automation scenario. For the fastest possible identity check on large files, use Get-FileHash, which only needs to read the file once to produce a hash.