param( [string] $srcPath = "C:\Program Files\Microsoft SQL Server\MSSQL16.SQLEXPRESS\MSSQL\Backup\", [string] $dstPath = "\\OITROC\C$\Program Files\Microsoft SQL Server\MSSQL16.SQLEXPRESS\MSSQL\Backup\", [string] $logFile = [IO.Path]::Combine($srcPath, "SyncLog.txt") ) $verbose = $false $feedback = 500 function GetTimeStamp { return "[{0:MM/dd/yy} {0:HH:mm:ss.fff}]" -f (Get-Date) } # For faster compare operations, can set $assumeEqualForWriteTime = $true function FilesAreEqual { param( [System.IO.FileInfo] $leftInfo, [System.IO.FileInfo] $rightlnfo, [int] $bufferSize = 524288, [bool] $assumeEqualForWriteTime = $true ) try { if ($leftInfo.Length -ne $rightlnfo.Length) { return $false } if ($assumeEqualForWriteTime -and $leftInfo.LastWriteTime -eq $rightlnfo.LastWriteTime) { return $true } $leftStream = $leftInfo.OpenRead() $rightStream = $rightlnfo.OpenRead() $leftBuffer = New-Object byte[] $bufferSize $rightBuffer = New-Object byte[] $bufferSize $equal = $true do { $bytesRead = $leftStream.Read($leftBuffer, 0, $bufferSize) $rightStream.Read($rightBuffer, 0, $bufferSize) | Out-Null $equal = [System.Linq.Enumerable]::SequenceEqual($leftBuffer, $rightBuffer) } while ($equal -and $bytesRead -eq $bufferSize) $leftStream.Close() $rightStream.Close() return $equal } catch { return $false } } try { if (![IO.Directory]::Exists($srcPath)) { throw "The source path does not exist! Terminating local project sync." } if (![IO.Directory]::Exists($dstPath)) { [IO.Directory]::CreateDirectory($dstPath) | Out-Null } Set-Location $dstPath Add-Type -AssemblyName System.Core $srcFiles = New-Object "Collections.Generic.HashSet[string]" $lastDir = "" $count = 0 "Scanning source files from `"$srcPath`"..." # Get list of all source files foreach ($srcInfo in Get-ChildItem -Path $srcPath -Recurse) { if (!$srcInfo.PSIsContainer) { $dirName = [IO.Path]::GetDirectoryName($srcInfo.FullName) if (![String]::IsNullOrWhiteSpace($dirName) -and !$dirName.Contains("\.git") -and !$dirName.Contains("\Temp")) { $srcFiles.Add($srcInfo.FullName) | Out-Null } # Scan is very fast, so we double feedback interval if ($verbose -and $count++ % ($feedback * 2) -eq 0) { " $($count.ToString('N0')) files scanned so far" if ($lastDir -ne $dirName) { " Scanning files from `"$dirName`"..." $lastDir = $dirName } } } } "Scan complete for $($srcFiles.Count.ToString('N0')) source files, comparing files at `"$dstPath`"..." $deleteCount = 0 $count = 0 # Scan all destination files foreach ($dstInfo in Get-ChildItem -Path $dstPath -Recurse) { if (!$dstInfo.PSIsContainer) { # Derive source file name from destination file name $srcFile = [IO.Path]::Combine($srcPath, $dstInfo.FullName.Substring($dstPath.Length)) # Remove any files that are not in source path - do this before copy to reserve disk space if (!$srcFiles.Contains($srcFile)) { [IO.File]::Delete($dstInfo.FullName) | Out-Null $deleteCount++ } else { # Do not copy any files that have not changed if (FilesAreEqual $(Get-Item $srcFile) $dstInfo) { $srcFiles.Remove($srcFile) | Out-Null } } if ($verbose -and $count++ % $feedback -eq 0) { " $($count.ToString('N0')) files compared so far" $dirName = [IO.Path]::GetDirectoryName($srcFile) if ($lastDir -ne $dirName) { " Comparing files from `"$dirName`"..." $lastDir = $dirName } } } } if ($deleteCount -gt 0) { "Removed $($deleteCount.ToString('N0')) files from `"$dstPath` that no longer exist in source..." } $copyCount = 0 if ($srcFiles.Count -gt 0) { "Detected $($srcFiles.Count.ToString('N0')) updated files, starting sync operation to `"$dstPath`..." # Copy files foreach ($srcFile in $srcFiles) { # Derive desintation file name from source file name $dstFile = [IO.Path]::Combine($dstPath, $srcFile.Substring($srcPath.Length)) $dirName = [IO.Path]::GetDirectoryName($dstFile) if (![IO.Directory]::Exists($dirName)) { [IO.Directory]::CreateDirectory($dirName) | Out-Null } [IO.File]::Copy($srcFile, $dstFile, $true) | Out-Null $copyCount++ if ($verbose -and $copyCount % $feedback -eq 0) { " $($copyCount.ToString('N0')) files copied so far" if ($lastDir -ne $dirName) { " Copying files from `"$dirName`"..." $lastDir = $dirName } } } } $logMessage = "Sync succeeded: removed $($deleteCount.ToString('N0')) files and copied $($copyCount.ToString('N0')) files at $dstPath" $logMessage Write-Output "$(GetTimeStamp): $logMessage" | Out-file $logFile -append } catch { $logMessage = "Sync failed: $_" $logMessage Write-Output "$(GetTimeStamp): $logMessage" | Out-file $logFile -append }