Using Microsoft SyncToy through PowerShell

4745206997_ac577b3248_m
This post is about running Microsoft SyncToy via PowerShell. For those that don’t know SyncToy:

SyncToy 2.1 is a free application that synchronizes files and folders between locations. Typical uses include sharing files, such as photos, with other computers and creating backup copies of files and folders.

SyncToy has been around already since good old Windows XP times and even though there are alternative freeware applications it’s still one of my favorite tools for the job.
While SyncToy already comes with a commandline version out of the box, it’s lacking quite some features as compared to the graphical user interface:

  • No option to preview the sync operation
  • No progress indication
  • No option to exclude subfolders
  • No option to exclude files by attributes (e.g. hidden, system)
  • No option to specify recursion
  • No option to specify action for overwritten files

Googling around for solutions I came across two related posts on codeproject.com:

Following translating the suggest approach to PowerShell it seemed to be quite easy to accomplish what I wanted utilizing the SyncToyEngine.dll .NET assembly that comes with the SyncToy installation. Considering that I would have setup already a folder pairing called ‘Test’ using the GUI, the following code should initiate the sync operation (it’s important to use the correct version of PowerShell to test this (i.e. SyncToy(x64) needs to be run via PowerShell x64):

$syncToyEnginePath = Resolve-Path 'c:\Program Files*\SyncToy 2.1\SyncToyEngine.dll'
#load the dll
Add-Type -Path $syncToyEnginePath

#retrieve the sync engine configuration
$syncToyEngineConfigPath = "$env:LOCALAPPDATA\Microsoft\SyncToy\2.0\SyncToyDirPairs.bin"
$bf = New-Object Runtime.Serialization.Formatters.Binary.BinaryFormatter 
$sr = New-Object IO.StreamReader($syncToyEngineConfigPath)
do{
    $seConfig = [SyncToy.SyncEngineConfig]$bf.Deserialize($sr.BaseStream)
    if ($seConfig.Name -eq 'Test'){
        $engineConfig = $seConfig
        break
    }
}
while($sr.BaseStream.Position -lt $sr.BaseStream.Length)
$sr.Close()
$sr.Dispose()

#invoke the sync
$syncEngine = New-Object SyncToy.SyncEngine($engineConfig)
$syncEngine.Sync()

But unfortunately the last line causes PowerShell to hang. After multiple unsuccessful attempts to work around this (also implementing the same as a C# PowerShell cmdlet). I ended up writing a C# executable that only takes care of the synchronization and preview part, since I wanted to keep as much as possible of the code in PowerShell. The end result is a PowerShell module ‘SyncToy.psm1’ providing three functions:

Name Description
Get-SyncConfig To retrieve an existing sync configuration (FolderPair) either setup via Set-SyncConfig or GUI
Set-SyncConfig To configure a new Sync Configuration (FolderPair). Those can be stored into the default configuration that the GUI uses (default behaviour) or into a custom path
Invoke-Sync To preview a sync operation or to run the actual sync operation showing results and a progress bar

Let’s have a look at an example usage. Setting up two folders and a sync between the two. The below code is part of the module (Test-SyncToy), and can be downloaded via GitHub:

#Import the module
Import-Module $PSScriptRoot\SyncToy.psm1
#create folders for testing
#leftDir containing some content
$leftDir = "$env:TEMP\Left"
mkdir $leftDir | Out-Null
foreach ($num in 1..30){
    mkdir "$leftDir\test$num" | Out-Null
    foreach ($num2 in 1..10){
        $extension = '.txt'
        if ($num2 % 2){
            $extension = '.ps1'
        }
        "Test $num2" | Set-Content -Path ("$leftDir\test$num\test$num2" + $extension)
    }
}
#rightDir as the initial destination
$rightDir = "$env:TEMP\Right"
mkdir $rightDir | Out-Null

#exclude test10-test29 sub-folders from sync
$excludeFolders = (dir "$leftDir\test[1-2][0-9]" -Directory).FullName 

#setup the sync configuration
Set-SyncConfig -folderPairName 'Test' -leftDir $leftDir -rightDir $rightDir -syncMode Synchronize `
    -includedFilesPattern '*.ps1' -excludedSubFolders $excludeFolders 

#preview the sync
$previewResults = Invoke-Sync -folderPairName 'Test' -previewOnly
$previewResults
$previewResults.Action
#run the snyc
$results = Invoke-Sync -folderPairName 'Test' 
$results

Please let me know if you have any further suggestions, questions or comments about the module.
shareThoughts


Photo Credit: Tatters ❀ via Compfight cc