Introduction
Microsoft's SyncToy is a quality tool that allows you to synchronize a set of directory pairs. It is very useful for maintaining a backup of your critical files. SyncToy has a very nice GUI and is intended to be run interactively. When I set up a scheduled task to run my SyncToy configuration as a nightly, unattended job, it failed because it was not able to interact with the desktop. This limitation was bothersome, so I put that idea aside, hoping that one day Microsoft would release an updated version that supported unattended execution.
I was therefore pleasantly surprised to find a recent article posted on CodeProject that solved this problem. The article "Quietly run Microsoft's SyncToy" by Domenic demonstrated how to call the SyncToyEngine.dll from your own code. This solved the problem of running SyncToy unattended.
Background
Domenic demonstrated how to read the SyncToy configuration file, instantiate the SyncEngine
and call the Preview
and Sync
methods. This is what is needed to operate the SyncToy engine. When I ran to code, it worked very well. On closer inspection, however, I noticed that only the first folder pair in my configuration was being used. After inspecting the SyncToyEngine
in the object browser, I could find no indication of a way to iterate through the set of folder pairs in the configuration file. After groping around in SyncToy.exe using ILDASM, I noticed that there was a call that used an array list of "Engines." So, now I knew that different instances of the engine needed to be created and configured with the needed folder pair data. The solution was to keep reading the configuration file stream to obtain all of the configured folder pairs.
Using the code
This code was inspired by and based on the work of Domenic in his article "Quietly run Microsoft's SyncToy." Being a console application, the path to the SyncToy configuration file is passed in as a command line argument. Typically, this path will look like: C:\Documents and Settings\<username>\My Documents\SyncToyData\SyncToyDirPairs.bin</username>. This is accomplished in the Main program:
static void Main(string[] args)
{
Console.WriteLine("SyncToyExec Starting {0}",
DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss"));
string ConfigFileName = args[0];
if (ConfigFileName.Length > 0)
{
SyncToyExec app = new SyncToyExec();
app.SyncFolderPairs(ConfigFileName);
}
else
{
Console.WriteLine("No SyncToy Configuration " +
"File Name Supplied. Aborting...");
}
Console.WriteLine("SyncToyExec Ending {0}",
DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss"));
}
Once the name of the configuration file is known, the file is read in the SyncFolderPairs
method. SyncFolderPairs
reads the input stream, deserializes it into a series of SyncToy.SyncEngineConfig
objects and stores them in an ArrayList
.
public void SyncFolderPairs(string ConfigFileName)
{
SyncToy.SyncEngineConfig SEConfig = null;
ArrayList EngineConfigs = new ArrayList();
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf =
new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
Console.WriteLine("SyncToy Configuration File = {0}", ConfigFileName);
try
{
using (StreamReader sr = new StreamReader(ConfigFileName))
{
do
{
SEConfig = (SyncToy.SyncEngineConfig)
bf.Deserialize(sr.BaseStream);
EngineConfigs.Add(SEConfig);
}
while (sr.BaseStream.Position < sr.BaseStream.Length);
sr.Close();
}
foreach(SyncToy.SyncEngineConfig Config in EngineConfigs)
{
SyncFolderPair(Config);
}
}
catch (Exception ex)
{
EventLog.WriteEntry("SyncToy", ex.Message, EventLogEntryType.Error);
Console.WriteLine("SyncToy Exception -> {0}", ex.Message);
}
}
Once the SyncToy.SyncEngineConfig
objects have been read, they are passed to the SyncFolderPair
method that actually performs the SyncToy actions:
private void SyncFolderPair(SyncToy.SyncEngineConfig Config)
{
PercentComplete = 0;
SyncToy.SyncEngine Engine = new SyncToy.SyncEngine(Config);
Engine.syncEvent +=
new SyncToy.SyncEventHandler(SyncEngineEventHandler);
Console.WriteLine("Analyzing...");
Engine.Preview();
Console.WriteLine("Completed Analyzing...\r\n");
Console.WriteLine("Syncronizing...");
Engine.Sync();
EventLog.WriteEntry("SyncToy", Engine.StatSummary,
EventLogEntryType.Information);
Console.WriteLine("Sync Status: {0}\r\n", Engine.StatSummary);
Engine.syncEvent -=
new SyncToy.SyncEventHandler(SyncEngineEventHandler);
Engine = null;
}
As SyncToy.SyncEngine
processes each entry in the folder pair configuration, it raises a number of events that can be of use to you. Here, an event handler, SyncEngineEventHandler
, receives the event stream and uses the PercentComplete
property to update the user regarding progress.
public void SyncEngineEventHandler(object sender,
SyncToy.SyncEventArgs SEArgs)
{
if (SEArgs.Failed)
{
string Msg = string.Format("Error: {0}", SEArgs.Action.ToErrorText());
EventLog.WriteEntry("SyncToy", Msg, EventLogEntryType.Error);
Console.WriteLine(Msg);
}
if (SEArgs.Type == SyncToy.SyncEventType.ActionStarting &&
File.Exists(SEArgs.Action.DestinationFullPath))
{
File.SetAttributes(SEArgs.Action.DestinationFullPath,
FileAttributes.Normal);
}
else if (SEArgs.Type == SyncToy.SyncEventType.ActionAttempted &&
File.Exists(SEArgs.Action.DestinationFullPath) &&
File.Exists(SEArgs.Action.SourceFullPath))
{
File.SetAttributes(SEArgs.Action.DestinationFullPath,
File.GetAttributes(SEArgs.Action.SourceFullPath));
}
if ((SEArgs.PercentComplete > 0) &&
(SEArgs.PercentComplete != PercentComplete) &&
(SEArgs.PercentComplete % 5 == 0))
{
PercentComplete = SEArgs.PercentComplete;
Console.WriteLine("{0}% Completed", PercentComplete);
}
}
Usage
I have been using SyncToyExec to run scheduled backups of my files. I just copy SyncToyExec.exe into the same folder used by SyncToy.exe and then I set up a task in Scheduled Tasks to run every night. So far, it has been very successful.
History
- Version 1.0 -- 4 November, 2006.
- Version 1.1 -- 30 November, 2006.
- Version 1.2 -- 13 June, 2007.
I have been programming professionally since the early eighties. I cut my teeth on the Apple II, but the WinTel platform has been my focus for remainder of my career. I am currently employed in the gaming industry working on data systems and Windows applications. When not working, I can be found wandering the fields and forests of western Oregon while dreaming of the beaches of Thailand!