The Synchronization Shuffle
I've given this solution to a couple customers so far and it appears to be working for them, so I thought I'd share it with the world.
The Problem:
You've written an application which uses IExchangeExportChanges::Synchronize to synchronize data between your back end database and Exchange. This application is typically deployed directly on an Exchange server, or a machine where the MAPI download has been installed. Occasionally, folder will have items in them which cannot be read through MAPI - typically these are messages which arrived via SMTP and which fail content conversion. From Outlook, you can usually move these messages around, but you can't open them. When the Synchronize method processes a folder containing one of these messages, it returns MAPI_E_CORRUPT_DATA and the whole folder sync is aborted.
Before we can discuss workarounds, we need to understand the problem: The client sets up to do a sync between the client and the server, possibly using IExchangeExportChanges::Config. Then the synchronization is started. During the synchronization, the client will ask the server to fill buffers with data. The server will fill these buffers with data representing the messages on the server. If the server has trouble dealing with one of the messages, for any reason, an error state is entered. If this error state is not resolved, then the entire synchronization operation fails. Exchange's MAPI doesn't know how to resolve the error state, so we fail.
Given that Exchange's implementation of IExchangeExportChanges has worked this way for over a decade, and that Exchange's MAPI implementation enters Extended support next year, there's little chance we'd be able to implement logic for resolving the error.
The Workaround:
If you dig around in edkmdh.h, you'll find an interesting interface: IExchangeExportChanges3, which introduces ConfigForSelectiveSync. This function is identical in use to Config except it adds a new parameter, lpMsgList. What this parameter allows us to do is to tell the synchronizer which messages we want it to sync. Now - we'll still have the problem that if the set of messages we've asked to synchronize includes one of these bad messages, we'll get MAPI_E_CORRUPT_DATA from Synchronize. But we can apply a little binary search style logic to get around the problem. Here's the logic:
- Try to sync using IExchangeExportChanges::Config - most folders will sync without problem, not requiring further processing
- If you get MAPI_E_CORRUPT_DATA, get the contents table and use ConfigForSelectiveSync in batches of 10-20 messages at a time. Most batches should not fail.
- If a batch fails, use ConfigForSelectiveSync to sync one message at a time. If it fails, you've identified a problem message, which you could report, move to another folder, delete, etc.
Of course, we could skip straight from step 1 to step 3, but on very large folders synchronizing a single message at a time could be rather slow. In fact, it might make sense to use even larger batches, say 1/10th the size of the folder. Then, if a batch fails, split it up into tenths and iterate until a single message at a time is being synchronized.
The Other Workaround:
If you're doing the synchronization manually using the Exchange Server Protocol Documentation (and that's a big IF, as it ain't easy), then you can implement code to resolve the error condition. The place to start in that document is section 2.2.4.3.4 errorInfo.
Comments
Anonymous
June 21, 2008
Steve, just out of curiosity, besides this scenario, what was IExchangeExportChanges3 designed to do? Do you have a real-world case? When is it advantageous to use selective sync as opposed to just tracking all the changes and ignoring the messages that I do not want? Also, do I need to keep the list of messages the same or can it change between calls to ConfigForSelectiveSync? Thanks!Anonymous
June 23, 2008
As best as I can tell, it was originally checked in to support a feature in Outlook 98 (a precurser to cached mode?) but I can't find evidence that it was used outside of test cases. The advantage to using the selective sync of course is that you can avoid syncing messages you know are going to cause problems. Yes - you can change the list of messages to sync - that's the key to the algorithm.Anonymous
August 13, 2008
Any ides for this one: Error in IExchangeExportChanges::Synchronize: MAPI_E_CALL_FAILED Only happens on the calendar of one user. Exporting the data to a PST and importing it into another user's calendar seems to fix the issue. Two different ICS apps get this error now for this user's calendar. How can we tell what is wrong with the calendar (which appointment or porperty)?Anonymous
August 13, 2008
Well - you could check the item out against the protocol documentation to see if there's anything wrong with it. The latest MFCMAPI (http://codeplex.com/mfcmapi) can parse a number of appointment properties which should help your analysis.Anonymous
November 09, 2009
Does IExchangeExportChanges::UpdateState make changes internal to the exporter such that it corrupts the ICS checkpoint if IExchangeExportChanges::Synchronize() is called after? I ask because IExchangeExportChanges::Synchronize is returning MAPI_E_CALL_FAILED for certain mailboxes, after reading some data out of the ICS checkpoint and making a few callbacks into the supplies IExchangeImportContentChanges. This error only occurs after a successful call to IExchangeExportChanges::Config that passes in the ICS checkpoint from the last sync - a sync with no checkpoint given to config succeeds. When I walk the folder using IExchangeExportChanges3 and no previous ICS checkpoint (my understanding is that the v1 and v3 checkpoints aren't compatible), the entire contents of the folder synchronize successfully. The usage follows (approximate): hResult = sync->Config(..., stream, ...); do { hResult = sync->Synchronize(...); hResult = sync->UpdateState(stream); } while(hResult == SYNC_W_PROGRESS);Anonymous
January 27, 2010
In Exchange 2010, checkpointing has been removed (http://msdn.microsoft.com/en-us/library/ee219637(EXCHG.80).aspx), so there's no point in calling UpdateState in your loop - it will always return the original state until the synchronization is complete. Using the "3" interface, you can sort of implement your own checkpointing mechanism by syncing smaller groups of changes at a time (and save the state after each batch).