PropertyAccessor in Exchange 2010 – issue and some workarounds

Following code works fine in Exchange 2003 and 2007. But fails in Exchange 2010, that too in Online Mode. There is an interesting discussion that happened recently in MSDN Forums on this topic. Just would like to share summary of it so that it will be helpful to them who are in need of it.

As of now, there is not work around, except changing mode of Exchange to Cache Mode. Some told that Microsoft is aware of this issue and working on it. A fix may be released with the next patches.

const string TEST_PROPERTY = @"http://schemas.microsoft.com/mapi/string/
{00020329-0000-0000-C000-000000000046}/TestProperty/00000003";
 const int TEST_VALUE = 9;
 application = new MSOutlook.ApplicationClass();
 email =
(MSOutlook.MailItem)application.CreateItem(MSOutlook.OlItemType.olMailItem);
 propertyAccessor = email.PropertyAccessor;
 propertyAccessor.SetProperty(TEST_PROPERTY, TEST_VALUE);
 object objValue = propertyAccessor.GetProperty(TEST_PROPERTY);

Here is another work around suggested by Joel…

“it looks like the it’s not completely resolved by simply using extended MAPI.  I was trying to use IMAPIProps::GetProps() and specifying a single property tag with the ID specified, but the type not specified (using PT_UNSPECIFIED).  As already noted, this works fine with Exchange 2007 online or cached mode, Exchange 2010 in cached mode, but not in Exchange 2010 online mode.

The workaround in this case is to specify the correct property type.  But, since the managed PropertyAccessor.GetProperty() method does not know what the property type is ahead of time (at least for custom properties), I guess that could be why it’s failing.  What I’m doing now is calling IMAPIProp::GetPropList() and finding the entry with matching property tag.  Since GetPropList() returns the property type as well, I can now call GetProps() with the correct property type.  This seems to work in Exchange 2010 online mode :)”

I think this is the simplest of all (which is suggested by Susanne Kapp via Wompi)…

For an easy workaround in VSTO, you can simply call the mailItem.Save() method after setting the property. But be aware of all the mails getting saved to your drafts folder if you don´t send the mail.

Hope this helps!

Deploying a COM Addin in 64-bit Office

Deployment alone is a hassle for VSTO Developers, especially beginners. In this, if one needs to deploy add-in in a 64-bit system is hectic. Here is a simple solution that I have used to debug and deploy add-ins in 64-bit Office.

You just need to follow these steps.
By default, the add-in will be registered with the respective office application. This is enough to run the application in debug mode. If we select ‘my add-in should be available to all users…’ option, it registers the add-in in the 32-bit global registry. This can be seen in RegEdit like this.
HKLMSoftwareWow6432NodeMicrosoftOffice\AddIns

But, 64-bit Office cannot see registries in this location. For the add-in to load correctly, the registry keys should be moved to the following location.
HKLMSoftwareMicrosoftOffice\AddIns

For deploying the application…

In Visual Studio 2010, you just need to update the TargetPlatform property to x64 in the Setup Project Properties. Your project is ready to be deployed in 64-bit versions of Office now!

If you are working on Visual Studio 2008, there is a bug in setup project which prevents the add-in from registering 64-bit COM components. A work around is suggested for this problem in Cannot load 64-bit self-registering DLL’s section in Troubleshooting Windows Installer Deployments article in MSDN. It describes how to manually write 64-bit registry keys for making your add-in to work in 64-bit Office.

For more details, go through this article: Deploying COM Add-ins for 64-bit Office using Visual Studio

Key points for updating a deployed office solution

You might have got a requirement to update the existing (deployed) office solution. If so, here are some key points related to signing certificates, that you need to keep in mind before doing this, which are suggested by msdn.

  1. Ensure that the solution is set up to provide the correct evidence for the security checks –
    • If the old solution is signed with a trusted certificate, sign the updated solution with the same certificate. If the certificate has expired or cannot be reused, use a certificate that is trusted at the enterprise level.
    • If the ClickOnce trust prompt was used for trust but the zone has been disabled, the solution will be disabled.
    • If the signing key was changed, trust must be re-established by using the ClickOnce trust prompt, or the certificate must be trusted at the enterprise level. If the trust is pre-established, the first version will be uninstalled and the updated version will be installed.
    • If the new certificate is trusted at the enterprise level, then the update takes place with no prompting.
    • If the new certificate is not trusted, a trust prompt is displayed to the user, if the prompt is allowed.
  2. Use the Publish Wizard to publish the updated Office solution to the same directory as the previous version.
    The Office solution is published to a folder at the same directory level as the old solution, but with an incremented version number. The application and deployment manifests are replaced, so the user is automatically directed to the new version the next time the solution checks for updates.
  3. Copy the solution files to the installation location, if it is different from the publish location. Keep the folders and files in the same relative positions.

Articles: Customizing the Office Ribbon

Just came across the series of articles on Customizing Office Fluent Ribbons for Developers which covers all the customizing options that are available for Developers. Here are their links.

Hope this helps!

Office 2010 Developer Roadmap

Just came across some beautiful videos on Office 2010 Development and would like to share with you.
Introducing Office 2010 – whats new in Office 2010 and its integration with SharePoint
Office 2010 Development Tools – various tools that supports development of office customization and the enhancements in them
Developing Office 2010 Customizations – customizing UI in Office 2010, concepts needed for Office 2010 development on both the server and client
Introduction to Office 2010 Developer Roadmap Module – a brief description of this module
Another noteworthy thing is Development Lab. Go through the link for more details.
Getting Started with Office 2010 Development Lab

Bootstrapper package for Office 2010 PIAs

Microsoft released Bootstrapper Package Installer for Office 2010 PIAs (Primary Interop Assemblies). This installer will add ‘Microsoft Office 2010 Primary Interop Assemblies‘ to the Prerequisites Dialog Box in Visual Studio 2008/2010. This will be useful to deploy Office PIAs with the addin when deployed with ClickOnce or Setup Project.
You can download the Bootstrapper Installer from the following link.
http://www.microsoft.com/downloads/en/details.aspx?FamilyID=5d57c998-b630-4f38-afaa-b79747a3da06&displaylang=en

Creating and Handling CommandBars in multiple Inspectors of Outlook

The window which displays a single item (email/appointment/task) in the Outlook is called as an Inspector in Outlook. It is derived from the namespace Microsoft.Office.Outlook.Inspector. Inspector class has methods  and properties using which we can do modifications to the window and handle the events that are raised.
We can get Inspector object by using one approach from the following –

  • Use the Inspectors property of the Microsoft.Office.Tools.Outlook.Application class to access all of the Inspector objects in Outlook.

(or)

  • Use the GetInspector method of a specific item, such as a Microsoft.Office.Interop.Outlook.MailItem or Microsoft.Office.Interop.Outlook.AppointmentItem, to retrieve the Inspector associated with it.

(or)

  • Use the ActiveInspector method of the Microsoft.Office.Tools.Outlook.Application class to get the Inspector that currently has focus.

Now, lets see how to create a CommandBar with some buttons and handling the events of them.
Following code has been written by Sue Mosher
ThisApplication.vb  (standard module generated for VSTO 2005 Outook add-in project)

Public Class ThisApplication
Dim WithEvents allInsp As Outlook.Inspectors
Public allInspHelper As New InspectorHelperClass
Private Sub ThisApplication_Startup(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Startup
      allInsp = Globals.ThisApplication.Inspectors
End Sub
Private Sub ThisApplication_Shutdown(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Shutdown
      allInspHelper = Nothing
      allInsp = Nothing
End Sub
Private Sub allInsp_NewInspector(ByVal Inspector As Microsoft.Office.Interop.Outlook.Inspector) Handles allInsp.NewInspector
      Dim itm As Object
      Dim id As String
      On Error Resume Next
      ' for the item types you want to have your CommandBar or button
      ' add the Inspector to the wrapper class collection
      itm = Inspector.CurrentItem
      If TypeOf itm Is Outlook.MailItem Then
            id = allInspHelper.AddInsp(Inspector)
      ElseIf TypeOf itm Is Outlook.ContactItem Then
            id = allInspHelper.AddInsp(Inspector)
      End If
      itm = Nothing
End Sub
End Class

InspectorHelperClass.vb

Imports Microsoft.Office.Interop.Outlook
Public Class InspectorHelperClass
 Public Shared InspectorsList As New InspectorWrapperCollection
 Private intID As Integer
 Public Function AddInsp(ByVal Inspector As Outlook.Inspector) As String
 Dim iWrap As New InspectorWrapper()
 Dim itm As Object
 Dim id As String
 On Error Resume Next
 'set the Inspector in the class
 iWrap.Inspector = Inspector
 'test which Outlook item type is here
 itm = Inspector.CurrentItem
 If TypeOf itm Is Outlook.MailItem Then
 iWrap.MailItem = itm
 ElseIf TypeOf itm Is Outlook.ContactItem Then
 iWrap.ContactItem = itm
 End If
 itm = Nothing
 iWrap.Key = intID
 id = CStr(intID)
 'add the class to the collection with a
 ' unique Key value.
 InspectorsList.Add(iWrap, id)
 'create buttons and menus for the Inspector
 iWrap.InitButton()
 AddInsp = id
 intID = intID + 1
 iWrap = Nothing
 itm = Nothing
 End Function
 Public Sub KillInsp(ByVal intID As Integer, ByVal inspWrap As InspectorWrapper)
 Dim inspWrap2 As InspectorWrapper
 On Error Resume Next
 inspWrap2 = InspectorsList.Item(CStr(intID))
 ' check to make sure we're removing the
 ' correct Inspector from the collection.
 If Not inspWrap2 Is inspWrap Then
 Err.Raise(1, Description:="Unexpected Error in KillInsp")
 Else
 InspectorsList.Remove(CStr(intID))
 End If
 inspWrap2 = Nothing
 End Sub
End Class

InspectorWrapper.vb (This code is developed by Ken Slovak)

Imports Microsoft.Office.Core
Imports Word = Microsoft.Office.Interop.Word
Imports Microsoft.Office.Interop.Outlook
Public Class InspectorWrapperCollection
 Inherits DictionaryBase
 Public Overloads Sub Add(ByVal myWrap As InspectorWrapper, ByVal myKey As String)
 MyBase.Dictionary.Add(myKey, myWrap)
 End Sub
 Default Public Property item(ByVal theKey As String) As InspectorWrapper
 Get
 Return CType(MyBase.Dictionary.Item(theKey), InspectorWrapper)
 End Get
 Set(ByVal value As InspectorWrapper)
 MyBase.Dictionary(theKey) = value
 End Set
 End Property
 Public ReadOnly Property keys() As ICollection
 Get
 Return MyBase.Dictionary.Values
 End Get
 End Property
 Public Sub Remove(ByVal theKey As String)
 MyBase.Dictionary.Remove(theKey)
 End Sub
End Class
Public Class InspectorWrapper
 Private WithEvents _thisInsp As Outlook.Inspector
 Private WithEvents _thisMail As Outlook.MailItem
 Private WithEvents _thisContact As Outlook.ContactItem
 Private WithEvents _cbb As Office.CommandBarButton
 Private _obj As Object
 Private _intID As Integer
 Private _tag As String
 Private _hasMailInspector As Boolean
 Private _hasWordEditor As Boolean
 Private Sub _cbb_Click1(ByVal Ctrl As Microsoft.Office.Core.CommandBarButton, ByRef CancelDefault As Boolean) _
 Handles _cbb.Click
 Dim msgText As String
 On Error Resume Next
 'code here to handle a click of the menu/toolbar button
 ' and perform whatever function you want.
 msgText = "You clicked the button on the " & _
 _thisInsp.Caption & " window."
 MessageBox.Show(msgText, "Command Button Click Event")
 End Sub
 Public Sub New()
 On Error Resume Next
 _thisInsp = Nothing
 _thisMail = Nothing
 _thisContact = Nothing
 _cbb = Nothing
 _obj = Nothing
 _hasWordEditor = False
 End Sub
 Protected Overrides Sub Finalize()
 On Error Resume Next
 _thisInsp = Nothing
 _thisMail = Nothing
 _thisContact = Nothing
 _cbb = Nothing
 _obj = Nothing
 End Sub
 Public Function InitButton() As Boolean
 On Error Resume Next
 'if you want buttons only for
 ' certain item types you can test
 ' for that here using
 ' _thisInsp.CurrentItem.Class or TypeOf _thisInsp.CurrentItem
 Call CreateButtons(_thisInsp)
 'you can now enable/disable buttons depending
 ' on what type if item is opened if you want.
 End Function
#Region "InspectorWrapper class properties"
 Public WriteOnly Property MailItem() As Outlook.MailItem
 Set(ByVal value As Outlook.MailItem)
 _thisMail = value
 _hasMailInspector = True
 End Set
 End Property
 Public WriteOnly Property ContactItem() As Outlook.ContactItem
 Set(ByVal value As Outlook.ContactItem)
 _thisContact = value
 _hasMailInspector = False
 End Set
 End Property
 Public Property Inspector() As Outlook.Inspector
 Get
 Inspector = _thisInsp
 End Get
 Set(ByVal value As Outlook.Inspector)
 _thisInsp = value
 End Set
 End Property
 Public Property Key() As Long
 ' key is used to set unique tag for each CommandBarButton
 Get
 Key = _intID
 End Get
 Set(ByVal value As Long)
 _intID = value
 End Set
 End Property
#End Region
#Region "Open and Close event handlers for Outlook object properties"
 Private Sub _thisInsp_Close() Handles _thisInsp.Close
 On Error Resume Next
 Me.KillButtons()
 Globals.ThisApplication.allInspHelper.KillInsp(_intID, Me)
 _thisInsp = Nothing
 End Sub
 Private Sub _thisContact_Close(ByRef Cancel As Boolean) Handles _thisContact.Close
 On Error Resume Next
 'can handle various events for the contact item
 ' in the Inspector like Close and Open.
 If Cancel = False Then
 Me.KillButtons()
 Globals.ThisApplication.allInspHelper.KillInsp(_intID, Me)
 _thisInsp = Nothing
 End If
 End Sub
 Private Sub _thisContact_Open(ByRef Cancel As Boolean) Handles _thisContact.Open
 On Error Resume Next
 'can handle various events for the contact item
 ' in the Inspector like Close and Open.
 End Sub
 Private Sub _thisMail_Open(ByRef Cancel As Boolean) Handles _thisMail.Open
 On Error Resume Next
 'can handle various events for the mail item
 ' in the Inspector like Close and Open.
 End Sub
 Private Sub _thisMail_Close(ByRef Cancel As Boolean) Handles _thisMail.Close
 On Error Resume Next
 'can handle various events for the mail item
 ' in the Inspector like Close and Open.
 If Cancel = False Then
 Me.KillButtons()
 Globals.ThisApplication.allInspHelper.KillInsp(_intID, Me)
 _thisInsp = Nothing
 End If
 End Sub
#End Region
#Region "Private procedures to create and remove toolbars,menus, and buttons in the wrapped Inspector"
 Private Sub CreateButtons(ByVal myInsp As Outlook.Inspector)
 On Error Resume Next
 'Adding a new menu item and a button to the main menu for any Inspector
 ' must take a different approach if using Word as email editor.
 If (myInsp.IsWordMail = True) And _
 (myInsp.EditorType = Outlook.OlEditorType.olEditorWord) Then
 _hasWordEditor = True
 _obj = Nothing
 ' #### ADD CODE HERE TO ADD TOOLBAR TO WORD
 Else
 _hasWordEditor = False
 _obj = myInsp
 Me.CreateToolbar()
 End If
 Err.Clear()
 End Sub
 Private Sub CreateToolbar()
 'add a toolbar to the main Inspector  bar
 ' in the Inspector.
 Dim _tag As String
 Dim myToolBar As Office.CommandBar
 On Error Resume Next
 _tag = "This string is unique to this button" & CStr(_intID)
 'check for the toolbar existing already and do not create
 ' it if does exist. The checking code would set a
 ' Boolean variable named blnToolbarExists.
 myToolBar = _thisInsp.CommandBars("My Bar")
 If myToolBar Is Nothing Then
 myToolBar = _thisInsp.CommandBars.Add(Name:="My Bar", Temporary:=True)
 End If
 ' add the button to the toolbar
 _cbb = myToolBar.Controls.Add(Type:=Office.MsoControlType.msoControlButton, _
 Temporary:=True)
 With _cbb
 .Tag = _tag
 .Caption = "Test Button"
 .TooltipText = "The ToolTip for the button"
 End With
 myToolBar.Visible = True
 myToolBar = Nothing
 End Sub
 Private Sub KillButtons()
 Dim oControl As Office.CommandBarControl
 On Error Resume Next
 ' if you had multiple buttons, you'd need a different tag
 ' property for each one and would delete each one individually
 oControl = _obj.CommandBars.FindControl(Tag:=_tag)
 If Not oControl Is Nothing Then
 oControl.Delete()
 End If
 oControl = Nothing
 End Sub
#End Region
End Class

Code has been adapted from the following sites. You can find more information regarding this there. Keeping it here so that it helps someone who is in need of it.

  1. http://www.outlookcode.com/codedetail.aspx?id=1344
  2. http://www.slovaktech.com/code_samples.htm#InspectorWrapper
  3. http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnhcvb04/html/vb04c10.asp

Exclusive category for VSTO in MSDN Forums

Yay! Happy to know that MSDN Forums now have an exclusive category for Office Development. Might be late yet useful. So, posting it here.

A snap from 'Office For Developers' in MSDN Forums

Microsoft Office for Developers – http://social.msdn.microsoft.com/Forums/en-US/category/officedev

It has separate wing for Outlook, Excel, Word and Access.

In addition to these, there are two other wings.

Saving mail content when a mail is sent from Outlook

To save the content of a mail item when it is sent, here is the procedure.

Private WithEvents itms As Outlook.Items

Private WithEvents SntFldr As Outlook.Folder

SntFldr = Globals.ThisAddIn.Application.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderSentMail)

itms = SntFldr.Items

Private Sub SaveMail() Handles itms.ItemAdd

‘ DO SOMETHING

End Sub

Before, I handled Application.Send Event and checked whether the Item is MailItem or not and then retrieved the content of that. But, with this, we couldn’t get the Date/Time and sending account information. So, opted for the above procedure.

UPDATE: Here is the updated sample code.

Private WithEvents SntFldr As Outlook.Folder
Private WithEvents itms As Outlook.Items
Private Sub ThisAddIn_Startup(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Startup
    SntFldr = Globals.ThisAddIn.Application.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderSentMail)
    itms = SntFldr.Items
End Sub
Private Sub SaveMailAsMsg() Handles itms.ItemAdd
    If TypeOf itms.Item(itms.Count) Is Outlook.MailItem Then
        Dim olMail As Outlook.MailItem = itms.Item(itms.Count)
        MessageBox.Show("Item Added. " & vbCrLf & "Subject: " & olMail.Subject & vbCrLf & "From: " & olMail.SenderEmailAddress)
    End If
End Sub

Hope this helps!

Getting Rules from an exchange account in Outlook

Getting Rules from an exchange account configured in Outlook is pretty easy though many developers who is working on it for the first time may confuse with the approach. I banged my head for 2 days to get the exact function and attributes to get them.

Dim olRules As Outlook.Rules = Globals.ThisAddIn.Application.Session.DefaultStore.GetRules

For Each olRule As Outlook.Rule In olRules
If olRule.Conditions.From.Recipients.Count > 0 Then
‘ CONDITION: FROM EMAIL
Dim exUser As Outlook.ExchangeUser = olRule.Conditions.From.Recipients.Item(1).AddressEntry.GetExchangeUser
Dim FromEmail As String = exUser.PrimarySmtpAddress
MessageBox.Show(“FROM:  ” & FromEmail)
ElseIf olRule.Conditions.SentTo.Enabled Then
‘ CONDITION: TO EMAIL
Dim exUser As Outlook.ExchangeUser = olRule.Conditions.To.Recipients.Item(1).AddressEntry.GetExchangeUser
Dim ToEmail As String = exUser.PrimarySmtpAddress
MessageBox.Show(“TO: ” & ToEmail)
ElseIf olRule.Conditions.Subject.Enabled Then
‘ CONDITION: SUBJECT
MessageBox.Show(“Subject: ” & olRule.Conditions.Subject.Text)
End If
‘ ACTION: MOVE TO FOLDER
If olRule.Actions.Item(1).ActionType = Outlook.OlRuleActionType.olRuleActionMoveToFolder Then
MessageBox.Show(“Move To Folder: ” & olRule.Actions.MoveToFolder.Folder.FolderPath)
End If
Next
Hope this Helps!