How to solve synchronization conflict when modifying Outlook item in ItemAdd event
Environment. I see this issue in the Sent Items folder on an Exchange account having Cached Exchange Mode (see e.g. here) turned on. Reportedly, turning the Cached Mode off solves the issue. The Outlook version used (Outlook 365 in my case) seems to be irrelevant as Google reports a similar conflict to occur in Outlook 2007.
Issue. In the ItemSend event of the Sent Items folder, your code adds a UserProperty to the Outlook item that the event handler supplies (and saves the item). To detect the issue easily, I suggest that your code also change the MailItem.Category property before saving the item. The issue shows itself as follows: after the add-in handles the event, the category is visible in the Explorer view for several seconds; then the category mark as well as the UserProperty disappear. During that time, mail items are generated in Sync Issues and Sync Issues | Conflicts folders.
Solution. Do not use the ItemAdd event to modify the item in this case. Instead, add the UserProperty while sending the email – in the ItemSend event. This solution may be a limited one; see below.
Background
Adding a UserProperty to an Outlook item in the ItemAdd event is a usual practice. At docs.microsoft.com, Microsoft demonstrates doing this in the Contacts folder. Getting any issue in that event isn’t expected.
COM objects should be released
Outlook is known for a great number of misbehavior scenarios starting with your code leaving a COM object unreleased. That means that the first step to solving any issue with your code using the Outlook Object Model (OOM) is to make sure you release all COM objects your code creates. I wrote several times on this:
- If a call to an Outlook (actually, any Office application!) property/method returns an object, that object is a COM object and you should release it.
- Don’t chain COM calls such as {Application reference}.ActiveExplorer().Selection.Item(1).Subject. Instead, split that code line so that every COM call in this chain returns a value to a separate variable, use the variables and release them as soon as possible.
- Don’t use foreach loops on COM collections; use for loops instead.
- COM objects received in events should be released; Add-in Express developers must not do this because Add-in Express manages these COM objects for them.
Check for background information on how .NET manages COM objects in Why doesn't Excel quit.
Collecting information
I’ve decided to study the additions and changes to the items in system folders. Add-in Express provides a handy class to let you process events of the Outlook.Items collection. Here’s how I create instances of that class:
OutlookItemsEventsClass1 eventsConflicts; OutlookItemsEventsClass1 eventsDeletedItems; OutlookItemsEventsClass1 eventsLocalFailures; OutlookItemsEventsClass1 eventsSentMail; OutlookItemsEventsClass1 eventsServerFailures; OutlookItemsEventsClass1 eventsSyncIssues; private void AddinModule_AddinInitialize(object sender, EventArgs e) { eventsConflicts = new OutlookItemsEventsClass1(this); eventsConflicts.ConnectTo(ADXOlDefaultFolders.olFolderConflicts, true); eventsDeletedItems = new OutlookItemsEventsClass1(this); eventsDeletedItems.ConnectTo(ADXOlDefaultFolders.olFolderDeletedItems, true); eventsLocalFailures = new OutlookItemsEventsClass1(this); eventsLocalFailures.ConnectTo(ADXOlDefaultFolders.olFolderLocalFailures, true); eventsSentMail = new OutlookItemsEventsClass1(this); eventsSentMail.ConnectTo(ADXOlDefaultFolders.olFolderSentMail, true); eventsServerFailures = new OutlookItemsEventsClass1(this); eventsServerFailures.ConnectTo(ADXOlDefaultFolders.olFolderServerFailures, true); eventsSyncIssues = new OutlookItemsEventsClass1(this); eventsSyncIssues.ConnectTo(ADXOlDefaultFolders.olFolderSyncIssues, true); }
In methods handling the ItemAdd and ItemChange events (not shown here), I only write debug messages (see below). When examining the log that I got, pay attention to the time, event, folder, subject, the number of conflicts, the number of UserProperty objects.
5:43:22 PM --- ItemChange in Sent Items
5:43:22 PM Subject="Subject string"
5:43:22 PM Conflicts.Count=0
5:43:22 PM Categories="Category string"
5:43:22 PM UserProperties.Count=1
5:43:22 PM ---
5:43:23 PM --- ItemAdd in Sync Issues\Conflicts
5:43:23 PM Subject="Subject string"
5:43:23 PM Conflicts.Count=1
5:43:23 PM Categories="Category string"
5:43:23 PM UserProperties.Count=1
5:43:23 PM ---
5:43:23 PM --- ItemChange in Sent Items
5:43:23 PM Subject="Subject string"
5:43:23 PM Conflicts.Count=0
5:43:23 PM Categories="Category string"
5:43:23 PM UserProperties.Count=1
5:43:23 PM ---
5:43:23 PM --- ItemChange in Sent Items
5:43:23 PM Subject="Subject string"
5:43:23 PM Conflicts.Count=0
5:43:23 PM Categories="Category string"
5:43:23 PM UserProperties.Count=1
5:43:23 PM ---
5:43:24 PM --- ItemChange in Sync Issues\Conflicts
5:43:24 PM Subject="Subject string"
5:43:24 PM Conflicts.Count=1
5:43:24 PM Categories="Category string"
5:43:24 PM UserProperties.Count=1
5:43:24 PM ----
5:43:24 PM ---- ItemAdd in Sync Issues
5:43:24 PM Subject="Modification Resolution"
5:43:24 PM Conflicts.Count=0
5:43:24 PM Categories=
5:43:24 PM UserProperties.Count=0
5:43:24 PM ---
5:43:24 PM --- ItemChange in Sent Items
5:43:24 PM Subject="Subject string"
5:43:24 PM Conflicts.Count=0
5:43:24 PM Categories="Category string"
5:43:24 PM UserProperties.Count=1
5:43:24 PM ---
5:43:37 PM --- ItemChange in Sent Items
5:43:37 PM Subject="Subject string"
5:43:37 PM Conflicts.Count=0
5:43:37 PM Categories="Category string"
5:43:37 PM UserProperties.Count=1
5:43:37 PM ---
The first thing you’ll notice is the number of ItemChange events. My test add-in directly caused only the first one: it occurred when the test add-in saved the modified item. All other events are produced by Exchange; you can find an explanation on the Outlook for Developers forum.
After studying Conflicts.Count, you’ll find that, according to the log above, the item is left in this state: there’re no conflicts, the category string is there, the UserProperty is there, too. This contradicts to what you’ll see if you test this scenario: in the Outlook UI, the item’s category is missing and checking the UserProperty programmatically shows UserProperties.Count equal to zero.
That is, although you can see that the category disappears right when you look at it, the ItemChange event doesn’t confirm this.
Here I’ve noticed the item Subject=”Modification Resolution” created in the Sync Issues folder. That item is the clue to solving the issue.
Modification resolution log
The Modification Resolution item contains the log below:
17:43:23 Message class: {SU:IPM.Note}
17:43:23 Mail Conflict Resolution
17:43:23 Local subject: {SU:Subject string}
17:43:23 Remote subject: {SU:Subject string}
17:43:23 Local Message Entry ID: {CB:70, LPB:%EntryId-like string here%}
17:43:23 Remote Message Entry ID: {CB:70, LPB: %EntryId-like string here%
17:43:23 Local Message ChgKey: {CB:20, LPB:%omitted%}
17:43:23 Remote Message ChgKey: {CB:22, LPB:%omitted%}
17:43:23 Local Message PCL: {CB:44, LPB: %EntryId-like string here%}
17:43:23 Remote Message PCL: {CB:23, LPB:%omitted%}
17:43:23 Checking local modifications
17:43:23 Ignore property: 0x3FFA001F
17:43:23 Merge named property: Keywords
17:43:23 Compare named property: 0x85400102
17:43:23 Compare named property: %UserProperty name here%
17:43:23 Getting remote properties
17:43:23 Checking remote modifications
17:43:23 MVString (using local) named property: Keywords
17:43:23 Compare (conflict) named property: 0x85400102
17:43:23 Local: {CB:66, LPB: %EntryId-like string here%}
17:43:23 Remote: {Error (0x8004010F)}
17:43:23 Not equal (conflict) named property: 0x85400102
17:43:23 Local modification: {14:43:21.0095 17/02/2020 [DD/MM/YYYY]}
17:43:23 Remote modification: {14:43:23.0148 17/02/2020 [DD/MM/YYYY]}
17:43:23 Conflict generated, remote item is winner
Studying the modification resolution log
The property 0x3FFA001F that they ignore is PidTagLastModifierName; see here.
The Keywords named MAPI property above is the string that you access via the Category property in OOM; see here.
The conflict occurs when comparing the property 0x85400102 on the local and remote items. This property is a mystery. It is only mentioned in several modification resolution logs published on the web. One of these logs is created in Outlook 2007 (in German). To proceed, you should know that the hexadecimal value of a MAPI property has two parts: the ID and type. Googling for 0x8540 helps: SearchCode.com reports that StdAfx.h C/C++ header file – part of MFCMAPI (github) – contains this line: #define dispidPropDefStream 0x8540. Googling for dispidPropDefStream points to PidLidPropertyDefinitionStream which is here.
Searching a little further supplies the missing context and information: the 0x85400102 named property stores the stream of the UserProperties collection.
This makes sense: the log appears to tell us that the conflict occurred when comparing these streams on the local and remote items. The local item is okay: we know that the code added a UserProperty to it, and the stream is initialized. The remote is not okay: getting the 0x85400102 property produces the 0x8004010F = MAPI_E_NOT_FOUND exception.
Getting a working solution
So, on Cached Exchange we are trying to add a UserProperty to the item supplied by the ItemAdd event of the Sent Items folder. The section above shows that doing this creates a conflict when Outlook compares the local item with the remote copy and finds the UserProperties stream not initialized on the remote – not a surprise since only the local copy was changed by your code. Thus, the local and remote items are different, Outlook solves the conflict: it declares that the remote item is correct and replaces the local with a copy of the remote. I read somewhere and the log above confirms that: Outlook saves a copy of the modified local item to Sync Issues | Conflicts so that you could use it for your needs.
On a side note. I haven’t found a simple way to use that copy: in my case, it was required to show the category string to the user and the UserProperty must be on the item when it’s selected.
Again, the local item has a UserProperty on it while the remote item doesn’t have even the stream to hold UserProperties.
Well, what about creating that UserProperty when sending an email?
I admit that the idea made me uneasy: it contradicts to what I know about sending UserProperties. It suffices to say: never in my life I have recommended anyone to send UserPorperties out. There are stories that start with sending out an Outlook item with a UserProperty on it. Typically, these UserProperties appeared missing on the receiving end (stackoverflow.com). Sometimes, they produced a strange attachment named winmail.dat (find a solution on social.msdn.microsoft.com). Also, it is good to know that in older Outlook versions, UserProperties were transferred differently (support.microsoft.com).
Obviously, this issue is an exception in my practice. So, I’ve tried sending an email with a UserProperty and it works for me: the email added to Sent Items has that UserProperty on it.
Since this is limited to Cached Exchange, below is a service method. It requires referencing an interop for Outlook 2010 or above; _MailItem being MailItem minus events :).
bool IsCachedExchange(Outlook._MailItem mailItem) { bool result = false; Outlook.Account account = mailItem.SendUsingAccount; Outlook.Store store = account.DeliveryStore; result = store.IsCachedExchange; Marshal.ReleaseComObject(store); Marshal.ReleaseComObject(account); return result; }
Here’s the same method in VB.NET:
Function IsCachedExchange(mailItem As Outlook._MailItem) As Boolean Dim result As Boolean = False Dim account As Outlook.Account = mailItem.SendUsingAccount Dim store As Outlook.Store = account.DeliveryStore result = store.IsCachedExchange Marshal.ReleaseComObject(store) Marshal.ReleaseComObject(account) Return result End Function
Good luck!
6 Comments
Great Job Andrei!
This described exactly what was happening to my customers. Your solution fixes one problem, the missing stream property, but I still have to modify the mail item in the initial ItemAdd event of the SentItemsFolder to indicate if the user saved the email or not. This happens very quickly as you might imagine. As soon as the “conflict” occurs my local changes are lost! (An the user is prompted a second time to save). Funny, they don’t like that…
The big question is WHY does the first sync conflict ignore the local changes and make the remote win? This seems like a bug to me. Based on the logs, it is because the date the remote item was modified is later than my local modification, but again why? Shouldn’t the remote item start with the SAME modified date as the original? I suppose Microsoft must be making some sort of modifications to the remote item that we are unable to detect. I have checked the ItemProperties both before and after and the only changes are the dates, with the remote (seemingly unmodified) modified date being later than my date.
At this present time, I am considering saving off the EntryID of the item, and the second time I see it, just changing the ItemProperty again and be done with it. But that sure seems like a hack to me.
Once again, thanks for the article. You saved me tons of time. I love your products and especially the Add-In Express team!
Keep it up!
Mark
Thank you, Mark.
> Funny, they don’t like that…
Strange people, I suppose. :)
> The big question is WHY does the first sync conflict ignore the local changes and make the remote win?
I suppose this is a design choice, as I don’t see a way to find out which of the two copies is better: you only have properties and their values.
I would look for a way to use the item in Sync Issues | Conflicts. Or, maybe, it is possible to do the save after a while (1 minute may be okay I suppose; look in a VERY lengthy topic at https://www.add-in-express.com/forum/read.php?SHOWALL_1=1&FID=5&TID=15644#nav_start).
Hey I had the exact same issue, this article was a great help!
– Waiting one minute was not an Option in my case
First approach: (Failed) – replacing the Items after the Conflicts-Folder raises the new-Item-Event.
My solution is after the “ItemAdd”-Event in olFolderSentItems – when i normally want to apply my Userproperties, i dont do it if its a Exchange-Online-Server. Instead I use the
Outlook.Mailitem.Copy() on the new Item and thats it – I apply the Userproperties on the Copy. I dont have to care for the original item anymore.
I then apply my changes to that copy, the Exchange-Online-Server does not care about that copy and leaves it alone.
I have created some potential follow-Up issues here – first is, there are now two items. But that is an easy Task to solve with multiple solutions.
But also maybe a User does not have write access to olFolderSentItems on a customer client, but this is an issue of configuration by the local It-Department to solve. Iam pretty sure it can be differentiated in Outlook between active-User rights and an Add-In that wants to add an Item to a folder, where the User might not have direct accsess.
Hello Henry,
The idea is great!
Having an item created after the first email may trigger Outlook’s warning: “You are not responding to the latest message in this conversation”.
But the main fear is the copy: open it, open another sent email and make sure the Ribbon controls in these emails are the same. The final test would be to Click Reply on a sent email on on that copy: do you get two reply email inspectors?
Thanks for your test-Suggestions Andreij!
On Outlook-Client-2019-Version
I have tested conversations from a lokal Exchange-Server to an Exchange-Online-Server via new and ongoing conversations, i did the “Reply” – Button from Inspector-View and the Explorer-Draft-Editor-View (or what to call it) i did not get two Inspectors or the infamous “you are not replying to the latest message in the conversation” – Message.
Just in case someone wants to try this:
This copy-and-delete chain of events, will take some time to figure out – so it might not be worth it for everybody. In my case it is.
I do programmatically delete the “original” mailItem from “SentItemsFolder” after the copy() – call and also monitor the “DeletedItemsFolder.items.ItemAdd-Event” so I can: “delete-the-delted” item from it – fast – so the user will not be confused.
Many thanks, Henry!