Skip to content

Instantly share code, notes, and snippets.

@pinecones-sx
Created December 2, 2016 21:59
Show Gist options
  • Save pinecones-sx/10946c478b8aeb9b090bf35e4e606a80 to your computer and use it in GitHub Desktop.
Save pinecones-sx/10946c478b8aeb9b090bf35e4e606a80 to your computer and use it in GitHub Desktop.
<#
Verify contents between two tree structures and copy newer or missing files from the source to the destination
#>
 
function Global:Copy-Diff{
param($SourcePath,$DestinationPath,[switch]$Informational,[switch]$Force)
# Variable setup...
# Verify paths & normalize path names
$sourceExists = Test-Path $SourcePath
$destinationExists = Test-Path $DestinationPath
If((-not $sourceExists) -or (-not $destinationExists)){Return}
Else{
$SourcePath = Get-Item $SourcePath | Select -ExpandProperty FullName
$DestinationPath = Get-Item $DestinationPath | Select -ExpandProperty FullName
}
# Get tree of source path. Add properties for source path and relative path to each item.
$sourceTree = Get-ChildItem $SourcePath -Recurse -Force |
Add-Member -MemberType ScriptProperty -Name 'RootLocation' -Force -PassThru `
-Value {$SourcePath} |
Add-Member -MemberType ScriptProperty -Name 'RelativePath' -Force -PassThru `
-Value {$this.FullName -replace ('.*' + [regex]::escape($SourcePath + '\'))} |
Add-Member -MemberType ScriptProperty -Name 'Info' -Force -PassThru `
-Value {'Source object.'}
# Get tree of destination path. Add properties for source path and relative path to each item.
$destinationTree = Get-ChildItem $DestinationPath -Recurse -Force |
Add-Member -MemberType ScriptProperty -Name 'RootLocation' -Force -PassThru `
-Value {$DestinationPath} |
Add-Member -MemberType ScriptProperty -Name 'RelativePath' -Force -PassThru `
-Value {$this.FullName -replace ('.*' + [regex]::escape($DestinationPath + '\'))} |
Add-Member -MemberType ScriptProperty -Name 'Info' -Force -PassThru `
-Value {'Destination object.'}
# declare work arrays
$workInfoDestIsDifferent = @()
$workCompareEqualFiles = @()
$workCopyToDestination = @()
$DiffTree = Compare $SourceTree $DestinationTree -IncludeEqual
 
# Sort each diff object out into appropriate work arrays.
ForEach ($diff in $DiffTree){
Switch ($diff.SideIndicator){
'=>' { # Present in destination only.
$workInfoDestIsDifferent += $diff.InputObject |
Add-Member -MemberType ScriptProperty -Name 'Info' -Force -PassThru `
-Value {'Present only at destination.'}
}
'<=' { # Present in source only.
$workCopyToDestination += $diff.InputObject |
Add-Member -MemberType ScriptProperty -Name 'Info' -Force -PassThru `
-Value {'Present only at source.'}
}
'==' { # Present in both source & destination.
$workCompareEqualFiles += $diff.InputObject |
Add-Member -MemberType ScriptProperty -Name 'Info' -Force -PassThru `
-Value {'Present at both locations.'}
}
default {Write-Error -Message ('Found crazy SideIndicator. File ' + $diff.InputObject.FullName)}
} # End Switch
} # End sorting foreach
 
# Compare hashes of '==' objects
ForEach ($sameObject in $workCompareEqualFiles) {
# ***".InputObject" should always be the source item when side indicator is '=='
If ($sameObject.PSIsContainer -eq $true){
$sameObject = $sameObject |
Add-Member -MemberType ScriptProperty -Name 'Info' -Force -PassThru `
-Value {'Folder present in both locations.'}
}
Else{
# Get source object. Add hash as property.
$objSource = Get-Item $sameObject.FullName |
Add-Member -MemberType ScriptProperty -Name 'FileHash' -Force -PassThru `
-Value {$this | Get-FileHash | Select -ExpandProperty Hash}
# Get destination object. Add hash as property.
$uncObjDest = ($DestinationPath + '\' + $sameObject.RelativePath)
$objDest = Get-Item $uncObjDest |
Add-Member -MemberType ScriptProperty -Name 'FileHash' -Force -PassThru `
-Value {$this | Get-FileHash | Select -ExpandProperty Hash}
# If hashes don't match...
If ($objSource.FileHash -ne $objDest.FileHash) {
# If destination object was modified most recently...
If ($objSource.LastWriteTime -lt $objDest.LastWriteTime){
$workInfoDestIsDifferent += Get-Item $objDest.FullName |
Add-Member -MemberType ScriptProperty -Name 'RootLocation' -Force -PassThru `
-Value {$DestinationPath} |
Add-Member -MemberType ScriptProperty -Name 'RelativePath' -Force -PassThru `
-Value {$this.FullName -replace ('.*' + [regex]::escape($DestinationPath + '\'))} |
Add-Member -MemberType ScriptProperty -Name 'Info' -Force -PassThru `
-Value {'Present only at destination.'}
}
Else{
$workCopyToDestination += $sameObject |
Add-Member -MemberType ScriptProperty -Name 'Info' -Force -PassThru `
-Value {'Newer copy at source.'}
}
} # End if hashed don't match
} # End Else if
} # End foreach hash compare
 
# Prompt for write if -Force isn't used
If ((-not $Force) -and (-not $Informational)){
Write-Host 'Comparisons finished. Begin copy/overwrite?'
$userAnswer = Read-Host '(y/n)'
Switch ($userAnswer){
'y'{Write-Host 'Starting copy process...'}
'n'{$Informational = $true}
deafult{
$Informational = $true
Write-Host 'Invalid selection. Returning information only. (accpets only "y" or "n")'
Pause
}
}
}
 
# Process copies or return information only
If ($Informational){
$returnArray = ($workCopyToDestination += $workInfoDestIsDifferent) |
Select Name,FullName,Info,PSIsContainer,Length,LastWriteTime,RootLocation,RelativePath
Return $returnArray
}
Else{
$returnArray = @()
ForEach ($workObject in $workCopyToDestination) {
$from = ($workObject.FullName)
$to = ($workObject.FullName -replace [regex]::Escape($workObject.RootLocation),$DestinationPath)
# DO WORK!
Copy-Item $from $to -Force > $null
$newInfoText = ($workObject.Info + ' Object Copied.')
$returnArray += $workObject |
Add-Member -MemberType ScriptProperty -Name 'Info' -Force -PassThru `
-Value {$newInfoText}
}
$returnArray = $returnArray |
Select Name,FullName,Info,PSIsContainer,Length,LastWriteTime
Return $returnArray
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment