John McClane
John McClane

Reputation: 13

PowerShell script to compare 2 folders and copy different items to third folder

I have 2 folders: SourceFolder and DestinationFolder with the same subfolders in them and with similar files in every subfolders. And I have a third folder ResultsFolder. I need to compare all files and:

  1. if there is a new file in the SourceFolder it needs to be copied to the ResultsFolder to the appropriate subfolder.
  2. if any of files content is changed it needs to be copied to the ResultsFolder to the appropriate subfolder it belongs to.

For example, both SourceFolder and DestinationFolder have 3 subfolders: sub1, sub2 and sub3.

SourceFolder\sub1 have file1sub1.txt, file2sub1.txt
SourceFolder\sub2 have file1sub2.txt
SourceFolder\sub3 have file1sub3.txt
DestinationFolder\sub1 have file1sub1.txt
DestinationFolder\sub2 have file1sub2.txt
DestinationFolder\sub3 have file1sub3.txt

Script needs to create subfolder sub1 in ResultsFolder and to copy file2sub1.txt to ResultsFolder\sub1. Also, if file1sub2.txt is changed (added or removed some character), script needs to create sub2 in ResultsFolder and to copy file1sub2.txt to ResultsFolder\sub2. And, if file1sub3.txt is not changed, it doesn't need to do anything.

I tried a lot of thing, and the latest I am playing with is this:

$SourceFolder = 'C:\Test\Source\'
$DestinationFolder = 'C:\Test\Destination\'
$ResultsFolder = 'C:\Test\Results\'

$RefFiles = Get-ChildItem $SourceFolder -Recurse | Select-Object -Property *, @{name = 'Hash'; expression = { (Get-FileHash $_.FullName -Algorithm MD5).hash } }
$DiffFiles = Get-ChildItem $DestinationFolder -Recurse | Select-Object -Property *, @{name = 'Hash'; expression = { (Get-FileHash $_.FullName -Algorithm MD5).hash } }

Compare-Object -Ref $RefFiles -Dif $DiffFiles -Property Name, Hash |
    ForEach-Object {
        if ($_.SideIndicator -eq '<=') {
            Copy-Item $_.Name -Destination $ResultsFolder -Recurse - Container
        }
    }

Upvotes: 1

Views: 120

Answers (1)

Santiago Squarzon
Santiago Squarzon

Reputation: 60848

Code is a bit extensive but the inline comments should help you understand the logic. The ideal route in this case is to use a hashtable as your reference for fast look-ups.

$SourceFolder = 'C:\Test\Source\'
$DestinationFolder = 'C:\Test\Destination\'
$ResultsFolder = 'C:\Test\Results\'

# The reference should actually be the Destination, instead of the source
$ref = @{}
# Get all files recursively
Get-ChildItem $DestinationFolder -Recurse -File | ForEach-Object {
    # construct a ref hash:
    #   - Key   = File Relative path
    #   - Value = The FileInfo Reference
    $ref[$_.FullName.Remove(0, $DestinationFolder.Length)] = $_
}

# Get all files in the source
Get-ChildItem $SourceFolder -Recurse -File | ForEach-Object {
    $relative = $_.FullName.Remove(0, $SourceFolder.Length)
    # if the ref map has this relative path and both files have the same `.Length`
    if($ref.ContainsKey($relative) -and $ref[$relative].Length -eq $_.Length) {
        # get the hash of the ref file
        $refHash = ($ref[$relative] | Get-FileHash -Algorithm MD5).Hash
        # if hash of the ref file is the same as this file hash
        if($refHash -eq ($_ | Get-FileHash -Algorithm MD5).Hash) {
            # then we can skip here, go next
            return
        }
    }

    # else, we can assume this file either does not exist or has a different hash
    # so, build the destination path first
    $destination = Join-Path $ResultsFolder ([System.IO.Path]::GetDirectoryName($relative))
    # if the destination directory does not exist
    if(-not [System.IO.Directory]::Exists($destination)) {
        # create it
        $null = [System.IO.Directory]::CreateDirectory($destination)
    }

    # now we can copy this file to the new destination folder
    $_ | Copy-Item -Destination $destination
}

Upvotes: 0

Related Questions