How to Find Duplicate Files with PowerShell and Export a Report

PowerShell can narrow a duplicate-file audit by grouping files with the same size and then calculating SHA-256 hashes only for those candidates. The script below exports matching-content groups and a separate scan-error report. It never deletes or moves a file.

Start with a narrow folder: do not scan an entire system drive first. Exclude backup folders when duplicate copies there are intentional, and keep another backup before any later cleanup.

Set the scan folder and report paths

$RootPath = "$env:USERPROFILE\Documents"
$ReportPath = "$env:USERPROFILE\Desktop\Duplicate_Audit_Report.csv"
$ErrorPath = "$env:USERPROFILE\Desktop\Duplicate_Scan_Errors.csv"

Use size grouping before hashing

$ScanErrors = @()

$allFiles = Get-ChildItem -LiteralPath $RootPath -File -Recurse -Force `
    -ErrorAction SilentlyContinue -ErrorVariable +ScanErrors

$sizeCandidates = $allFiles |
    Group-Object -Property Length |
    Where-Object { $_.Count -gt 1 } |
    ForEach-Object { $_.Group }

$hashedFiles = foreach ($file in $sizeCandidates) {
    try {
        $hash = Get-FileHash -LiteralPath $file.FullName `
            -Algorithm SHA256 -ErrorAction Stop

        [PSCustomObject]@{
            Hash          = $hash.Hash
            Path          = $file.FullName
            SizeBytes     = $file.Length
            LastWriteTime = $file.LastWriteTime
        }
    }
    catch {
        $ScanErrors += [PSCustomObject]@{
            Path  = $file.FullName
            Stage = "Hash"
            Error = $_.Exception.Message
        }
    }
}

$report = foreach ($group in (
    $hashedFiles |
    Group-Object -Property Hash |
    Where-Object { $_.Count -gt 1 }
)) {
    $ordered = $group.Group | Sort-Object -Property Path

    for ($index = 0; $index -lt $ordered.Count; $index++) {
        [PSCustomObject]@{
            GroupHash     = $group.Name
            GroupCount    = $ordered.Count
            CandidateRank = $index + 1
            Path          = $ordered[$index].Path
            SizeMB        = [math]::Round($ordered[$index].SizeBytes / 1MB, 2)
            LastWriteTime = $ordered[$index].LastWriteTime
            ReviewNote    = "Matching SHA-256 content; review purpose and path"
        }
    }
}

if ($report) {
    $report | Export-Csv -Path $ReportPath -NoTypeInformation -Encoding UTF8
    Write-Host "Duplicate candidate report: $ReportPath"
}
else {
    Write-Host "No matching SHA-256 groups were found in the files that were scanned."
}

if ($ScanErrors) {
    $ScanErrors | Export-Csv -Path $ErrorPath -NoTypeInformation -Encoding UTF8
    Write-Warning "Some paths could not be scanned. Review: $ErrorPath"
}

Understand what the report proves

A matching SHA-256 hash is a strong indication that the scanned file contents are identical. It does not show which path is the authoritative copy, whether both copies are required, or whether an application depends on a particular location. Scan errors also mean the audit was incomplete.

Run the script

  1. Save it as find-duplicates.ps1.
  2. Set $RootPath to a folder you own and understand.
  3. Run it in PowerShell.
  4. Open both CSV files if they were created.
  5. Do not proceed to cleanup until scan errors and intentional backup locations are reviewed.

Review each duplicate group

Why automatic deletion is excluded

Identical content can be intentional. A project may require a local copy, an archive may preserve a release, or software may reference a fixed path. The decision requires context the hash cannot provide.

Reduce incomplete or misleading scans

Completion checklist

Official Microsoft reference

Related Guides

About the author

Tweaknook Editorial publishes practical guides and browser-based tools for everyday digital work. Product-dependent facts are checked against current primary documentation, with limitations and safer verification steps stated where relevant.