How to: Synchronize Files by Using Managed Code

This topic shows how to use a managed language, such as C# or Visual Basic, to create an application that synchronizes files and subfolders by using the Sync Framework file synchronization provider.

The examples in this topic focus on the following Sync Framework types:

Understanding File Synchronization

Sync Framework implements a synchronization provider that can be used to synchronize files and subfolders that are contained in a folder on a file system. This provider exposes several configurable settings to give a finer degree of control over exactly how synchronization occurs and which items are synchronized. To synchronize files between two folders, an application completes the following basic steps:

  1. Creates an FileSyncProvider object to represent each folder.

  2. Passes the two providers to an SyncOrchestrator object, and specifies one as the source provider and the other as the destination provider.

  3. Calls Synchronize to start the synchronization session.

For more information about synchronizing files, see Synchronizing Files.

Example

The example code in this section is from a console application that synchronizes two directories, including the subdirectories and files in those directories. The example code shows the following tasks:

  • How to set synchronization options.

  • How to explicitly perform change detection for a replica.

  • How to specify a filter that controls which items are included in synchronization.

  • How to handle conflicts that can occur during synchronization.

  • How to synchronize two replicas.

After showing these code examples, we include the complete code for the application so that you can build and run it.

Setting Synchronization Options

The FileSyncOptions object enables you to set several options for file synchronization, including how to detect changes and whether to delete items during synchronization or move them to the Recycle Bin. The following code example sets four options, three of which are related to item deletes. The option ExplicitDetectChanges means that Sync Framework will not perform change detection unless the application explicitly calls DetectChanges. This is explained in the next section "Performing Change Detection".

FileSyncOptions options = FileSyncOptions.ExplicitDetectChanges |
         FileSyncOptions.RecycleDeletedFiles | FileSyncOptions.RecyclePreviousFileOnUpdates | 
         FileSyncOptions.RecycleConflictLoserFiles;

Performing Change Detection

By default, Sync Framework performs change detection at both replicas whenever Synchronize is called. Change detection enables Sync Framework to determine which items should be sent from the source to the destination and which items, if any, are in conflict. By specifying ExplicitDetectChanges, you can control when change detection is performed. The following code example calls change detection for each replica before Synchronize is ever called. This example is meant to illustrate DetectChanges, but it does have the benefit of having one change detection pass rather than the two that would occur when we perform bidirectional synchronization later in the application.

DetectChangesOnFileSystemReplica(
    replica1RootPath, filter, options);
DetectChangesOnFileSystemReplica(
    replica2RootPath, filter, options);

public static void DetectChangesOnFileSystemReplica(
        string replicaRootPath,
        FileSyncScopeFilter filter, FileSyncOptions options)
{
    FileSyncProvider provider = null;

    try
    {
        provider = new FileSyncProvider(replicaRootPath, filter, options);
        provider.DetectChanges();
    }
    finally
    {
        // Release resources.
        if (provider != null)
            provider.Dispose();
    }
}

Public Shared Sub DetectChangesOnFileSystemReplica(ByVal replicaRootPath As String, ByVal filter As FileSyncScopeFilter, _
                                                   ByVal options As FileSyncOptions)
    Dim provider As FileSyncProvider = Nothing

    Try
        provider = New FileSyncProvider(replicaRootPath, filter, options)
        provider.DetectChanges()
    Finally
        ' Release resources. 
        If provider IsNot Nothing Then
            provider.Dispose()
        End If
    End Try
End Sub

Specifying a Static Filter

Static filters can be set to exclude files by name (including wildcard names) and by attribute. Static filters can also be set to exclude the contents of whole subfolders. Or, an explicit list of file names to include (including wildcard names) can be specified. To be included in the scope, a file or a folder must pass all filters. For example, if all files that have a .txt extension are excluded from the scope and MyFile.txt is specified in the list of files to explicitly include in the scope, MyFile.txt will be excluded because of its .txt extension.

The following code example uses the FileSyncScopeFilter object to create a filter that excludes all *.lnk files. A filter has no relationship to its creating provider. To connect a filter to a provider, pass the filter to one of the constructors for FileSyncProvider or by setting the ScopeFilter property. In the sample application, we do this in the DetectChangesOnFileSystemReplica() method because the filter is relevant only for change detection. Because the filter is independent of the provider, only one filter should be created per synchronization session; providers should not use different filters, because this can lead to non-convergence of data.

FileSyncScopeFilter filter = new FileSyncScopeFilter();
filter.FileNameExcludes.Add("*.lnk");

In addition to static filters, you can also exclude files during synchronization by handling an event raised by the provider. For more information, see Controlling Which Files Are Synchronized.

Handling Conflicts

Sync Framework detects and resolves concurrency conflicts and constraint conflicts for files and folders. A concurrency conflict occurs when the same item is changed at both replicas since the last synchronization session between those replicas. A constraint conflict occurs if a file or folder with the same name is added to both replicas. Conflicts are resolved by keeping the file or folder with the most recent change and deleting (or moving) the file or folder with the older change. For files, you also have the option of specifying that the source or destination should win the conflict, regardless of which change occurred first. The following code example registers event handlers for the ItemConflicting and ItemConstraint events that are available through the SyncCallbacks object. The methods that are called resolve all conflicts in favor of the source and write information to the console.

SyncCallbacks destinationCallbacks = destinationProvider.DestinationCallbacks;
destinationCallbacks.ItemConflicting += new EventHandler<ItemConflictingEventArgs>(OnItemConflicting);
destinationCallbacks.ItemConstraint += new EventHandler<ItemConstraintEventArgs>(OnItemConstraint);

public static void OnItemConflicting(object sender, ItemConflictingEventArgs args)
{
    args.SetResolutionAction(ConflictResolutionAction.SourceWins);
    Console.WriteLine("-- Concurrency conflict detected for item " + args.DestinationChange.ItemId.ToString());
}

public static void OnItemConstraint(object sender, ItemConstraintEventArgs args)
{
    args.SetResolutionAction(ConstraintConflictResolutionAction.SourceWins);
    Console.WriteLine("-- Constraint conflict detected for item " + args.DestinationChange.ItemId.ToString());
}

Synchronizing Two Replicas

After options and filters are set, the application synchronizes the two replicas by instantiating a SyncOrchestrator, and calling the Synchronize method. The following code example specifies the provider for each replica, sets options, registers event handlers, specifies a synchronization direction of Upload, and calls Synchronize. The method is called twice to perform bidirectional synchronization between the replicas.

SyncFileSystemReplicasOneWay(replica1RootPath, replica2RootPath, null, options);
SyncFileSystemReplicasOneWay(replica2RootPath, replica1RootPath, null, options);

public static void SyncFileSystemReplicasOneWay(
        string sourceReplicaRootPath, string destinationReplicaRootPath,
        FileSyncScopeFilter filter, FileSyncOptions options)
{
    FileSyncProvider sourceProvider = null;
    FileSyncProvider destinationProvider = null;

    try
    {
        // Instantiate source and destination providers, with a null filter (the filter
        // was specified in DetectChangesOnFileSystemReplica()), and options for both.
        sourceProvider = new FileSyncProvider(
            sourceReplicaRootPath, filter, options);
        destinationProvider = new FileSyncProvider(
            destinationReplicaRootPath, filter, options);

        // Register event handlers so that we can write information
        // to the console.
        destinationProvider.AppliedChange +=
            new EventHandler<AppliedChangeEventArgs>(OnAppliedChange);
        destinationProvider.SkippedChange +=
            new EventHandler<SkippedChangeEventArgs>(OnSkippedChange);

        // Use SyncCallbacks for conflicting items.
        SyncCallbacks destinationCallbacks = destinationProvider.DestinationCallbacks;
        destinationCallbacks.ItemConflicting += new EventHandler<ItemConflictingEventArgs>(OnItemConflicting);
        destinationCallbacks.ItemConstraint += new EventHandler<ItemConstraintEventArgs>(OnItemConstraint);

        SyncOrchestrator agent = new SyncOrchestrator();
        agent.LocalProvider = sourceProvider;
        agent.RemoteProvider = destinationProvider;
        agent.Direction = SyncDirectionOrder.Upload; // Upload changes from the source to the destination.

        Console.WriteLine("Synchronizing changes to replica: " +
            destinationProvider.RootDirectoryPath);
        agent.Synchronize();
    }
    finally
    {
        // Release resources.
        if (sourceProvider != null) sourceProvider.Dispose();
        if (destinationProvider != null) destinationProvider.Dispose();
    }
}

Public Shared Sub SyncFileSystemReplicasOneWay(ByVal sourceReplicaRootPath As String, _
                                               ByVal destinationReplicaRootPath As String, ByVal filter As FileSyncScopeFilter, _
                                               ByVal options As FileSyncOptions)

    Dim sourceProvider As FileSyncProvider = Nothing
    Dim destinationProvider As FileSyncProvider = Nothing

    Try
        ' Instantiate source and destination providers, with a null filter (the filter
        ' was specified in DetectChangesOnFileSystemReplica()), and options for both.
        sourceProvider = New FileSyncProvider(sourceReplicaRootPath, filter, options)
        destinationProvider = New FileSyncProvider(destinationReplicaRootPath, filter, options)

        ' Register event handlers so that we can write information 
        ' to the console. 
        AddHandler destinationProvider.AppliedChange, AddressOf OnAppliedChange
        AddHandler destinationProvider.SkippedChange, AddressOf OnSkippedChange

        ' Use SyncCallbacks for conflicting items.
        Dim destinationCallbacks As SyncCallbacks = destinationProvider.DestinationCallbacks
        AddHandler destinationCallbacks.ItemConflicting, AddressOf OnItemConflicting
        AddHandler destinationCallbacks.ItemConstraint, AddressOf OnItemConstraint

        Dim agent As New SyncOrchestrator()
        agent.LocalProvider = sourceProvider
        agent.RemoteProvider = destinationProvider
        agent.Direction = SyncDirectionOrder.Upload
        ' Upload changes from the source to the destination. 
        Console.WriteLine("Synchronizing changes to replica: " & destinationProvider.RootDirectoryPath)
        agent.Synchronize()
    Finally
        ' Release resources. 
        If sourceProvider IsNot Nothing Then
            sourceProvider.Dispose()
        End If
        If destinationProvider IsNot Nothing Then
            destinationProvider.Dispose()
        End If
    End Try
End Sub

Complete Code Example

The following code is the complete code for this example. The previous examples in this section were taken from this code. To run this code:

  • Create a console application project, and add the code to the project.

  • Add references to Microsoft.Synchronzation.dll and Microsoft.Synchronzation.Files.dll.

  • Build the project to create an executable.

  • Run the executable from the command line to synchronize the files and subdirectories of two replica directories: MyExeName.exe \path\to\directoryA \path\to\directoryB.

using System;
using System.IO;
using Microsoft.Synchronization;
using Microsoft.Synchronization.Files;

namespace Microsoft.Samples.Synchronization
{
    class Program
    {
        public static void Main(string[] args)
        {
            if (args.Length < 2 ||
                string.IsNullOrEmpty(args[0]) || string.IsNullOrEmpty(args[1]) ||
                !Directory.Exists(args[0]) || !Directory.Exists(args[1]))
            {
                Console.WriteLine(
                  "Usage: MyExecutableName.exe [valid directory path 1] [valid directory path 2]");
                return;
            }

            string replica1RootPath = args[0];
            string replica2RootPath = args[1];

            try
            {
                // Set options for the synchronization session. In this case, options specify
                // that the application will explicitly call FileSyncProvider.DetectChanges, and
                // that items should be moved to the Recycle Bin instead of being permanently deleted.
                FileSyncOptions options = FileSyncOptions.ExplicitDetectChanges |
                         FileSyncOptions.RecycleDeletedFiles | FileSyncOptions.RecyclePreviousFileOnUpdates | 
                         FileSyncOptions.RecycleConflictLoserFiles;

                // Create a filter that excludes all *.lnk files. The same filter should be used 
                // by both providers.
                FileSyncScopeFilter filter = new FileSyncScopeFilter();
                filter.FileNameExcludes.Add("*.lnk");

                // Explicitly detect changes on both replicas before syncyhronization occurs.
                // This avoids two change detection passes for the bidirectional synchronization 
                // that we will perform.
                DetectChangesOnFileSystemReplica(
                    replica1RootPath, filter, options);
                DetectChangesOnFileSystemReplica(
                    replica2RootPath, filter, options);

                // Synchronize the replicas in both directions. In the first session replica 1 is
                // the source, and in the second session replica 2 is the source. The third parameter
                // (the filter value) is null because the filter is specified in DetectChangesOnFileSystemReplica().
                SyncFileSystemReplicasOneWay(replica1RootPath, replica2RootPath, null, options);
                SyncFileSystemReplicasOneWay(replica2RootPath, replica1RootPath, null, options);
            }
            catch (Exception e)
            {
                Console.WriteLine("\nException from File Sync Provider:\n" + e.ToString());
            }
        }

        // Create a provider, and detect changes on the replica that the provider
        // represents.
        public static void DetectChangesOnFileSystemReplica(
                string replicaRootPath,
                FileSyncScopeFilter filter, FileSyncOptions options)
        {
            FileSyncProvider provider = null;

            try
            {
                provider = new FileSyncProvider(replicaRootPath, filter, options);
                provider.DetectChanges();
            }
            finally
            {
                // Release resources.
                if (provider != null)
                    provider.Dispose();
            }
        }

        public static void SyncFileSystemReplicasOneWay(
                string sourceReplicaRootPath, string destinationReplicaRootPath,
                FileSyncScopeFilter filter, FileSyncOptions options)
        {
            FileSyncProvider sourceProvider = null;
            FileSyncProvider destinationProvider = null;

            try
            {
                // Instantiate source and destination providers, with a null filter (the filter
                // was specified in DetectChangesOnFileSystemReplica()), and options for both.
                sourceProvider = new FileSyncProvider(
                    sourceReplicaRootPath, filter, options);
                destinationProvider = new FileSyncProvider(
                    destinationReplicaRootPath, filter, options);

                // Register event handlers so that we can write information
                // to the console.
                destinationProvider.AppliedChange +=
                    new EventHandler<AppliedChangeEventArgs>(OnAppliedChange);
                destinationProvider.SkippedChange +=
                    new EventHandler<SkippedChangeEventArgs>(OnSkippedChange);

                // Use SyncCallbacks for conflicting items.
                SyncCallbacks destinationCallbacks = destinationProvider.DestinationCallbacks;
                destinationCallbacks.ItemConflicting += new EventHandler<ItemConflictingEventArgs>(OnItemConflicting);
                destinationCallbacks.ItemConstraint += new EventHandler<ItemConstraintEventArgs>(OnItemConstraint);

                SyncOrchestrator agent = new SyncOrchestrator();
                agent.LocalProvider = sourceProvider;
                agent.RemoteProvider = destinationProvider;
                agent.Direction = SyncDirectionOrder.Upload; // Upload changes from the source to the destination.

                Console.WriteLine("Synchronizing changes to replica: " +
                    destinationProvider.RootDirectoryPath);
                agent.Synchronize();
            }
            finally
            {
                // Release resources.
                if (sourceProvider != null) sourceProvider.Dispose();
                if (destinationProvider != null) destinationProvider.Dispose();
            }
        }

        // Provide information about files that were affected by the synchronization session.
        public static void OnAppliedChange(object sender, AppliedChangeEventArgs args)
        {
            switch (args.ChangeType)
            {
                case ChangeType.Create:
                    Console.WriteLine("-- Applied CREATE for file " + args.NewFilePath);
                    break;
                case ChangeType.Delete:
                    Console.WriteLine("-- Applied DELETE for file " + args.OldFilePath);
                    break;
                case ChangeType.Update:
                    Console.WriteLine("-- Applied OVERWRITE for file " + args.OldFilePath);
                    break;
                case ChangeType.Rename:
                    Console.WriteLine("-- Applied RENAME for file " + args.OldFilePath +
                                      " as " + args.NewFilePath);
                    break;
            }
        }

        // Provide error information for any changes that were skipped.
        public static void OnSkippedChange(object sender, SkippedChangeEventArgs args)
        {
            Console.WriteLine("-- Skipped applying " + args.ChangeType.ToString().ToUpper()
                  + " for " + (!string.IsNullOrEmpty(args.CurrentFilePath) ?
                                args.CurrentFilePath : args.NewFilePath) + " due to error");

            if (args.Exception != null)
                Console.WriteLine("   [" + args.Exception.Message + "]");
        }

        // By default, conflicts are resolved in favor of the last writer. In this example,
        // the change from the source in the first session (replica 1), will always
        // win the conflict.
        public static void OnItemConflicting(object sender, ItemConflictingEventArgs args)
        {
            args.SetResolutionAction(ConflictResolutionAction.SourceWins);
            Console.WriteLine("-- Concurrency conflict detected for item " + args.DestinationChange.ItemId.ToString());
        }

        public static void OnItemConstraint(object sender, ItemConstraintEventArgs args)
        {
            args.SetResolutionAction(ConstraintConflictResolutionAction.SourceWins);
            Console.WriteLine("-- Constraint conflict detected for item " + args.DestinationChange.ItemId.ToString());
        }
    }
}

In the Visual Basic example, the code explicitly sets the MTAThread attribute on the Main() method. File synchronization provider requires applications to use the multithreaded apartment (MTA) threading model.

Imports System
Imports System.IO
Imports Microsoft.Synchronization
Imports Microsoft.Synchronization.Files

Namespace Microsoft.Samples.Synchronization

    Class Program

        ' File synchronization provider requires applications to use the multithreaded apartment (MTA) 
        ' threading model. This is specified by using the MTAThread attribute.
        <MTAThreadAttribute()> _
        Public Shared Sub Main(ByVal args As String())
            If args.Length < 2 OrElse String.IsNullOrEmpty(args(0)) OrElse String.IsNullOrEmpty(args(1)) OrElse Not Directory.Exists(args(0)) OrElse Not Directory.Exists(args(1)) Then
                Console.WriteLine("Usage: MyExecutableName.exe [valid directory path 1] [valid directory path 2]")
                Exit Sub
            End If

            Dim replica1RootPath As String = args(0)
            Dim replica2RootPath As String = args(1)

            Try
                ' Set options for the synchronization session. In this case, options specify 
                ' that the application will explicitly call FileSyncProvider.DetectChanges, and 
                ' that items should be moved to the Recycle Bin instead of being permanently deleted. 
                Dim options As FileSyncOptions = _
                    FileSyncOptions.ExplicitDetectChanges Or FileSyncOptions.RecycleDeletedFiles _
                    Or FileSyncOptions.RecyclePreviousFileOnUpdates _
                    Or FileSyncOptions.RecycleConflictLoserFiles

                ' Create a filter that excludes all *.lnk files. The same filter should be used 
                ' by both providers.
                Dim filter As New FileSyncScopeFilter()
                filter.FileNameExcludes.Add("*.lnk")

                ' Explicitly detect changes on both replicas before syncyhronization occurs. 
                ' This avoids two change detection passes for the bidirectional synchronization 
                ' that we will perform. 
                DetectChangesOnFileSystemReplica(replica1RootPath, filter, options)
                DetectChangesOnFileSystemReplica(replica2RootPath, filter, options)

                ' Synchronize the replicas in both directions. In the first session replica 1 is 
                ' the source, and in the second session replica 2 is the source. The third parameter
                ' (the filter value) is null because the filter is specified in DetectChangesOnFileSystemReplica().
                SyncFileSystemReplicasOneWay(replica1RootPath, replica2RootPath, Nothing, options)
                SyncFileSystemReplicasOneWay(replica2RootPath, replica1RootPath, Nothing, options)
            Catch e As Exception
                Console.WriteLine(vbLf & "Exception from File Sync Provider:" & vbLf & e.ToString())
            End Try
        End Sub

        ' Create a provider, and detect changes on the replica that the provider 
        ' represents. 
        Public Shared Sub DetectChangesOnFileSystemReplica(ByVal replicaRootPath As String, ByVal filter As FileSyncScopeFilter, _
                                                           ByVal options As FileSyncOptions)
            Dim provider As FileSyncProvider = Nothing

            Try
                provider = New FileSyncProvider(replicaRootPath, filter, options)
                provider.DetectChanges()
            Finally
                ' Release resources. 
                If provider IsNot Nothing Then
                    provider.Dispose()
                End If
            End Try
        End Sub

        Public Shared Sub SyncFileSystemReplicasOneWay(ByVal sourceReplicaRootPath As String, _
                                                       ByVal destinationReplicaRootPath As String, ByVal filter As FileSyncScopeFilter, _
                                                       ByVal options As FileSyncOptions)

            Dim sourceProvider As FileSyncProvider = Nothing
            Dim destinationProvider As FileSyncProvider = Nothing

            Try
                ' Instantiate source and destination providers, with a null filter (the filter
                ' was specified in DetectChangesOnFileSystemReplica()), and options for both.
                sourceProvider = New FileSyncProvider(sourceReplicaRootPath, filter, options)
                destinationProvider = New FileSyncProvider(destinationReplicaRootPath, filter, options)

                ' Register event handlers so that we can write information 
                ' to the console. 
                AddHandler destinationProvider.AppliedChange, AddressOf OnAppliedChange
                AddHandler destinationProvider.SkippedChange, AddressOf OnSkippedChange

                ' Use SyncCallbacks for conflicting items.
                Dim destinationCallbacks As SyncCallbacks = destinationProvider.DestinationCallbacks
                AddHandler destinationCallbacks.ItemConflicting, AddressOf OnItemConflicting
                AddHandler destinationCallbacks.ItemConstraint, AddressOf OnItemConstraint

                Dim agent As New SyncOrchestrator()
                agent.LocalProvider = sourceProvider
                agent.RemoteProvider = destinationProvider
                agent.Direction = SyncDirectionOrder.Upload
                ' Upload changes from the source to the destination. 
                Console.WriteLine("Synchronizing changes to replica: " & destinationProvider.RootDirectoryPath)
                agent.Synchronize()
            Finally
                ' Release resources. 
                If sourceProvider IsNot Nothing Then
                    sourceProvider.Dispose()
                End If
                If destinationProvider IsNot Nothing Then
                    destinationProvider.Dispose()
                End If
            End Try
        End Sub

        ' Provide information about files that were affected by the synchronization session. 
        Public Shared Sub OnAppliedChange(ByVal sender As Object, ByVal args As AppliedChangeEventArgs)
            Select Case args.ChangeType
                Case ChangeType.Create
                    Console.WriteLine("-- Applied CREATE for file " & args.NewFilePath)
                    Exit Select
                Case ChangeType.Delete
                    Console.WriteLine("-- Applied DELETE for file " & args.OldFilePath)
                    Exit Select
                Case ChangeType.Update
                    Console.WriteLine("-- Applied OVERWRITE for file " & args.OldFilePath)
                    Exit Select
                Case ChangeType.Rename
                    Console.WriteLine(("-- Applied RENAME for file " & args.OldFilePath & " as ") & args.NewFilePath)
                    Exit Select
            End Select
        End Sub

        ' Provide error information for any changes that were skipped. 
        Public Shared Sub OnSkippedChange(ByVal sender As Object, ByVal args As SkippedChangeEventArgs)
            Console.WriteLine(("-- Skipped applying " & args.ChangeType.ToString().ToUpper() & " for ") & (If(Not String.IsNullOrEmpty(args.CurrentFilePath), args.CurrentFilePath, args.NewFilePath)) & " due to error")

            If args.Exception IsNot Nothing Then
                Console.WriteLine(" [" & args.Exception.Message & "]")
            End If
        End Sub

        ' By default, conflicts are resolved in favor of the last writer. In this example, 
        ' the change from the source in the first session (replica 1), will always 
        ' win the conflict. 
        Public Shared Sub OnItemConflicting(ByVal sender As Object, ByVal args As ItemConflictingEventArgs)
            args.SetResolutionAction(ConflictResolutionAction.SourceWins)
            Console.WriteLine("-- Concurrency conflict detected for item " & args.DestinationChange.ItemId.ToString())
        End Sub

        Public Shared Sub OnItemConstraint(ByVal sender As Object, ByVal args As ItemConstraintEventArgs)
            args.SetResolutionAction(ConstraintConflictResolutionAction.SourceWins)
            Console.WriteLine("-- Constraint conflict detected for item " & args.DestinationChange.ItemId.ToString())
        End Sub
    End Class
End Namespace

See Also

Show: