none
Outlook 2010 vb.net - item_change event only triggering after looping through all items RRS feed

  • Question

  • Hi,

    I at a complete loss here and having been searching forums and blogs for the past few days to try and figure this out myself, however I've hit a dead end.  If there is a better suited forum for this, I would be very grateful if you could point me in that direction.

    My problem is regarding an _ItemChange event that I have on the contact items in Outlook 2010.  When I load up the addin I set:

            ContactItems = Application.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderContacts).Items

    I then have two events in my code that are to capture a user adding or editing a contact:

        Private Sub ContactItems_ItemAdd(ByVal Item As Object) Handles ContactItems.ItemAdd
        Private Sub ContactItems_ItemChange(ByVal Item As Object) Handles ContactItems.ItemChange

    The reason I have done this is because I also need the events to fire if someone adds a contact on a mobile device that uses activeSync to sync the contact back to Outlook - I haven't tested this yet so please let me know if this isn't going to work.  Whilst I also need to capture the delete event whilst passing the contact details into a variable before it is deleted.

    The problem occurs when I am adding contacts via code after receiving an update from a CRM platform (to sync any new contacts that have been added there).  I iterate through the contacts I receive and either add them if they don't already exist, update them if they do, or delete them if I have been passed a delete request from the CRM.

    In this particular scenario,  I start from fresh with no contacts in Outlook, so it therefore will loop through adding all the contacts from the CRM and doesn't need to worry about updating or deleting contacts.  Each contact is added via:

        newContact = ContactItems.Add(Outlook.OlItemType.olContactItem)

    I then set the details (name, number, address...etc) and then save this contact.  The ItemAdd event fires which I handle with a flag that tells it the contact is being added via the CRM, and then I loop to the next contact...etc.

    Once all the contacts have been added everything seems fine, however about 10-15 seconds later, the ItemChange event starts firing for every contact item that I have added, which I catch via my writelog debugging and also by the fact that this then freezes up Outlook.  I've obviously already returned all the flags to default (false = non-CRM event) and so it thinks the user is updating contacts and triggers the API function to upload this contact to the CRM.

    Questions:

    - Why does this event fire so much later than when I am actually doing the save events?

    - I've read that MAPI can act asynchronously sometimes, might this be it?

    - How can I handle this if so?

    - When I have tested this on Outlook 2010 but using a Gmail account connected to it, everything is fine... so this suggests that it may be something to do with me using an exchange account now?

    Below is sample code and test setup:

    - Visual studio 2013

    - Outlook 2010 (using test exchange user created on exchange 2010, windows server 2008 R2)

    'If there is data from CRM, deserialise JSON to output structure and loop through either adding or updating contacts
                If returnStr <> "" And returnStr <> "{""output"":0,""list"":[]}" And returnStr <> "{""output"":1,""list"":[]}" Then
                    Try
                        'Deserialise data
                        SyncContacts = JsonConvert.DeserializeObject(Of API_ContactArray)(returnStr)
    
                        If SyncContacts.output = 0 Then 'If output bit implies no errors on the CRM side, continue
                            SyncFlag = True
                            addContactFlag = True
                            For i = LBound(SyncContacts.list) To UBound(SyncContacts.list)
                                searchID = Replace(SyncContacts.list(i).custom_id, "L", "") 'Removes the "L" at the end of contact IDs when searching to allow for non-synced contacts to be updated
                                filter = "@SQL=(" & Chr(34) & "urn:schemas:contacts:customerid" & Chr(34) & " LIKE '%" & searchID & "') AND NOT (" & Chr(34) & "urn:schemas:contacts:customerid" & Chr(34) & " LIKE '%D')"
                                resultItems = ContactItems.Restrict(filter) 'Returns contact if ID already exists
    
                                If SyncContacts.list(i).action = "add" Then
                                    If resultItems.Count > 0 Then 'If contact exists already then update
                                        contact = CType(resultItems(1), Outlook.ContactItem)
                                        If InStr(contact.CustomerID, "U") > 0 Then
                                            contact.CustomerID = Replace(contact.CustomerID, "U", "L")
                                        ElseIf InStr(contact.CustomerID, "E") > 0 Then
                                            contact.CustomerID = Replace(contact.CustomerID, "E", "L")
                                        End If
                                        UpdateContact(contact, SyncContacts.list(i)) 'Function to update contact
                                    Else 'If contact doesn't exist already then add
                                        newContact = ContactItems.Add(Outlook.OlItemType.olContactItem)
                                        newContact.LastName = SyncContacts.list(i).second_name
                                        newContact.FirstName = SyncContacts.list(i).first_name
                                        newContact.Email1Address = SyncContacts.list(i).email
                                        newContact.BusinessTelephoneNumber = SyncContacts.list(i).primary_number
                                        newContact.JobTitle = SyncContacts.list(i).role
                                        newContact.MobileTelephoneNumber = SyncContacts.list(i).secondary_number
                                        newContact.CompanyName = SyncContacts.list(i).company
                                        newContact.CustomerID = SyncContacts.list(i).custom_id
                                        newContact.BusinessAddressStreet = addressSplit(SyncContacts.list(i).address, "Street")
                                        newContact.BusinessAddressCity = addressSplit(SyncContacts.list(i).address, "City")
                                        newContact.BusinessAddressPostalCode = SyncContacts.list(i).postcode
                                        newHTML = expression.Replace(CStr(newContact.BusinessCardLayoutXml), "bgcolor=""ECECEC""") ' Format as grey background to show that it has been sync'd with CRM
                                        newContact.BusinessCardLayoutXml = newHTML
                                        addContactFlag = True
                                        newContact.Save()
                                        addContactFlag = False
                                    End If
                                ElseIf SyncContacts.list(i).action = "delete" Then
                                    If resultItems.Count > 0 Then
                                        contact = CType(resultItems(1), Outlook.ContactItem)
                                        RemoveContact(contact)
                                    End If
                                End If
                            Next
                            SyncFlag = False
                        Else
                            syncSuccess = False
                            WriteLogError(Date.Now.ToString & " Sync not successful, sync output: " & SyncContacts.output)
                        End If
                    Catch ex As Exception
                        WriteLogError(Date.Now.ToString & " - Error trying to add/edit/delete contact details during sync: " & ex.Message & ".  StackTrace: " & ex.StackTrace & ")")
                        syncSuccess = False
                    End Try
                End If

     Private Sub ContactItems_ItemAdd(ByVal Item As Object) Handles ContactItems.ItemAdd
           
            Dim rng As New Random
            WriteLog(Date.Now.ToString & " Add item event 1, " & Item.LastNameAndFirstName & ", ID: " & Item.CustomerID)
    
            If (TypeOf Item Is Outlook.ContactItem) And SyncFlag = False And addContactFlag = False Then
                addContactFlag = True
                WriteLog(Date.Now.ToString & " Add item event 2, " & Item.LastNameAndFirstName & ", ID: " & Item.CustomerID)
                If InStr(Item.CustomerID, "U") = 0 And InStr(Item.CompanyName, "[Private]") = 0 And InStr(Item.CompanyName, "[private]") = 0 Then
                    WriteLog(Date.Now.ToString & " Add item event 3, " & Item.LastNameAndFirstName & ", ID: " & Item.CustomerID)
                        updateContactFlag = True
                    
                    API_AddContact(Item) 'Function to add contact to CRM
                End If
            End If
            GC.Collect()
            'End SyncLock
        End Sub
    
        ' Used to update CRM contacts if Outlook contact is updated
        Private Sub ContactItems_ItemChange(ByVal Item As Object) Handles ContactItems.ItemChange
    
            WriteLog(Date.Now.ToString & " Item change event 1: " & Item.CreationTime & ", " & Item.FirstName & " " & Item.LastName & ", Flags: " & addContactFlag & updateContactFlag & addContactFlag & AdderrorContactFlag & SyncFlag)
    
            'MsgBox("Item change event: " & addContactFlag & " " & updateContactFlag)
            If addContactFlag = False And addContactFlag = False And updateContactFlag = False And AdderrorContactFlag = False And SyncFlag = False Then
                If InStr(Item.CompanyName, "[Private]") = 0 And InStr(Item.CompanyName, "[private]") = 0 Then
                    WriteLog(Date.Now.ToString & " Item change event 2")
                    API_AddContact(Item) 'Function to add contact to CRM
                End If
            Else
                WriteLog(Date.Now.ToString & " Item change event 3")
    
                If addContactFlag = True Then
                    addContactFlag = False
                End If
                If updateContactFlag = True Then
                    updateContactFlag = False
                End If
                If AdderrorContactFlag = True Then
                    AdderrorContactFlag = False
                End If
            End If
            GC.Collect()
            'End SyncLock
    
        End Sub

    Thanks a lot in advance for any help!

    Tom

    Tuesday, April 29, 2014 2:46 PM

Answers

  • Hello Tom,

    Be aware, such events like ItemAdd, do not run when a large number of items are passed.

    - Why does this event fire so much later than when I am actually doing the save events?

    For Exchange server profiles an event notification may delay for an on an arbitrary period. Also another add-ins may do some work in the background.

    - I've read that MAPI can act asynchronously sometimes, might this be it?

    Yes, you can call Extended MAPI methods on the background threads.

    - How can I handle this if so?

    The Outlook object model shouldn't be touched on the background threads. But you can use low-level Extended MAPI functions or any wrappers, for example, like Redemption.

    - When I have tested this on Outlook 2010 but using a Gmail account connected to it, everything is fine... so this suggests that it may be something to do with me using an exchange account now?

    Yes, you are on the right avenue.

    Tuesday, April 29, 2014 3:06 PM
  • Tom,

    > if I somehow added multiple items at once it wouldn't fire?

    Yes, you are on the right avenue. And the issue is related to other events, not only to ItemAdd.

    > read somewhere that the Events on MAPI objects can happen asynchronously

    Extended MAPI provides notifications, not events. And yes, they can be asynchronous.

    > Would this affect anything, or do they just stay set as nothing until GC is run in the background?

    Setting the managed object to null doesn't release an underlying COM object. You need to run the Collect method of the System.GC class to swipe the heap and release all objects.

    > Since whenever I add a new contact it effectively calls the ItemChange event, which I now know can run at some point in the future (not synchronously), do I have to release the Item being passed into it at the end of that ItemChange sub?

    No. You shouldn't release object passed as parameters.

    > - It seems that maybe since I am looping through > 256 contacts and the ItemChange event is being called for each, but not actually executed, I might therefore be hitting the 256 enumeration limit stated in your link (Systematically Releasing Objects)?

    The actual number depends on the Outlook/Exchange version. 256 is for older Outlook versions.

    > My overall problem comes from the fact that the ItemChange event is being executed after I have set my flags = false, so the event doesn't know that it has been originally called inside the sync loop.  

    You can add a user property. Then in the event handler you may check the value and decide what to do depending on the value.

    Tuesday, April 29, 2014 4:11 PM
  • If you are running against a cached Exchange store, Outlook syncs the changes to the server, which may tweak your contacts. Those changes are them downloaded by the local cached store causing the ItemChange events to fire.

    The only workaround is to compare the changes with what you already have to decide whether the changes need to be handled by your code.


    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!

    Tuesday, April 29, 2014 4:24 PM

All replies

  • Hello Tom,

    Be aware, such events like ItemAdd, do not run when a large number of items are passed.

    - Why does this event fire so much later than when I am actually doing the save events?

    For Exchange server profiles an event notification may delay for an on an arbitrary period. Also another add-ins may do some work in the background.

    - I've read that MAPI can act asynchronously sometimes, might this be it?

    Yes, you can call Extended MAPI methods on the background threads.

    - How can I handle this if so?

    The Outlook object model shouldn't be touched on the background threads. But you can use low-level Extended MAPI functions or any wrappers, for example, like Redemption.

    - When I have tested this on Outlook 2010 but using a Gmail account connected to it, everything is fine... so this suggests that it may be something to do with me using an exchange account now?

    Yes, you are on the right avenue.

    Tuesday, April 29, 2014 3:06 PM
  • Tom,

    Also I'd recommend releasing underlying COM objects instantly. Use System.Runtime.InteropServices.Marshal.ReleaseComObject to release an Outlook object when you have finished using it. Then set a variable to Nothing in Visual Basic (null in C#) to release the reference to the object. You can read more about this in the Systematically Releasing Objects article in MSDN.

    The MAPI Multithreading Rules article provides rules for initializing MAPI subsystem in another thread. Microsoft officially doesn't recommend to use Extended MAPI calls from the managed code. But nobody forbids - on your own risk. I.e. it is not officially supported  scenario.

    Tuesday, April 29, 2014 3:13 PM
  • Hi Eugene,

    Thanks for such a quick reply!

    The ItemAdd event I believe fires in my case since I am adding each contact and saving them individually, I can see this in my debug log, so I assume you mean that if I somehow added multiple items at once it wouldn't fire?  Or should it not even be firing in my scenario and something else is triggering it?  Either way, that one is fine and it is just the ItemChange event that is affecting me.

    I actually used to run this on a background thread and manage it via synlocks...etc (also looked at redemption) but decided to try and do it all synchronously since it seemed to not affect the response in outlook, so I won't go back down that path.  What I meant about MAPI being asynchronous was that I think I read somewhere that the Events on MAPI objects can happen asynchronously, which on the basis of you event notification delay answer, I guess is true.

    Regarding using ReleaseComObject, I have read up quite a bit on this and my understanding is that you need to be very careful when using it so that you don't release something that you shouldn't and affect other addins...etc.  At the end of my syncContacts sub (which the code snippet above comes from) I set all the variables to Nothing at the end, but don't use ReleaseComObject.

    - Would this affect anything, or do they just stay set as nothing until GC is run in the background?

    - Since whenever I add a new contact it effectively calls the ItemChange event, which I now know can run at some point in the future (not synchronously), do I have to release the Item being passed into it at the end of that ItemChange sub?

    - It seems that maybe since I am looping through > 256 contacts and the ItemChange event is being called for each, but not actually executed, I might therefore be hitting the 256 enumeration limit stated in your link (Systematically Releasing Objects)?

    My overall problem comes from the fact that the ItemChange event is being executed after I have set my flags = false, so the event doesn't know that it has been originally called inside the sync loop.  One way I could possibly get around this is by setting an unused property of a contact to say 1, and then testing this on the ItemChange event.  If true then I could just change it back to "" and not do anything else.

    - Do you know if there is a neater / more obvious way of getting over this problem?  This might even call the ItemChange event again since I will have to save the contact after changing it back to "".

    Sorry for all the questions... thanks again for your help!

    Tom

    Tuesday, April 29, 2014 3:59 PM
  • Tom,

    > if I somehow added multiple items at once it wouldn't fire?

    Yes, you are on the right avenue. And the issue is related to other events, not only to ItemAdd.

    > read somewhere that the Events on MAPI objects can happen asynchronously

    Extended MAPI provides notifications, not events. And yes, they can be asynchronous.

    > Would this affect anything, or do they just stay set as nothing until GC is run in the background?

    Setting the managed object to null doesn't release an underlying COM object. You need to run the Collect method of the System.GC class to swipe the heap and release all objects.

    > Since whenever I add a new contact it effectively calls the ItemChange event, which I now know can run at some point in the future (not synchronously), do I have to release the Item being passed into it at the end of that ItemChange sub?

    No. You shouldn't release object passed as parameters.

    > - It seems that maybe since I am looping through > 256 contacts and the ItemChange event is being called for each, but not actually executed, I might therefore be hitting the 256 enumeration limit stated in your link (Systematically Releasing Objects)?

    The actual number depends on the Outlook/Exchange version. 256 is for older Outlook versions.

    > My overall problem comes from the fact that the ItemChange event is being executed after I have set my flags = false, so the event doesn't know that it has been originally called inside the sync loop.  

    You can add a user property. Then in the event handler you may check the value and decide what to do depending on the value.

    Tuesday, April 29, 2014 4:11 PM
  • If you are running against a cached Exchange store, Outlook syncs the changes to the server, which may tweak your contacts. Those changes are them downloaded by the local cached store causing the ItemChange events to fire.

    The only workaround is to compare the changes with what you already have to decide whether the changes need to be handled by your code.


    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!

    Tuesday, April 29, 2014 4:24 PM
  • Hi Eugene,

    Thanks again for your feedback.  You confirmed what I thought about not releasing ComObjects that are passed as a parameter, I'm reading up on this (blogs.msdn.com/b/visualstudio/archive/2010/03/01/marshal-releasecomobject-considered-dangerous.aspx) which I got from (stackoverflow.com/questions/22500268/when-to-marshal-releasecomobject-in-office-when-passing-objects-as-a-parameter) that should hopefully provide a bit more guidance on where/when I should use ReleaseComObject.

    I think I will change to adding a user property / editing an unused contact property that I can check the value of.

    - I would need to set this value back to default however during the event handler (to distiguish between user editing/adding vs vb code/activesync from blackberry) and save the contact again to make it stick... wouldn't this call the same ItemChange event again therefore and put me in the same problem since I've just changed this value back?

    Cheers,

    Tom

    (sorry about the links...it still won't let me add them for some reason, even though I have verified my account??)

    Tuesday, April 29, 2014 4:37 PM
  • Hi Dmitry,

    Yes I think this might be very likely, I will have to check the exchange server settings I went with (am very new to setting up exchange test platforms...have only just managed to get a local version working).  Either way, my addin is going to have to function with local cached store, so I need to account for this event also.

    - What do you mean by the server may tweak the contacts?

    - Do you know of any events that I called use rather than calling an _ItemChange event on the variable I created pointing at the contact items?

    It would make things much easier if one existed that executes instantly and synchronously to my code...

    e.g.

    start looping through contacts

    add contact 1

    _ItemAdd event fires (which it does already)

    set contact 1 details

    save contact 1

    _ItemChange event fires (doesn't do this atm, waits until maybe 15-20 secs after syncContacts sub finishes)

    loop back to next contact

    Thanks a lot,

    Tom

    Tuesday, April 29, 2014 4:46 PM
  • There is nothing you can do on the server to fix this. Short of opening a support case with MS and making them fix this behavior.

    Exchange can "tweak" your contact by, for example, adding an extra property to it.

    Again, you should still process these events - what if a contact is changed by somebody who is connected to the same mailbox from another machine?

    You can prevent picking up your own changes in Outlook by (just an idea) concatenating the values of all properties that you set on a contact and calculating a hash (MD5?). You can then store the hash as one of the user properties on a contact.

    When ItemChange event fires, you can read the properties and calculate the hash again. If it is the same as the one you set before, you can ignore the change.


    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!

    Tuesday, April 29, 2014 5:23 PM
  • Hi Dmitry,

    I actually don't need to worry about which user edits it, since the changes always have to be uploaded to the CRM regardless.  All users are actually wanting to have all CRM contacts sync'd with their accounts, so any user who edits any CRM contact, this will be uploaded to the CRM and then subsequently downloaded to everyone else on the next sync event.

    I might implement the hash method you suggest to determine whether anything has changed, since I presume the LastModificationTime property will change between when I'm creating the contact vs when the server is tweaking it / sending it back.  Will test this first actually.

    One question in advance of another task I have to do, does adding a contact on Blackberry that then is sync'd to the outlook contacts fire off the same event?  I haven't set up a BB to test yet, but I basically need this to also fire off the event.

    Thanks,

    Tom

    Tuesday, April 29, 2014 5:55 PM
  • If BB is synchronized to the local store first, you might see the same behavior. If it is synchronized to the server, you most likely not have this problem.


    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.5 is now available!

    Tuesday, April 29, 2014 6:00 PM
  • A quick update... it looks like the MD5 hash tracking trick combined with some flags I was using before is working nicely now need to test a lot more to catch all the different scenarios, but so far so good.

    I can also confirm that the item change event occurring c.20 secs after the event was indeed being caused by the exchange server deciding to update/sync the contacts folder again with what I had just added - spotted down in the bottom right of the screen that it came up saying "updating contacts folder..." or something.

    Thanks for all the help so far, will let you know how I get on with the testing and also the BB integration when I get to it.

    Tom

    Tuesday, April 29, 2014 6:51 PM
  • Tom, You are right, the ItemChange event will be fired each time you change the item and then save it.
    Tuesday, April 29, 2014 8:19 PM