none
Contact item object that is passed during an item_change event in Outlook is not receiving data correctly from blackberry via activesync RRS feed

  • Question

  • Hi,

    I have an outlook addin that effectively syncs the users contacts in outlook with an online CRM platform.  On start up I created two events on the contact folder, item_add and item_change.  These work great for capturing any new contacts added or any edits within outlook, and can also capture the add item event when any contacts are added via blackberry and then synced down to outlook.

    However, if I try to edit a contact via blackberry and the edit involves adding information to a field that was previously blank (e.g. email address or phone number), then this is when it starts acting strange.  When activesync tries to sync the changes down, the item_change event fires and I can see the changes on the contact in outlook when in business card view (e.g. the number I added appears or the email address appears), but if I double click and open the contact, there is no email address or number in the respective fields.

    When I check my logs, I can see the item_change event firing and the contact firing it is the correct one, however the contact item that is being passed into the event seems to be the older version of the contact, one without the number or email address.

    The only two things I can think of that might be causing this are:

    1) Activesync is messing up for some reason or the way item_change event is being called as a result is slightly different

    2) The contact item is being passed is not being released or something when it should be

    Below is the code that I am using for the item_change event, I used to directly use the item that is passed in, however I have tried using a variable and setting that to the item, then I can release it later on since I've read it is a big no no to release objects that are passed into things.  The synclock is to stop an separate sync event affecting this, and the flags are also doing similar things.

    In item change event 1, I log some of the details that the contact triggering the event has.  This is where I can see the problem describe above occur.

    Private Sub ContactItems_ItemChange(ByVal Item As Object) Handles ContactItems.ItemChange
            SyncLock CloudSynclock
                Dim userProperty As Outlook.UserProperty = Nothing
                Dim md5Hash As MD5 = MD5.Create()
                Dim rng As New Random
                Dim tempItem As Outlook.ContactItem
    
                tempItem = Item
    
                userProperty = tempItem.UserProperties.Find("HashCheckValue", True)
                WriteLog(Date.Now.ToString & " Item change event 1: " & tempItem.LastNameAndFirstName & ", ID: " & tempItem.GovernmentIDNumber & ", number: " & tempItem.BusinessTelephoneNumber & ", company: " & tempItem.CompanyName & ", Flags: " & addCloudContactFlag & updateContactFlag & addContactFlag & AdderrorContactFlag & CloudSyncFlag)
    
                If Not IsNothing(userProperty) Then
                    WriteLog(Date.Now.ToString & " Item change event 1.1: " & tempItem.LastNameAndFirstName & ", current hash: " & userProperty.Value & ", new hash: " & GetMd5Hash(md5Hash, tempItem))
                    If VerifyMd5Hash(md5Hash, tempItem, userProperty.Value) = False And addCloudContactFlag = False And addContactFlag = False And updateContactFlag = False And AdderrorContactFlag = False And CloudSyncFlag = False Then
                        If (InStr(tempItem.CompanyName, "[Private]") = 0 And InStr(tempItem.CompanyName, "[private]") = 0) And tempItem.Sensitivity = Outlook.OlSensitivity.olNormal Then
                            WriteLog(Date.Now.ToString & " Item change event 2.1: " & userProperty.Value)
                            Cloud_AddContact(tempItem)
                        Else
                            If InStr(tempItem.GovernmentIDNumber, "E") > 0 Then
                                tempItem.GovernmentIDNumber = Replace(tempItem.GovernmentIDNumber, "E", "L")
                            ElseIf InStr(tempItem.GovernmentIDNumber, "L") > 0 Then
                                tempItem.GovernmentIDNumber = Replace(tempItem.GovernmentIDNumber, "L", "P")
                            ElseIf tempItem.GovernmentIDNumber = "" Then
                                tempItem.GovernmentIDNumber = CStr(Format(Now, "yyyyMMddHHmmss") & GenerateDigits(rng, 4) & "P")
                            End If
                        End If
                    Else
                        WriteLog(Date.Now.ToString & " Item change event 3.1: " & userProperty.Value)
    
                        If addContactFlag = True Then
                            addContactFlag = False
                        End If
                        If updateContactFlag = True Then
                            updateContactFlag = False
                        End If
                        If AdderrorContactFlag = True Then
                            AdderrorContactFlag = False
                        End If
                        If addCloudContactFlag = True Then
                            addCloudContactFlag = False
                        End If
                    End If
                Else
                    If addCloudContactFlag = False And addContactFlag = False And updateContactFlag = False And AdderrorContactFlag = False And CloudSyncFlag = False Then
                        If InStr(tempItem.CompanyName, "[Private]") = 0 And InStr(tempItem.CompanyName, "[private]") = 0 And tempItem.Sensitivity = Outlook.OlSensitivity.olNormal Then
                            WriteLog(Date.Now.ToString & " Item change event 2.2")
                            CloudAPI_AddContact(tempItem)
                        Else
                            If InStr(tempItem.GovernmentIDNumber, "E") > 0 Then
                                tempItem.GovernmentIDNumber = Replace(tempItem.GovernmentIDNumber, "E", "L")
                            ElseIf InStr(tempItem.GovernmentIDNumber, "L") > 0 Then
                                tempItem.GovernmentIDNumber = Replace(tempItem.GovernmentIDNumber, "L", "P")
                            ElseIf tempItem.GovernmentIDNumber = "" Then
                                tempItem.GovernmentIDNumber = CStr(Format(Now, "yyyyMMddHHmmss") & GenerateDigits(rng, 4) & "P")
                            End If
                        End If
                    Else
                        WriteLog(Date.Now.ToString & " Item change event 3.2")
    
                        If addContactFlag = True Then
                            addContactFlag = False
                        End If
                        If updateContactFlag = True Then
                            updateContactFlag = False
                        End If
                        If AdderrorContactFlag = True Then
                            AdderrorContactFlag = False
                        End If
                        If addCloudContactFlag = True Then
                            addCloudContactFlag = False
                        End If
                    End If
                End If
    
                If Not IsNothing(tempItem) Then Marshal.ReleaseComObject(tempItem)
    
            End SyncLock
            GC.Collect()
    
        End Sub

    Any help would be amazing as I am a bit stuck here!

    Many thanks,

    Tom

    Tuesday, July 1, 2014 8:39 PM

Answers

All replies

  • Outlook does not see changes made by MAPI until the item is completely released and reopened.

    Do you touch the contact being modified with the Outlook Object prior to 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, July 1, 2014 9:02 PM
  • Hi Dmitry, Thanks for your response on this.. I will have to do some testing to see if I am touching the contact prior to the item change event, it definitely does occur if I have just add the contact via blackberry, so maybe I am not releasing it correctly there. What is the best practice way to make sure it is released when the contact item has been passed in to this add item event? Cheers, Tom
    Wednesday, July 2, 2014 7:30 AM
  • Hello Tom,

    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.

    Wednesday, July 2, 2014 10:22 AM
  • Hi Eugene,

    In my code above I had included releasing the com object (near the bottom), I have also now added setting the variable to nothing, however the problem still persists for some reason.

    For example editing an existing contact on a blackberry and adding an email address (where no email addresses previously existed for the contact) seems to not filter through correctly into the item parsed during item_change event.  In outlook when in business card view the email address suddenly appears on the contact's card, however when double clicking on it, the email address isn't in the email address field.  Similarly in my code, no email value is included in any of the items email fields.

    - Dmitry mentioned that MAPI changes are not seen until the item is completely released and reopened, might this be affecting it even though I am releasing items in my code?

    - How would I capture the item_change event to upload the change to our CRM whilst also allowing for the item to be released first from the MAPI changes?

    Really appreciate any help you can provide!

    Many thanks,

    Tom

    Wednesday, July 2, 2014 4:10 PM
  • Outlook likes to keep the last used contact object even if you completely release it. It is even worse for appointments.

    Did you touch the contact in question in the current Outlook session with the Outlook UI or programmatically prior to the ItemChange event firing for that contact?

    The only workaround I can suggest is either switching to Extended MAPI (C++ or Delphi) or Redemption (any language) - it does not try to be "smart" and its RDOContact object passed to RDOItems.ItemChange event will never cache any old data.


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

    Wednesday, July 2, 2014 4:27 PM
  • Hi Dmitry,

    Yeah I remember looking at redemption before and am now considering giving it a try.  Do you have a vb.net tutorial on your website that goes through the steps of setting it up?  I have managed to reference it as a COM library and have added it via Imports in my code.  What should I do to start the session if my user will already be logged in to outlook 2010 via an exchange account?

    In my addin start up code I got as far as:

    Dim session as RDOSession

    session = CreateObject("Redemption.RDOSession")

    What else do I need to do to assign this session to the already running outlook session?

    Thanks,

    Tom

    Thursday, July 3, 2014 2:08 PM
  • It is as easy as

    session = CreateObject("Redemption.RDOSession")
    session.MAPIOBJECT = Application.Session.MAPIOBJECT

    After that you can use RDOSession.GetDefaultFolder to retrieve the Contacts folder the same way you do it in the Outlook Object Model.


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

    Thursday, July 3, 2014 2:15 PM
  • Yes I just managed to get that far actually... What I have done is:

       

    ' at the start of the thisAddIn class
    
    Friend WithEvents TestContacts As RDOItems 
    
    ******************
    'in ThisApplication_Startup
    
    Dim session As RDOSession
    
            session = CreateObject("Redemption.RDOSession")
            session.Logon()
            session.MAPIOBJECT = application.Session.MAPIOBJECT
            TestContacts = session.GetDefaultFolder(rdoDefaultFolders.olFolderContacts).Items
            ContactItems = Application.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderContacts).Items
    
    'Further down
    
    Private Sub TestContacts_ItemChange(Item As RDOMail) Handles TestContacts.ItemChange
            MsgBox("test contact added")
        End Sub

    What I am intending to do next is effectively move the code from Item_Change in my initial post to sit in this new TestContacts event.  What other things would I need to change in regards to the way I address the contacts being passed in to the events?  Do you think this will solve my initial problem?

    Thanks,

    Tom

    Thursday, July 3, 2014 2:27 PM
  • You don't need the session.Logon() line. Other than that, it looks good.

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

    Thursday, July 3, 2014 2:30 PM
  • Okay will delete that.  In my original code, I create tempItem as a contactItem that I point to the one that triggered the event, can I still do this or should I create this as an RDOContactItem instead?

    I also call a function during the itemChange event: Cloud_AddContact(tempItem), which passes the tempItem and edits the contact.  Within that function should I set the contact variable I use to RDOContactItem rather than Outlook.ContactItem also?

    Cheers,

    Tom

    Thursday, July 3, 2014 2:39 PM
  • You cannot assume that the item passed to your event handler will always be RDOContactItem. You can also have RDODistListItem objects in the Contacts folder.

    You need to actually check the object type.


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

    Thursday, July 3, 2014 2:45 PM
  • Yeah I check the type that it is an RDOContactItem type since I only care about contact items, no distribution lists or anything else.

    I'm making my way converting the code atm changing everything from Outlook.ContactItem to RDOContactItem, I also had a BeforeDelete event that I used to extract information of the contact when a user deletes it (so I can ping the cloud CRM and know which one to delete).

    Is there an equivalent for redemption, would the OnDeleted event pass the contact details?

    Thursday, July 3, 2014 3:01 PM
  • There is ItemRemove event, but it only passes the value of PR_INSTANCE_KEY as the parameter. Since the event is asynchronous, the item is already gone, and PR_INSTANCE_KEY from the contents table is only useful if you cached it beforehand.


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

    Thursday, July 3, 2014 3:24 PM
  • Hi Dmitry,

    I have got Redemption working great now and it has seemed to solve the issues I was having, but am not sure how to include it in my project setup file for deploymet.  I am using VS2013 which has InstallShield LE included, and since this is an Outlook COM addin (2010 upwards) I am creating, I have selected Any CPU when compiling.

    Ideally I would like the user installing it to be able to just double click on the .exe file and everything installs including Redemption, but it seems it's not quite as straightforward as just including the Redemption dlls in the Application files.

    What do I need to include in the setup file to make sure Redemption is working correctly?  I understand that the developer version has a popup box when installing with it, I can't seem to get this coming up at the moment? (We would look to get the paid version once this is all working)

    Thanks, Tom

    Tuesday, July 8, 2014 2:50 PM
  • Tom,

    you can either mark Redemption as a self-registering COM library or you can include the dll as regular files to be copied alongside your dll and use RedemptionLoader (http://www.dimastr.com/redemption/security.htm#redemptionloader) to load Redemption objects - this way the dlls do not need to be registered in the registry at all.


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

    Tuesday, July 8, 2014 3:06 PM
  • Thanks.  I have imported the RedemptionLoader and ProfManLoader classes into my project, do I need both of these?

    The ProfManLoader gives two errors saying "Profiles is not defined" and "PropertyBag is not defined".  Do I need to include something else that defines these?

    Thanks again

    Tuesday, July 8, 2014 3:39 PM
  • You do not need ProfManLoader unless you are actually using ProfMan (which is only included with the distributable version of Redemption).

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

    Tuesday, July 8, 2014 3:45 PM
  • Yeah I thought maybe I didn't need that.  I am getting an exception being thrown on the RedemptionLoader module when trying to run it on the line:

                        _dllGetClassObjectPtr = Win32NativeMethods.GetProcAddress(_redemptionDllHandle, "DllGetClassObject")

    It says:

    An exception of type 'System.ComponentModel.Win32Exception' occurred in MyAddin.dll but was not handled in user code

    Additional information: The specified module could not be found

    Do you know what might be causing this?  I had a quick google but couldn't find anything specific, and am wary about playing around too much in my code...

    Tuesday, July 8, 2014 3:48 PM
  • Does _redemptionDllHandle have a value? Does dll exist at the specified location?

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

    Tuesday, July 8, 2014 4:07 PM
  • Got it!  I had to hard code the locations of the two dlls in the code as the default didn't seem to point where my developer code was running from.

    In the release version, if I am loading the dlls from the installation directory (e.g. C:\Program Files (x86)\MyInstallationDir\) at runtime for each user, I'm not sure what to put in the VB code to refer to this location?  Since in the registry I have the manifest entry specifying:

    file:///[INSTALLDIR]MyAddin.vsto|vstolocal

    I guess I could add another entry that points to the installation folder which I could read from to get the location of the redemption dlls?  Or is there a better way you could suggest?

    Really appreciate all this help btw, a bit out my depth here so having to learn as I go along...


    • Edited by moatak787 Tuesday, July 8, 2014 4:42 PM
    Tuesday, July 8, 2014 4:42 PM
  • All you need to do is make sure the dlls are copied to the same folder as your executable and leave the DllLocation32Bit and DllLocation64Bit properties empty - RedemptionLoader will use the executing assembly's location to figure out where the dlls need to be loaded from.


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

    Tuesday, July 8, 2014 5:04 PM
  • The executable will likely be run from a usb drive or download folder, and so it won't be where the dlls are ultimately saved on the users pc.

    I have used the registry to store the installDir of the vsto file which is always loaded on outlook startup, and I can use this to load the dlls from that same folder.

    One thing for you to note is that on my test machine I didn't get any popups saying that it was a trial edition of redemption...etc, the user was able to install my addin via .exe file and then run outlook normally and use the program.  The message did pop up when I was compiling in VS using the InstallShield setup project, however that was the only time.

    I will do some more testing over the next few days and then will most likely be heading over to buy the full version of redemption since this has definitely solved a lot of my problems! :-)

    Cheers, Tom

    Tuesday, July 8, 2014 5:29 PM
  • I am not sure I understand - do you not have an installer that ultimately copies your dll and all the related files (which include Redemption.dll and Redemption64.dll) to some folder? As long as both your dll and Redemption are in the same folder, RedemptionLoader will pick up the right location to load the dlls.

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

    Tuesday, July 8, 2014 5:47 PM
  • Yeah I have an installer (.exe) that does that, but I thought you said the RedemptionLoader picks up the location from the folder where the executable sits, as in the installation executable.  Since the installation executable location and the installation location of my addin vsto and dll files will be different, I thought I would have to find another way that what you suggest above.
    Tuesday, July 8, 2014 6:01 PM
  • No, I mean the actual executing assembly that invokes RedemptiomnLoader. here is what it does:

    static RedemptionLoader()
            {
                //default locations of the dlls
                //use CodeBase instead of Location because of Shadow Copy.
                string codebase = Assembly.GetExecutingAssembly().CodeBase;
                var vUri = new UriBuilder(codebase);
                string vPath = Uri.UnescapeDataString(vUri.Path + vUri.Fragment);
                string directory = Path.GetDirectoryName(vPath);
                if (!string.IsNullOrEmpty(vUri.Host)) directory = @"\\" + vUri.Host + directory;
                DllLocation64Bit = Path.Combine(directory, "redemption64.dll");
                DllLocation32Bit = Path.Combine(directory, "redemption.dll");
            }
    


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

    Tuesday, July 8, 2014 6:07 PM