Comparing folders and content with PowerShell

Viewed 83564

I have two different folders with xml files. One folder (folder2) contains updated and new xml files compared to the other (folder1). I need to know which files in folder2 are new/updated compared to folder1 and copy them to a third folder (folder3). What's the best way to accomplish this in PowerShell?

7 Answers

Here's an approach which will find files which are missing or differ in content.

First, a quick-and-dirty one-liner (see caveat below).

dir -r | rvpa -Relative |%{ if (Test-Path $right\$_) { if (Test-Path -Type Leaf $_) { if ( diff (cat $_) (cat $right\$_ ) ) { $_ } } } else { $_ } }

Run the above in one of the directories, with $right set to (or replaced with) the path to the other directory. Things missing from $right, or which differ in content, will be reported. No output means no differences found. CAVEAT: Things existing in $right but missing from the left will not be found/reported.

This doesn't bother calculating hashes; it just compares the file contents directly. Hashing makes sense when you want to reference something in another context (later date, on another machine, etc.), but when we're comparing things directly, it adds nothing but overhead. (It's also theoretically possible for two files to have the same hash, although that's basically impossible to happen by accident. Deliberate attack, on the other hand...)

Here's a more proper script, which handles more corner cases and errors.

[CmdletBinding()]
Param(
    [Parameter(Mandatory=$true,Position=0)][string]$Left,
    [Parameter(Mandatory=$True,Position=1)][string]$Right
    )

# throw errors on undefined variables
Set-StrictMode -Version 1

# stop immediately on error
$ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop

# init counters
$Items = $MissingRight = $MissingLeft = $Contentdiff = 0

# make sure the given parameters are valid paths
$left  = Resolve-Path $left
$right = Resolve-Path $right

# make sure the given parameters are directories
if (-Not (Test-Path -Type Container $left))  { throw "not a container: $left"  }
if (-Not (Test-Path -Type Container $right)) { throw "not a container: $right" }

# Starting from $left as relative root, walk the tree and compare to $right.
Push-Location $left

try {
    Get-ChildItem -Recurse | Resolve-Path -Relative | ForEach-Object {
        $rel = $_
        
        $Items++
        
        # make sure counterpart exists on the other side
        if (-not (Test-Path $right\$rel)) {
            Write-Output "missing from right: $rel"
            $MissingRight++
            return
            }
    
        # compare contents for files (directories just have to exist)
        if (Test-Path -Type Leaf $rel) {
            if ( Compare-Object (Get-Content $left\$rel) (Get-Content $right\$rel) ) {
                Write-Output "content differs   : $rel"
                $ContentDiff++
                }
            }
        }
    }
finally {
    Pop-Location
    }

# Check items in $right for counterparts in $left.
# Something missing from $left of course won't be found when walking $left.
# Don't need to check content again here.

Push-Location $right

try {
    Get-ChildItem -Recurse | Resolve-Path -Relative | ForEach-Object {
        $rel = $_
        
        if (-not (Test-Path $left\$rel)) {
            Write-Output "missing from left : $rel"
            $MissingLeft++
            return
            }
        }
    }
finally {
    Pop-Location
    }

Write-Verbose "$Items items, $ContentDiff differed, $MissingLeft missing from left, $MissingRight from right"

Handy version using script parameter

Simple file-level comparasion

Call it like PS > .\DirDiff.ps1 -a .\Old\ -b .\New\

Param(
  [string]$a,
  [string]$b
)

$fsa = Get-ChildItem -Recurse -path $a
$fsb = Get-ChildItem -Recurse -path $b
Compare-Object -Referenceobject $fsa -DifferenceObject $fsb

Possible output:

InputObject                  SideIndicator
-----------                  -------------
appsettings.Development.json <=
appsettings.Testing.json     <=
Server.pdb                   =>
ServerClientLibrary.pdb      =>

Do this:

compare (Get-ChildItem D:\MyFolder\NewFolder) (Get-ChildItem \\RemoteServer\MyFolder\NewFolder)

And even recursively:

compare (Get-ChildItem -r D:\MyFolder\NewFolder) (Get-ChildItem -r \\RemoteServer\MyFolder\NewFolder)

and is even hard to forget :)

Related