Replicate / Sync files from source to destination with powershell only
Frankly, robocopy.exe is such a good tool for this job and has been a part of the base Windows install since Vista/2008 that I would just use that instead of reinventing the wheel (especially when you're replacing a top fuel dragster wrinkle slick with an iron-shod wooden wagon wheel).  Yes, robocopy only uses datetime (LastWriteTime) and file size (Length) to detect changes, but if that doesn't work you're doing something rather strange.  That and the fact that Compare-Object is an awful command to work with because extremely unclear exactly what it's doing.

However, from a quick look:

$SourceRootFiles = Get-ChildItem $SourceRoot -Recurse
$DestinationRootFiles = Get-ChildItem $DestinationRoot -Recurse


These will both ignore Hidden and System files without the -Force parameter.

$CompareObjects = Compare-Object -ReferenceObject $SourceRootFiles -DifferenceObject $DestinationRootFiles -ErrorAction SilentlyContinue -IncludeEqual 


As far as I'm aware the above only compares file name.  At the very least, you'll want to specify the parameters you're comparing with Compare-Object.  It does not compare all properties like you might expect.  You've got to specify them:

$CompareObjects = Compare-Object -ReferenceObject $SourceRootFiles -DifferenceObject $DestinationRootFiles -ErrorAction SilentlyContinue -IncludeEqual -Property Name, LastWriteTime, Length


However, even then, Compare-Object isn't going to work here when files move within the directory structure.

Example:

# Create two directory trees
$SourceDir = mkdir DirectoryA
$DestDir = mkdir DirectoryB
$DestDirSub = mkdir "$DestDir\DirectoryC"

# Create some files in the SourceDir
Push-Location $SourceDir
0..9 | ForEach-Object {
    Set-Content -Path $_ -Value $null
}
Pop-Location

# Copy the files
Get-ChildItem -Path $SourceDir -File | Copy-Item -Destination $DestDir

# Move some files to a subdirectory
Get-ChildItem -Path $DestDir -File | Select-Object -Last 5 |  Move-Item -Destination $DestDirSub


OK, now lets get the list of files.  0-9 should be in DirectoryA, 0-4 in DirectoryB, and 5-9 in DirectoryB\DirectoryC.  Now do the comparison:

# Fetch file lists
$Source = Get-ChildItem -Path $SourceDir -Recurse
$Dest = Get-ChildItem -Path $DestDir -Recurse

# Do comparison
Compare-Object -ReferenceObject $Source -DifferenceObject $Dest -IncludeEqual -Property Name, LastWriteTime, Length


I get:

InputObject SideIndicator
----------- -------------
0.txt       ==
1.txt       ==
2.txt       ==
3.txt       ==
4.txt       ==
5.txt       ==
6.txt       ==
7.txt       ==
8.txt       ==
9.txt       ==
DirectoryC  =&gt;


And that's why I hate Compare-Object.

You can't just specify -Property FullName, LastWriteTime, Length or Compare-Object [...] -Property * either, since it's got the root part of the path in the properties.  You'll need to do something like this:

# Normalize the source and destination paths
$SourcePath = $SourceDir.FullName.TrimEnd('\');
$DestPath = $DestDir.FullName.TrimEnd('\');

# Fetch file lists
$Source = Get-ChildItem -Path $SourcePath -Recurse | Select-Object -Property *, @{n = 'RelativeName';e ={$_.FullName.Substring($SourcePath.Length)}}
$Dest = Get-ChildItem -Path $DestPath -Recurse | Select-Object -Property *, @{n = 'RelativeName';e ={$_.FullName.Substring($DestPath.Length)}}

# Do comparison
Compare-Object -ReferenceObject $Source -DifferenceObject $Dest -IncludeEqual -Property Name, LastWriteTime, Length, RelativeName


Alternately, you could do something like this using Tuples as the key in a Dictionary:

$SourceTable = @{}
$Source = Get-ChildItem -Path $SourceDir -Recurse | ForEach-Object { 
    $t = [System.Tuple]::Create($_.FullName.Substring($SourceDir.FullName.TrimEnd('\').Length), $_.LastWriteTime, $_.Length)
    $SourceTable.Add($t, $_.FullName)
}

$DestTable = @{}
$Dest = Get-ChildItem -Path $DestDir -Recurse | ForEach-Object { 
    $t = [System.Tuple]::Create($_.FullName.Substring($DestDir.FullName.TrimEnd('\').Length), $_.LastWriteTime, $_.Length)
    $DestTable.Add($t, $_.FullName)
}

# Files in source and dest and could be filehashed
$SourceTable.GetEnumerator() | Where-Object { $DestTable.ContainsKey($_.Key) }

# Files in source and not in dest
$SourceTable.GetEnumerator() | Where-Object { !$DestTable.ContainsKey($_.Key) }

# Files in dest and not in source
$DestTable.GetEnumerator() | Where-Object { !$SourceTable.ContainsKey($_.Key) }


That will be really fast once it's loaded all the tables, but it's really pretty gross to set up.