Creating a Custom Synchronization Manager Handler

 

Leszynski Group, Inc.

March 2006

Applies to:
   Microsoft Windows XP
   Microsoft Windows XP Tablet PC Edition 2005

Summary: This article and sample code demonstrate how to write and add your own custom synchronization handler to the Windows Synchronization Manager. (15 printed pages)

Click here to download the code sample for this article.

Contents

Introduction
FileSync Sample Overview
FileSync Registration and Invocation
FileSync Sample Architecture
Interfacing with MobSync
Extending the Sample
   GUIDs
   Descriptive Strings
   Synchronization Functionality
   ISyncMgrSynchronize Calls
Conclusion
Biography

Introduction

This article and sample code provide an example of how to write a custom synchronization handler that can be called by the Windows Synchronization Manager (SyncMgr) to perform file synchronization operations from custom applications. The sample code creates a custom handler and demonstrates how to:

  • Write a custom synchronization handler DLL in C#.
  • Register the handler with SyncMgr so that it is listed in the Windows Synchronization Manager's Items to Synchronize dialog.
  • Send events and information—such as synchronization status—from the custom handler back to the user interface.
  • Use the custom handler to perform a one-way synchronization between two file folders.
  • Code managed, .NET Framework APIs against existing COM components by using COM interop under difficult circumstances. In this particular case, the COM components do not follow normal conventions.
  • Invoke a custom handler through SyncMgr, either manually or through program code.
  • Invoke a custom handler directly from program code, bypassing SyncMgr

The sample includes two helper classes to use in your applications as-is or with customization:

  • Microsoft.MobilePC.Samples.SyncMgr.dll, which provides managed definitions of the COM interfaces used by the Windows Synchronization Manager.
  • Microsoft.MobilePC.Samples.FileSync.dll, which provides programmable file folder synchronization features for use in Visual Studio applications.

The sample was created in Visual Studio 2003 (.NET Framework 1.1) using C#, but the sample includes compiled binaries that can be demonstrated without Visual Studio. For installation instructions and demo use cases, see Using the FileSync Sample (UsingFileSync.doc), enclosed with the sample.

The sample code can be converted successfully to Visual Studio 2005 without errors but has not been extensively tested with this version.

FileSync Sample Overview

The goal of the FileSync sample is to provide a simple, yet realistic, example of a custom software solution that makes updated content available to a mobile PC for use when that computer is not connected to the company network. To do this, FileSync replicates the contents of a server folder (including all subfolders) across the network to a client folder on the mobile PC so that the client folder has exactly the same content as the server folder.

FileSync leverages SyncMgr, the same synchronization UI framework used by Windows Offline Files, but demonstrates how to use SyncMgr from within an application. Offline Files may not be appropriate for use in custom business solutions because:

  • Offline Files is configured and executed by the user through the Windows user interface, not from within an application. This means the user may modify or delete settings that are critical to application functionality.
  • By design, configuration and synchronization in Offline Files are not application-specific; they are machine-specific or profile-specific. While Offline Files is a convenient tool for broad file synchronization needs, it does not directly expose a programmable set of APIs for customization at the workflow or application level.

SyncMgr should not be confused with ActiveSync, which provides support for synchronizing data between a Windows-based desktop computer and portable devices running Windows CE .NET.

FileSync Registration and Invocation

Because SyncMgr is extensible, you can create custom synchronization "handlers" to be added to its list of items to synchronize. Figure 1 shows the Items to Synchronize dialog, the standard Windows user interface for SyncMgr. There are two ways to display this dialog:

  • Click Start, point to All Programs, point to Accessories, and then click Synchronize.

  • Click Start, click Run, type mobsync, and then click OK.

    Figure 1. Windows Synchronization Manager dialog

Once a handler is registered with SyncMgr, SyncMgr displays and invokes it when appropriate. The FileSync sample contains a component called FileSyncHandler, which the sample registers with SyncMgr, as shown in the following code sample:

/// <summary>
/// Registers FileSyncHandler with SyncMgr.
/// </summary>
/// <exception cref="System.Exception">Thrown when the handler can't be registered.</exception>
public static void RegisterWithSyncMgr()
{
   Guid syncmgrClsid = new Guid("6295DF27-35EE-11D1-8707-00C04FD93327");
   Type syncmgrType = Type.GetTypeFromCLSID(syncmgrClsid);
   ISyncMgrRegister smr = (ISyncMgrRegister)Activator.CreateInstance(syncmgrType);

   Guid fileSyncHandlerId = FileSyncHandler.FileSyncHandlerId;
   FileSyncConfig config = FileSyncConfig.GetConfig();
   int hresult = smr.RegisterSyncMgrHandler(ref fileSyncHandlerId, config.RegistryComment, 0);
   if (hresult != 0)
   {
      throw new Exception("Failed to register with HRESULT = " + hresult);
   }
}

Once registered, SyncMgr displays the handler FileSync Item in the Items to Synchronize dialog, as shown in Figure 2.

Figure 2. FileSync has been registered with SyncMgr

FileSync Sample Architecture

Figure 3 depicts the architecture of the FileSync component.

Figure 3. Component architecture for FileSync

This architecture has the following advantages:

  • Users can synchronize on-demand outside of the custom application. For example, a user can quickly synchronize before un-docking and going home. The custom handler can be registered and made available from within the standard Windows SyncMgr UI. This flow is indicated in Figure 3 by the Calls arrow from SyncMgr to FileSyncHandler. The user can also schedule SyncMgr to invoke the custom handler at user-selected events such as logon and logoff.
  • FileSyncHandler can be called directly from a custom application on demand. For example, when the application receives notification of changes to the application's data files on the server, the application can call FileSyncHandler. This enables you to use the same synchronization logic from SyncMgr and from direct calls to the handler. This flow is indicated in Figure 3 by the Calls arrows from FileSyncSample App to FileSync and from FileSync to FileSyncHandler; this calling convention bypasses SyncMgr.

A custom application can also invoke its custom handler through SyncMgr or have SyncMgr perform a complete synchronization of all handlers. While this method provides a consistent user experience by displaying the SyncMgr dialog and built-in progress information, you cannot control or customize this experience. For example, the invoking application has no way of determining when the synchronization is complete and whether or not SyncMgr executed each handler successfully.

A custom application may also have a need for multiple custom handlers. For example, an application may provide handlers for synchronization of its necessary data files and SQL Server database. In this case code a customized handler procedure to control the timing of, and receive the feedback from, the synchronization. Through code, the custom application can also provide a controlled and customized user experience, by way of a dialog, to manage synchronization initiation, progress, and results. The SyncMgr sample includes a synchronization status form that demonstrates this user experience. Because the SyncMgr UI is familiar to many users and is common across the Windows user experience, code a custom synchronization dialog only when the specific requirements of your application dictate user interaction that is different from that provided by SyncMgr.

The sample demonstrates how to directly invoke a custom handler and how to invoke by using SyncMgr. The FileSync class in the sample exposes an InvokeDirectly property that specifies which invocation method will be used. When InvokeDirectly == true, the following occurs:

  • The Sync method does not return until synchronization is complete. The method throws an exception if the synchronization is unsuccessful.
  • SyncStatus and SyncError events are raised to indicate progress or errors.
  • The FileSync class does not display any user interface. The custom application may present its own progress dialog.

When InvokeDirectly == false, the following occurs:

  • The Sync method returns immediately after invocation.
  • No exceptions are thrown to indicate an unsuccessful synchronization and no events are raised.
  • SyncMgr presents its built-in status dialog, which is either dismissed automatically upon successful completion, or displays any errors and must be dismissed explicitly by the user.

Interfacing with MobSync

The SyncMgr functionality used in the sample is derived from the COM component MobSync.dll, found in the Windows System32 folder. The most common way for managed code to call into an unmanaged DLL such as MobSync is to use an interop assembly. Microsoft does not ship an interop assembly for MobSync.dll, so the sample includes one for use with FileSync.

Unfortunately, you cannot create a MobSync.dll interop assembly by adding a reference to this COM component in the Visual Studio Solution Explorer. Attempting to do so produces an error. This occurs because MobSync.dll, unlike many COM component DLLs, doesn't have type library (TLB) information to support COM interop embedded inside it. The Windows Platform SDK does contain Mobsync.idl—the interface definition for this DLL—so normally you would expect to be able to use the MIDL utility to compile Mobsync.idl into a type library MobSync.tlb, from which an interop could be generated. However, using MIDL to compile MobSync.idl does not produce a TLB file as expected, because MobSync.idl does not contain a library section. So for the sample, we created a library as follows:

[
   uuid(699f380a-e10b-49d8-abb4-b4dc1d145768),
   version(1.0)
]
library MobSyncLib
{
   importlib("stdole32.tlb");

   interface ISyncMgrSynchronize;
   interface ISyncMgrSynchronizeCallback;
   interface ISyncMgrEnumItems;
   interface ISyncMgrSynchronizeInvoke;
   interface ISyncMgrRegister;

   [
   uuid(6295DF27-35EE-11d1-8707-00C04FD93327),
   helpstring("Common Synchronization UI service")
   ]
   coclass SyncMgr
   {
         [default]   interface ISyncMgrSynchronizeInvoke;
            interface ISyncMgrRegister;
   };
};

However, there is a problem in the declaration of the default interface—it has a source attribute, which incorrectly indicates that it is a source of events. That attribute has been removed. You can then use the library section and MIDL to produce a usable TLB file and created an interop assembly by referencing the TLB file in the sample.

In many cases, the interfaces provided by an interop assembly produced in this manner can be used directly. However, in the case of MobSync.dll, there are some problems that make it necessary to create custom interface definitions. These definitions are in the file SyncMgrInterfaces.cs in the sample. The following problems required custom interface definitions:

  • Several of the methods return success HRESULTs other than S_OK. For instance, ISyncMgrEnumItems::Next returns S_FALSE when it has no more items to return. To solve this, and to make the interface straightforward, we added a [PreserveSig] attribute to allow such methods to return an appropriate result value.
  • Many IDL attributes such as [length_is] and [size_is] cannot be represented in TLB format, and are therefore missing from the auto-generated interop assembly. We added these attributes in order to allow correct marshalling of arrays.
  • To conform with the .NET Framework's Common Language Specification (CLS), we changed all unsigned types to signed types.

FileSync must be registered as a COM interface in order for SyncMgr to invoke it. This is accomplished in Visual Studio by setting the Register for COM Interop property to true. This property is located in the SyncMgr project's Property Pages dialog in the Build node under Configuration Properties.

The interface can also be registered outside of Visual Studio by using the Regasm utility from the command line as follows:

Regasm Microsoft.MobilePC.Samples.FileSync.dll /codebase

A custom handler that you deploy with a business application should be registered by the application's installer. FileSync supports self-registration by deriving a class from System.Configuration.Install.Installer whose Install override method registers the COM interface. For information about how to use this class when creating an installation project in Visual Studio, see Adding Predefined Custom Actions in the Custom Actions Editor.

Extending the Sample

You can extend the FileSync sample code to use it as the basis for writing your own SyncMgr custom handler. If you choose not to extend the sample, its two assemblies (Microsoft.MobilePC.Samples.FileSync.dll and Microsoft.MobilePC.Samples.SyncMgr.dll) can be used as-is to provide your applications with simple, one-way file synchronization.

GUIDs

Each element of a handler that can be synchronized is called an Item. The first task for customization is to assign new GUIDs to the handler and to each of its Items to avoid conflicts with other COM CLSIDs and other handler Items. To generate a new GUID in Visual Studio:

  1. Click the Tools menu, and then click Create GUID.
  2. In the Create GUID dialog, select 4. Registry Format, (i.e. { xxxxxxxx-xxxxxxxx}), and then click Copy.
  3. Visual Studio places the new GUID on the Clipboard.

The CLSID of the handler is defined at the top of FileSyncHandler.cs as an attribute of the FileSyncHandler class. Paste the new GUID from the Clipboard over the CLSID GUID in FileSyncHandler.cs (you may need to remove the curly braces and the line break inserted by Visual Studio after the GUID):

    [Guid("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx "), 
    ClassInterface(ClassInterfaceType.None)]
    public class FileSyncHandler : ISyncMgrSynchronize

Next, generate another new GUID and change the GUID for your handler Item to the new GUID. The Item GUID is in the file EnumItems.cs as the first field:

/// <summary>
/// Implements the ISyncMgrEnumItems interface to enable SyncMgr to enumerate available 
/// synchronization items. FileSyncHandler has only one synchronization item.
/// </summary>
public class EnumItems : ISyncMgrEnumItems
{
    private Guid itemGuid = new Guid("xxxxxxxx-xxxx-xxxx-xxxx-
xxxxxxxxxxxx");
    ...
}

A custom handler can have more than one Item, but FileSync contains only a single Item. If your handler has more than one Item, use a different GUID for each Item. Creating multiple Items provides functionality that is useful in some custom application scenarios. For example, an application may provide both one-way and two-way file synchronization in the same handler, with each process driven from a different configuration file, and would benefit from the use of separate Items.

Descriptive Strings

The following strings are used in various places to identify the sample handler and should be changed to describe your modified handler. These strings are stored in a configuration file used by the sample and are also exposed as properties of FileSync. You can change the values in the configuration file or change their default values in FileSyncConfig.cs:

  • Handler name. SyncMgr displays this string in the SyncMgr user interface. The string is specified as SyncHandlerName in the configuration file and properties.
  • Item name. SyncMgr displays this string in the SyncMgr user interface. The string is specified as SyncItemName in the configuration file and properties.
  • Registration description. SyncMgr stores this string as the default value of the registry entry that tells SyncMgr to use the custom handler. The string is specified as RegistryComment in the configuration file and properties.

Synchronization Functionality

You may customize the primary synchronization functionality. FileSync exposes five items in its properties dialog: ClientFolder, ServerFolder, SyncHandlerName, SyncItemName, and RegistryComment. You can create your own properties form for SyncMgr, or modify the sample's PropertiesForm. The properties form is launched from the ShowProperties method in FileSyncHandler.cs. You must change the lines where the property values are initialized and then update them if the dialog result is "OK":

/// <summary>
/// Called by the Synchronization Manager when the user selects
/// an item in the Choice dialog and clicks the Properties button.
/// </summary>
/// <param name="hWndParent">[in] The parent hWnd for any user interface that a registered application displays to set properties.  This value can be NULL.</param>
/// <param name="ItemID">[in] The item ID for properties that are requested.</param>
public int ShowProperties(IntPtr hWndParent, ref Guid ItemID)
{
   if (syncMgrSynchronizeCallback == null)
   {
      unchecked
      {
         return (int) 0x80004005;  // E_FAIL
      }
   }

   NativeWindow nw = NativeWindow.FromHandle(hWndParent);
   PropertiesForm propertiesForm = new PropertiesForm();
   propertiesForm.ClientFolder = config.ClientFolder;
   propertiesForm.ServerFolder = config.ServerFolder;
   propertiesForm.SyncHandlerName = config.SyncHandlerName;
   propertiesForm.SyncItemName = config.SyncItemName;
   propertiesForm.RegistryComment = config.RegistryComment;
   if (DialogResult.OK == propertiesForm.ShowDialog((IWin32Window) nw))
   {
      config.ClientFolder = propertiesForm.ClientFolder;
      config.ServerFolder = propertiesForm.ServerFolder;
      config.SyncHandlerName = propertiesForm.SyncHandlerName;
      config.SyncItemName = propertiesForm.SyncItemName;
      config.RegistryComment = propertiesForm.RegistryComment;
      config.WriteConfig();
   }

   syncMgrSynchronizeCallback.ShowPropertiesCompleted(0);
   return 0;
}

You must also provide a method of persisting your handler's properties. FileSync writes its properties to a FileSync.config XML file located in the folder with the FileSync DLL. FileSync locates this folder by looking up the FileSyncHandler COM registration in the registry. We chose this approach because the following two alternate approaches do not work in this case. They yield different (and non-useful) locations under different execution scenarios:

  • Application.StartupPath. This property returns different values depending on how FileSyncHandler is invoked. Application.StartupPath returns the folder of the containing application if the handler is invoked directly, but it returns the folder for SyncMgr (Windows\System32) if the handler is invoked by SyncMgr.
  • Assembly.GetExecutingAssembly.Location. This property also returns different values depending on the FileSyncHandler invocation method, but for a different reason. When building the sample under Visual Studio, the FileSync DLL has its COM interface registered from SyncMgr's bin\debug folder, so this is the folder it executes from when invoked as a COM object. However, the FileSync DLL is copied by Visual Studio into the sample's bin\debug folder and it executes there when called directly from the sample, yielding a different execution folder to the Location property.

Use the class FileSyncConfig, located in FileSyncConfig.cs, as a starting point for leveraging this storage mechanism, or you can store your handler's properties in any other way that might be appropriate, such as in the registry or a database.

In addition to the properties mentioned previously, the time of the last synchronization is also stored in FileSync.config. You must persist this time somewhere if you want to remember and show it in the SyncMgr dialog's Last Updated column. It is convenient to store the time in the configuration file, but there are other options, such as in the registry.

FileSync also exposes its properties directly from the FileSync class, so that an application that calls it directly can programmatically read and write the properties. So if you change or add properties, you should make corresponding changes to these properties in FileSync.cs:

/// <summary>
/// Gets or sets the client-side folder that will be synchronized to match the ServerFolder.
/// </summary>
public string ClientFolder
{
   get
   {
      FileSyncHandler fsh = new FileSyncHandler();
      return fsh.ClientFolder;
   }
   set
   {
      FileSyncHandler fsh = new FileSyncHandler();
      fsh.ClientFolder = value;
   }
}

/// <summary>
/// Gets or sets the server-side folder that the ClientFolder will be synchronized to match.
/// </summary>
public string ServerFolder
{
   get
   {
      FileSyncHandler fsh = new FileSyncHandler();
      return fsh.ServerFolder;
   }
   set
   {
      FileSyncHandler fsh = new FileSyncHandler();
      fsh.ServerFolder = value;
   }
}

ISyncMgrSynchronize Calls

SyncMgr makes the following calls into the custom handler's ISyncMgrSynchronize interface during the process of performing a synchronization operation. All of these methods are defined in FileSyncHandler.cs.

Initialize

Initialize is the first method SyncMgr calls when using the custom handler. FileSyncHandler does nothing here except remember the state of the MayBotherUser flag.

Your handler should do any appropriate initialization here. The first time that your handler is called on a particular computer, perhaps display a dialog to let the user set the initial configuration or to show the user any appropriate information about your custom handler.

The dwSyncMgrFlags parameter tells the handler how the synchronization event was initiated, whether scheduled, on idle, or by some other means. FileSync ignores this information, but you may find it useful and can capture it during initialization.

Finally, the return value determines whether or not you want to process this synchronization event. The FileSync sample always processes it, but if you decide not to, perhaps based on the value of dwSyncMgrFlags, you can return S_FALSE (1) instead of S_OK (0).

PrepareForSync

SyncMgr calls the PrepareForSync method after Initialize when performing synchronization. FileSync does nothing here except remember its ItemId. You can do anything appropriate here, including showing a user interface if the MayBotherUser flag is set. One possible use for a dialog is to enable the user to log in to a server that requires authentication in order for synchronization to succeed. If you do show a user interface at this point, you must call ISyncMgrSynchronizeCallback::EnableModeless before and after showing the dialog. For more information about EnableModeless, see ShowError, later in this section.

Synchronize

Synchronize does the actual synchronization, so you may end up rewriting much of this method in your own handler, depending on your needs. Some important concepts include the following points:

  • You can call ISyncMgrSynchronizeCallback::EstablishConnection at the start of this method if you want SyncMgr to establish a network connection, typically a dial-up connection or a VPN. In this call, pass either the name of a specific connection or Null to use the default connection. Note that the FileSync implementation of this method doesn't try to establish the connection, but simply returns E_FAIL (0x80004005). When you call your custom handler directly (as opposed to calling it through SyncMgr), you may want to modify this behavior by changing the EstablishConnection method in FileSyncCallback.cs.
  • In order to provide good progress information, you must determine the total number of units of work that you will be processing before you actually start the synchronization. FileSync initially determines the number of files that will be copied or deleted and uses that count as the total work units (the maximum progress value), then increments the progress value each time it copies or deletes a file. Your handler can improve the accuracy of a progress bar by taking into account the size of the files involved, and whether they are being copied or deleted.
  • When your handler has new progress to report, instantiate a SyncMgrProgressItem object, populate it, and then pass it to SyncMgr with a call to ISyncMgrSynchronizeCallback::Progress.
  • Have your handler report errors by instantiating a SyncMgrLogErrorInfo object, populating it, and passing it to SyncMgr with a call to ISyncMgrSynchronizeCallback::LogError.
  • The behavior of ISyncMgrSynchronizeCallback::Progress and ISyncMgrSynchronizeCallback::LogError depends on whether the custom handler was called through SyncMgr or called directly:
    • If called through SyncMgr, progress is shown on the Progress tab of the SyncMgr dialog and errors are shown on its Results tab.
    • If called directly, SyncStatusDelegate and SyncErrorDelegate are called, notifying the invoking application to deal with the information directly.
  • If the return value from ISyncMgrSynchronizeCallback::Progress is not S_OK (0), then the user has canceled the synchronization. Your Synchronize method should return immediately with a non-success return value.

ShowError

SyncMgr calls the ShowError method when an error has occurred and the user clicks For more information, click here in the SyncMgr dialog's Results tab.

The errorId parameter is provided in the call to ISyncMgrSynchronizeCallback::LogError to enable the handler to provide information specific to the actual error instance. The FileSync sample does not use LogError and simply shows the same generic information every time.

Before showing its error dialog, your custom handler must request permission from SyncMgr by calling ISyncMgrSynchronizeCallback::EnableModeless with its parameter set to 1 (true). SyncMgr returns 0 (S_OK) if the handler is granted the requested permission. After the user dismisses the error dialog, the handler must notify SyncMgr that it is finished by calling ISyncMgrSynchronizeCallback::EnableModeless with its parameter set to 0 (false).

Conclusion

The Windows Synchronization Manager provides a simple and standardized user experience for synchronizing files for offline use on a mobile PC, but its architecture is difficult to program against from managed code. Using the techniques and helper classes in this article, your custom applications can now leverage the SyncMgr user interface by adding custom items to it for the benefit of your business applications. Additionally, with some minor modifications to the FileSync sample code, you can create custom handlers that can be used directly from your applications with or without the MobSync UI.

Windows Vista enhances the synchronization experience with an updated, centralized user interface that includes additional programmability support. Windows XP synchronization handlers like FileSync will continue to work in Windows Vista.

Biography

Leszynski Group is a solution provider and independent software developer specializing in development tools, Tablet PC applications, and mobilized databases. Leszynski Group is a regular contributor of MSDN content and has authored advanced software that includes the Snipping Tool and Physics Illustrator.