Building Outlook 2002 Add-ins with Visual Basic .NET

This content is no longer actively maintained. It is provided as is, for anyone who may still be using these technologies, with no warranties or claims of accuracy with regard to the most recent product version or service release.

 

Randy Byrne
Micro Eye, Inc.

October 2002

Applies to:
    Microsoft® Visual Basic® .NET
    Microsoft Outlook® 2002

Summary: Learn how to use Visual Basic .NET to develop COM add-ins for Outlook 2002. The sample Visual Basic .NET Outlook COM add-in resolves Contact mailing addresses with an XML Web service. (28 printed pages)

Download odc_oladdinvbnet.exe.

Contents

Introduction
Support for Add-ins in Visual Studio .NET
Using the Office XP Primary Interop Assembly
The OutlookCOMAddinVBNet Sample Application
Coding Fundamentals for a Visual Basic. NET Outlook COM Add-in
Working with a Setup Project
Conclusion

Introduction

Add-ins let developers extend Microsoft® Outlook® 2002 beyond its native capabilities. For Microsoft Office developers, add-ins are commonly known as COM add-ins since they depend on the Component Object Model (COM) to operate in-process with Outlook. You might wonder how a managed code assembly can interoperate with Outlook, which remains unmanaged code dependent on COM. The solution is to install the Microsoft Office XP Primary Interop Assemblies (PIAs) to ensure interoperability. When you deploy your setup package, you should also package the relevant PIA. You will learn how to code an Outlook add-in using Microsoft Visual Basic® .NET. The Visual Basic .NET sample add-in illustrates best-practice coding techniques that you can use in your own solutions. The sample application is a well-behaved Outlook COM add-in that consumes an XML Web service. For non-commercial, private use only, this XML Web service allows you to validate ContactItem mailing addresses against the U.S. Postal Service database.

It's assumed that the reader:

  • Knows what a COM add-in is, and how to build, deploy and install one. For more information about COM add-ins, take a look at What is a COM Add-in?
  • Is familiar with the Outlook 2002 object model.
  • Understands XML Web services and knows how to add a Web reference to a Microsoft Visual Studio® .NET project.
  • Has at least a beginning knowledge of .NET and COM interop.

Support for Add-ins in Visual Studio .NET

Visual Studio .NET has built-in support for Office add-ins. You can create an Office add-in by following these steps:

  1. Press CTRL+SHIFT+N to display the New Project dialog box.
  2. Expand Other Projects in the Project Types tree view, then select Extensibility Projects.
  3. Double-click Shared Add-in in the Templates list view.
  4. The Visual Studio Add-in Wizard will launch and prompt you for information that it uses to create the Connect class and registry settings for your add-in.

The Visual Studio Add-in Wizard, however, omits several blocks of code that are essential for an add-in to work properly in Outlook 2002. At present, there are also problems that relate to the interop assembly generated by Visual Studio .NET when you set a reference to the Outlook Object Library. An interop assembly contains metadata that helps your managed code to communicate with a COM-based type library, such as the Microsoft Outlook 10.0 Object Library. The interop assembly created by Visual Studio .NET is known as a TLBIMP-generated interop assembly. TLBIMP is invoked when you add a reference to your Visual Basic .NET project and select object libraries from the COM page of the References dialog box shown in Figure 1. For example, let's assume that you choose to add a reference to the Microsoft Outlook 10.0 Object Library. Once you add a reference to the Microsoft Outlook 10.0 Object Library to your project, a file named Outlook.Interop.dll will be created in the bin folder of your project.

Aa155703.odc_oladdinvbnet01(en-us,office.10).gif

Figure 1. The Add Reference dialog box

When you use the Visual Studio Add-in Wizard to create an Outlook add-in, and you have not installed the Office XP Primary Interop Assembly, you will discover the following:

  • The Add-in Wizard does not create a reference to the Outlook object library. You must create the reference manually.
  • You cannot sink Outlook events if you have set a reference to Outlook.
  • Visual Basic .NET IntelliSense® will show both the native Outlook classes such as Explorer and Inspector and the .NET coclass interface such as ExplorerClass and InspectorClass.
  • Item is not the default property on collection objects, so you won't be able to use syntax like For Each objMessage In colMessages.
  • The add-in could cause Outlook to remain in memory after the user closes Outlook.

For additional details, and for instructions on fixing the TLBIMP-generated interop assembly, see the following Microsoft Knowledge Base articles:

Using the Office XP Primary Interop Assembly

To solve some of the problems you might experience with a TLBIMP-generated Interop Assembly, download the Office XP Primary Interop Assemblies (PIAs). When you install the Office XP Primary Interop Assemblies, you no longer have to fix the TLBIMP-generated interop assembly. Instead of creating a separate interop assembly in the bin folder for each project that references an Office XP type library, the primary interop assembly resides in the global assembly cache (GAC). Think of a PIA as an Interop Assembly that is given a special status because the PIA has been digitally signed by the publisher of the original COM component. In this case, the publisher of the Outlook Object Library is Microsoft. Figure 2 shows the Microsoft.Office.Interop.Outlook PIA as it appears in the GAC.

Aa155703.odc_oladdinvbnet02(en-us,office.10).gif

Figure 2. The global assembly cache

Before you run the sample code that accompanies this article, you must install the Office XP PIAs on your development computer. If you want to distribute your solution in a setup package, you must add Microsoft.Office.Interop.Outlook.dll to the setup project.

To Install the Office XP PIAs

  1. Extract the Office XP PIAs to a directory on a computer with Visual Studio .NET installed.

  2. Click (All) Programs on the Start menu, point to Microsoft Visual Studio .NET, point to Visual Studio .NET Tools, and click Visual Studio .NET Command Prompt. The Visual Studio .NET Command Prompt window appears. This facilitates the use of the .NET Framework SDK tools.

  3. In the Visual Studio .NET Command Prompt window, use the cd (change directory) command to change to the file directory in which the Office XP PIAs were extracted. For example, if you extracted the Office XP PIAs to a C:\Office XP PIAs\ folder, type

    cd C:\Office XP PIAs\
    

    and press ENTER.

  4. To install all of the Office XP PIAs at the same time, type

    register.bat
    

    and press ENTER.

To Use the Outlook 2002 PIA in Your Solution

Assuming that you have already built your Outlook add-in project and added a Setup project to your solution, add the Outlook 2002 PIA to the Global Assembly Cache output folder. The PIAs and .reg files that ship with the Office XP Primary Interop Assembly are redistributable.

  1. In the Solution Explorer window, right-click your Setup project, point to View, and click File System. The File System panes appear.
  2. Right-click File System on Target Machine, point to Add Special Folder, and click Global Assembly Cache Folder. The Global Assembly Cache Folder is added to the File System on Target Machine folder list.
  3. In the Solution Explorer window, right-click Microsoft.Office.Interop.Outlook.dll under the name of your Setup project, and click Properties. The Property window appears.
  4. In the Property window, in the Folder box, click the ellipsis (. . .) button. The Select Folder dialog box appears.
  5. Click Global Assembly Cache Folder, and click OK to close the Select Folder dialog box.

Next, add the Outlook 2002 PIA registry entries to the Registry on Target Machine pane.

  1. In the Solution Explorer window, right-click your Setup project, point to View, and click Registry. The Registry on Target Machine panes appear.
  2. Right-click Registry on Target Machine, and click Import. The Import Registry File dialog box appears.
  3. Locate and click the Microsoft.Office.Interop.Outlook.dll.reg file in the folder in which you extracted the original Office XP PIAs, and then click Open. The Outlook 2002 PIA registry entries are added, and the Import Registry File dialog box disappears.

The OutlookCOMAddinVBNet Sample Application

The sample application gives you a working example of an Outlook COM Add-in created with Visual Basic .NET. Before you start to look at the sample code, you need to download, install and then test the Outlook COM Add-in for VB.NET.

To Download the Sample OutlookCOMAddinVBNET Solution

  1. Download odc_oladdinvbnet.exe from the link at the beginning of this article
  2. Extract OutlookCOMAddinVBNET.exe to the Visual Studio Projects folder under your My Documents folder. Use the folder names stored in the self-extracting file when you extract files in OutlookCOMAddinVBNET.exe. You should now have a folder named OutlookCOMAddinVBNET folder under the Visual Studio Projects folder.

The OutlookCOMAddinVBNET folder contains the sample managed COM add-in Microsoft Visual Basic .NET project. The name of the COM add-in assembly is OutlookCOMAddinVBNET. This project will be used to demonstrate how to code an Outlook COM add-in using Visual Basic. NET.

  • To view this project, open OutlookCOMAddinVBNET.sln located in . . .\My Documents\Visual Studio Projects\OutlookCOMAddinVBNET\.
  • This folder also contains a subfolder with the name OutlookCOMAddinVBNETSetup that contains the managed OutlookCOMAddinVBNET setup project.

Installing the Sample OutlookCOMAddinVBNET Solution

Note   Before installing the managed OutlookCOMAddinVBNET COM add-in DLL, close Microsoft Outlook.

To install the COM add-in, do the following:

  1. To open the OutlookCOMAddinVBNET project in Visual Studio .NET, open the OutlookCOMAddinVBNET.sln file located at . . .\My Documents\Visual Studio Projects\OutlookCOMAddinVBNET\.
  2. You should find two projects in the Solution Explorer of the Visual Studio .NET IDE: OutlookCOMAddinVBNET and OutlookCOMAddinVBNETSetup.
  3. First, rebuild the managed COM add-in by right-clicking on the OutlookCOMAddinVBNET project and then clicking Rebuild.
  4. Next, build the managed COM add-in setup project by right-clicking on the OutlookCOMAddinVBNETSetup project and then clicking Build.
  5. You can now install the COM add-in by right-clicking on the OutlookCOMAddinVBNETSetup project and then clicking Install. This will launch the Outlook COM Add-in for VB.NET setup wizard.
  6. Click Next.
  7. In the Select Installation Folder dialog box, accept the default or supply a different installation folder.
  8. Click Next.
  9. Click Next to install Outlook COM Add-in for VB.NET.

Using Outlook COM Add-in for Visual Basic .NET

After you have installed the sample COM add-in, launch Outlook. You should see a Visual Basic .NET command bar button on the Standard toolbar of the Outlook Explorer Window. The Explorer command bar button displays an About Outlook COM Add-in for VB.NET dialog box. Provided that you have a working Internet connection, you will also be able to resolve postal addresses for Contact items in a Contacts folder. The add-in creates a Resolve Zip command button (shown in Figure 3). When you click the Resolve Zip button for an Outlook Contact item, the postal address is resolved against a U.S. Postal Service database by an XML Web service. Finally, the add-in prevents you from sending messages with a blank subject. Coding this functionality into an Outlook COM add-in is explained below.

Aa155703.odc_oladdinvbnet03(en-us,office.10).gif

Figure 3. Resolve Zip command bar button

Coding Fundamentals for a Visual Basic. NET Outlook COM Add-in

The following sections discuss many of the coding fundamentals that are recommended when you create an add-in for Outlook. Some of these elements are required while others are optional and reflect the preference of the author. Although not all the code in the sample application will be discussed in this article, there are some code elements that are essential to building a well-behaved Outlook add-in. For example, you must implement the Extensibility.IDTExtensibility2 interface in your add-in Connect class. If you don't implement this interface, then Outlook will not be able to load your managed code add-in. You also must use coding techniques that are specific to Outlook to ensure that your add-in does not cause Outlook.exe to remain in memory when a user closes Outlook. Unlike add-ins created with Microsoft Visual Basic 6.0, the memory management techniques in Visual Basic .NET rely on garbage collection provided by the common language runtime. Garbage collection for managed code resources is automatic. However, the unmanaged code objects represented by the Outlook object model require explicit clean-up. Unless you garbage collect your event-aware objects correctly, you will experience problems with objects that are not destroyed and Outlook will remain in memory.

Setting References and Using Imports

In the sample application, a reference has already been set to Microsoft Outlook 10.0 Object Library. Figure 4 illustrates Outlook listed as a reference in the Solution Explorer window. Notice that the Properties window to the right of the Solution Explorer shows that the path for the interop assembly for Outlook points to the Outlook Primary Interop Assembly in the GAC.

Aa155703.odc_oladdinvbnet04(en-us,office.10).gif

Figure 4. Outlook Reference in the Solution Explorer

If you are creating your own solution that requires a reference to Outlook, use the Add References dialog box to add an Outlook reference. The Add References dialog box is shown in Figure 5.

Aa155703.odc_oladdinvbnet05(en-us,office.10).gif

Figure 5. The Add References dialog box

To add a reference to Outlook:

  1. Point to **Add Reference. . . **on the Visual Studio Project menu.
  2. Click the COM tab on the Add References dialog box.
  3. Select Microsoft Outlook 10.0 Object Library in the Component List.
  4. Click the Select button to add Outlook to the list of Selected Components.
  5. Click OK.

Once you have added Outlook as a reference, you should add an Imports statement so that you don't have to fully qualify Outlook objects by typing, for example, Microsoft.Office.Interop.Outlook.MailItem. An Imports statement appears after any Option statements but before any type declarations. In the case of the sample application, an Import alias is used for Outlook in the Connect and OutlAddIn classes as follows:

Imports Extensibility
Imports System.Reflection
Imports System.Runtime.InteropServices
Imports Outlook = Microsoft.Office.Interop.Outlook
Imports Microsoft.Office.Core
Imports Microsoft.Win32

Implementing the IDTExtensibility2 Interface

All COM add-ins must implement the IDTExtensibility2 interface. Unlike the Add-in Designer in Visual Basic 6.0, which exposes a property page user interface (UI) for your Connect class, Visual Studio. NET does not support UI for IDTExtensibility2. You must manually create required add-in registry settings such as LoadBehavior, Description and FriendlyName by using the Registry Editor for your Setup project, discussed later. The IDTExtensibility2 interface acts as a connector between your managed code add-in and Outlook, which acts as a host application for connected add-ins. Five events are exposed by the IDTExtensibility2 interface:

  • OnAddinsUpdate This event fires when an add-in is removed or added to the COMAddins collection object. The COMAddins collection is a member of the Outlook Application object.
  • OnBeginShutdown This event fires when the host application begins to shutdown. It cannot be used reliably in the Outlook environment.
  • OnConnection This event fires when Outlook loads or when your add-in is connected. The initialization code for your add-in belongs in the OnConnection event.
  • OnDisconnection This event fires when a user disconnects your add-in by using the COM Add-ins dialog box. It does not fire reliably when the host Outlook application is closing. You'll learn more about how to properly release resources and detect Outlook shutdown later in the article.
  • OnStartupComplete This event fires after the host application has loaded and all its resources are available.

The following code shows you how the OnConnection event works in Visual Basic. NET. We'll discuss the important code statements in the OnConnection event in the following sections. If you are familiar with COM add-in OnConnection code in Visual Basic 6.0, you will notice that this code looks familiar, but there are some important new elements, including:

  • The use of a Try Catch End Try block to trap errors.
  • GetType and InvokeMember retrieve properties from object variables when Option Strict is on.
  • The RegistryKey class lets you look for a WriteDiagnostics, which outputs debugging information to a text file.
  • The generic application object passed to the OnConnection event must be cast explicitly to an object whose type is Outlook.Application. The implicit type conversions of Visual Basic 6.0 are no longer allowed when you turn on Option Strict in Visual Basic. NET.
    'Implements is required in Connect class declarations.
    Implements Extensibility.IDTExtensibility2
    'The OutAddIn class separates add-in code from the Connect class.
    Dim m_BaseClass As New OutAddIn()
    Private Sub OnConnection(ByVal application As Object, _
            ByVal connectMode As Extensibility.ext_ConnectMode, _
            ByVal addInInst As Object, ByRef custom As System.Array) _
            Implements Extensibility.IDTExtensibility2.OnConnection
        Dim oApp As Outlook.Application
        Dim oType As Type
        Dim GetProgID As Object
        Dim MyProgID As String
        Dim oArgs As Object()
        Dim Reg As RegistryKey
        Dim strKey As String
        Try
            'Use InvokeMember to get the ProgID of addInInst object.
            oType = addInInst.GetType
            GetProgID = oType.InvokeMember("ProgID", _
                BindingFlags.Public Or BindingFlags.GetField Or _
                BindingFlags.GetProperty, _
                Nothing, _
                addInInst, _
                oArgs)
            MyProgID = CType(GetProgID, String)
            'Look for the WriteDiagnostics value.
            'If this key is found, DebugWriter writes to addinerr.txt.
            strKey = "Software\Microsoft\Office\Outlook\Addins\" _
                & MyProgID
            Reg = Registry.CurrentUser.OpenSubKey(strKey)
            Dim arrayNames As Array = Reg.GetValueNames
            If arrayNames.BinarySearch(arrayNames, "WriteDiagnostics") _
                    < 0 Then
                m_blnWriteDiagnostics = False
            Else
                m_blnWriteDiagnostics = True
            End If
            'Close the key.
            Reg.Close()
            DebugWriter("OnConnection Called")
            'Convert application from generic object to
            'Outlook.Application.
            oApp = CType(application, Outlook.Application)
            'Call InitHandler.
            m_BaseClass.InitHandler(oApp, MyProgID)
        Catch ex As SystemException
            DebugWriter("OnConnection Exception: {0}", ex.Message)
        End Try
    End Sub

Dual-Class Architecture

OutlookCOMAddinVBNET is built using a dual-class architecture. The Connect class contains the generic IDTExtensibility2 plumbing for the add-in, and the OutAddin class contains the application-specific code and event-aware objects for the add-in. The OutAddin class is initialized by the Dim m_BaseClass As New OutAddIn() statement in the Connect class. Both the OutAddin and Connect classes must contain unique <GuidAttribute> and <ProgIDAttribute> attributes in order for the classes to be recognized by COM. For example, here are the attributes for the OutlookCOMAddinVBNET.Connect class:

<GuidAttribute("14CF4472-86DE-48b0-9FD1-E5CA7673F9A9"), _
ProgIdAttribute("OutlookCOMAddinVBNET.Connect")> _
Public Class Connect
    'Implements is required.
    Implements Extensibility.IDTExtensibility2
    'The IDTExensibility event procedures goes here.
    'Additional code...
End Class

Note   If you modify OutlookCOMAddinVBNET to serve as the basis for your own add-in, you should modify both Guids and ProgIDs for both Connect and OutAddin classes. Use the Create GUID command on the Tools menu to generate new GUIDs for your project. Be sure to remove the {} (braces) from the GUID after you paste the new GUIDs into the <GuidAttribute>.

Option Strict

In the OutlookCOMAddinVBNET project, Option Strict has been turned ON in the Project Properties dialog box, as shown in Figure 6. Option Strict requires explicit declaration of all object variables (by implicitly turning on Option Explicit) and prevents widening type conversions between one object type and another. If you set Option Strict to off, your add-in code will behave more like add-ins created in Visual Basic 6.0. However, your code is then subject to potential type-conversion errors that will be exposed at run time rather than design time.

Aa155703.odc_oladdinvbnet06(en-us,office.10).gif

Figure 6. Use Project Properties dialog box to turn Option Strict on

Setting Option Strict to on has certain implications for the different object types supported by the Outlook Object Model. For example, the Outlook Object model uses the generic Item type to represent MailItem, AppointmentItem, ContactItem, and other specific Outlook classes. In the sample application, the generic Outlook Item is handled by a Friend class named OutlookItem. This class makes it possible to retrieve Outlook Item properties such as MessageClass, Subject, EntryID, and StoreID from Item objects. Specific Item properties are returned as properties of the OutlookItem class.

For example, the following code from OutlookItem shows you how the ObjectClass property is returned for an OutlookItem object. CbbZipResolveOnOff is a procedure that receives two arguments, oItem As Object and blnShow As Boolean. These arguments determine whether or not the Resolve Zip command bar button is shown on an Outlook Inspector. Since Option Strict is on, an implicit type conversion between oItem and the Outlook class represented by that Item is not allowed. To work around this restriction, the OutlookItem class retrieves a subset of properties held in common by MailItems, PostItems, AppointmentItems and so forth. Most of the work in the OutlookItem class is performed by the GetPropValue function. GetPropValue uses the overloaded InvokeMember method to retrieve an object that represents the specific property name passed as an argument to GetPropValue. InvokeMember is useful when Option Strict is on and you need to determine the value of an object property.

    Private Sub CbbZipResolveOnOff(ByVal oItem As Object, _
            ByVal blnShow As Boolean)
        Dim cbStandard As CommandBar
        Dim oAI As Outlook.AppointmentItem
        Dim oJI As Outlook.JournalItem
        Dim oCI As Outlook.ContactItem
        Dim oDI As Outlook.DistListItem
        Dim oTI As Outlook.TaskItem
        Dim oPI As Outlook.PostItem
        Dim oMI As Outlook.MailItem
        'Use the OutlookItem class to retrieve Item properties.
        Dim MyItem As New OutlookItem(oItem)
        DebugWriter("CbbZipResolveOnOff Called")
        Try
            Select Case MyItem.ObjectClass
                Case Outlook.OlObjectClass.olAppointment
                    oAI = CType(oItem, Outlook.AppointmentItem)
                    cbStandard = oAI.GetInspector.CommandBars("Standard")
                Case Outlook.OlObjectClass.olMail
                    oMI = CType(oItem, Outlook.MailItem)
                    cbStandard = oMI.GetInspector.CommandBars("Standard")
                Case Outlook.OlObjectClass.olContact
                    oCI = CType(oItem, Outlook.ContactItem)
                    cbStandard = oCI.GetInspector.CommandBars("Standard")
                Case Outlook.OlObjectClass.olDistributionList
                    oDI = CType(oItem, Outlook.DistListItem)
                    cbStandard = oDI.GetInspector.CommandBars("Standard")
                Case Outlook.OlObjectClass.olPost
                    oPI = CType(oItem, Outlook.PostItem)
                    cbStandard = oPI.GetInspector.CommandBars("Standard")
                Case Outlook.OlObjectClass.olTask
                    oTI = CType(oItem, Outlook.TaskItem)
                    cbStandard = oTI.GetInspector.CommandBars("Standard")
                Case Outlook.OlObjectClass.olJournal
                    oJI = CType(oItem, Outlook.JournalItem)
                    cbStandard = oJI.GetInspector.CommandBars("Standard")
            End Select

            If Not cbStandard Is Nothing Then
                CBBZipResolve = _
                  CType(cbStandard.FindControl(Tag:="ZipResolve"), _
                  CommandBarButton)
                If CBBZipResolve Is Nothing Then
                    CBBZipResolve =  _
                      CType(cbStandard.Controls.Add _
                      (Type:= MsoControlType.msoControlButton, _
                      Parameter:="ZipResolve"), CommandBarButton)
                End If
                With CBBZipResolve
                    .DescriptionText = _
                       "Resolve Zip Codes and Addresses using Web Service"
                    .BeginGroup = True
                    .Caption = "&Resolve Zip"
                    .FaceId = 4211
                    .Tag = "ZipResolve"
                    .TooltipText = _
                       "Resolve Zip Codes and Addresses using Web Service"
                    .Style = MsoButtonStyle.msoButtonIconAndCaption
                    .OnAction = "!<" & m_ProgID & ">"
                    .Visible = blnShow
                End With
            End If
        Catch ex As SystemException
            DebugWriter("CbbZipResolveOnOff Exception: {0}", ex.Message)
        End Try
    End Sub

    Public ReadOnly Property ObjectClass() As Outlook.OlObjectClass
        Get
            Return CType(GetPropValue(OlClass), Outlook.OlObjectClass)
        End Get
    End Property

    Public Function GetPropValue(ByVal strPropName As String) As Object
        Try
            GetPropValue = m_Type.InvokeMember(strPropName, _
              BindingFlags.Public Or BindingFlags.GetField Or _
              BindingFlags.GetProperty, _
              Nothing, _
              m_Item, _
              m_Args)
        Catch ex As SystemException
            Debug.WriteLine(String.Format _
              ("OutLookItem: GetPropValue for {0} Exception: {1} ", _
              strPropName, ex.Message))
        End Try
    End Function

Calling InitHandler

The InitHandler procedure in the OutAddin class does the work of initializing event-aware objects for OutlookCOMAddinVBNET. It is declared using the Friend keyword so that it can be called from OnConnection in the Connect class but is not visible publicly. It also creates a command bar button on the Standard toolbar of the Outlook Explorer window. The command bar button displays a .NET Windows form that displays information about the OutlookCOMAddinVBNET application.

    Friend Sub InitHandler(ByVal oApp As Outlook.Application, _
            ByVal strProgID As String)
        Dim oCommandBars As CommandBars
        Dim oStandardBar As CommandBar
        DebugWriter("InitHandler Called")
        Try
            'Declared WithEvents
            m_olOutlook = oApp    'Application object
            'Instantiate public module-level Outlook application variable.
            m_olApp = oApp
            'The ProgID string is required for CommandBarControls.
            m_ProgID = strProgID
            'Instantiate event-aware objects.
            m_olNamespace = m_olOutlook.Session
            m_olExplorers = m_olOutlook.Explorers
            m_olInspectors = m_olOutlook.Inspectors
            'Type conversion is required due to "ambiguous name" issue.
            m_olExplorer = CType(m_olOutlook.ActiveExplorer, _
                Outlook.ExplorerClass)
            'Create command bar button.
            'Set up a custom button on the "Standard" command bar.
            oCommandBars = m_olExplorer.CommandBars
            oStandardBar = oCommandBars.Item("Standard")
            ' In case the button was not deleted, use the existing one.
            CBBAbout =CType(oStandardBar.FindControl _
                (Tag:="Visual Basic.NETSample"), CommandBarButton)
            If CBBAbout Is Nothing Then
                CBBAbout = CType(oStandardBar.Controls.Add _
                    (Type:=MsoControlType.msoControlButton, _
                    Temporary:=False), CommandBarButton)
                With CBBAbout
                    .Caption = ".NET"
                    .Style = MsoButtonStyle.msoButtonIconAndCaption
                    .FaceId = 472
                    .TooltipText = _
                         "About Outlook COM Add-in for Visual Basic.NET"
                    .BeginGroup = False
                    ' Use the Tag property to find the control.
                    .Tag = "Visual Basic.NETSample"
                    .OnAction = "!<" & m_ProgID & ">"
                    ' Make the button visible.
                    .Visible = True
                End With
            End If
        Catch ex As SystemException
            DebugWriter("InitHandler Exception: {0}", ex.Message)
        End Try
    End Sub

Creating Event-Aware Objects

Event-aware objects such as m_olOutlook, m_olNamespace, m_olExplorers, m_olExplorer, and m_olInspectors are instantiated in InitHandler. These objects respond to events raised by the Outlook Object Model. The events themselves are COM events, and the common language runtime provides the connection points so that the add-in's managed code can respond to COM events hosted by Outlook. Visual Basic .NET provides two methods of hooking events:

  • Using the traditional Dim WithEvents m_olOutlook As Outlook.Application and then writing event procedure code using a Handles clause. This approach is used in the OutlookCOMAddinVBNET project.

       Dim WithEvents m_olOutlook As Outlook.Application
       'Declare additional event-aware object declarations as needed.   Private Sub m_olOutlook_NewMail() Handles m_olOutlook.NewMail       'Event code goes here   End Sub
    
  • Using the new AddHandler and RemoveHandler statements in Visual Basic .NET. These statements can dynamically connect and disconnect event handlers from event procedures in your code. Unlike the WithEvents approach, AddHandler and RemoveHandler allow you to associate multiple event handlers with a single event.

       Private Sub TestLocalEventHander()
           Dim m_olApp As New Outlook.Application()
           AddHandler m_olApp.NewMail, AddressOf Me.EventHandlerNewMail
           'Additional code
           RemoveHandler m_olApp.NewMail, AddressOf Me.EventHandlerNewMail
       End Sub
    
       Private Sub EventHandlerNewMail()
          'Event code goes here.
       End Sub
    

    Note   Do not use WithEvents and AddHandler/RemoveHandler for the same event.

The current version of the Outlook Primary Interop Assembly and the Visual Basic .NET compiler combine to produce a problem when you attempt to declare event-aware objects using certain Outlook classes. For example, if you Dim m_olExplorer As Outlook.Explorer and then attempt to create an event procedure for the Close event, Visual Basic .NET will display the following design-time error in the Task List:

Note   'Close' is ambiguous across the inherited interfaces 'Microsoft.Office.Interop.Outlook._Explorer' and 'Microsoft.Office.Interop.Outlook.ExplorerEvents_10_Event'.

To prevent this message, and to allow your project to compile, you must use the RCW or .NET coclass for declarations of certain Outlook objects that cause the ambiguous name design-time error. For example, the following syntax works correctly for the declaration of the event-aware m_olExplorer object and the m_olExplorer_Close event. m_olExplorer is declared as an ExplorerClass object rather than an Explorer object:

   'Declarations are made using WithEvents and coclass.
   Dim WithEvents m_olExplorer As Outlook.ExplorerClass

   Private Sub m_olExplorer_Close() _
      Handles m_olExplorer.ExplorerEvents_Event_Close
      'Event code goes here.   End Sub

Note   Look for this problem to be fixed in future versions of the Visual Basic .NET compiler.

Integrating XML Web Services

One of the most compelling aspects of development using Visual Basic .NET is the easy integration of XML Web services into your project. If you select the Add Web Reference command on the Project menu, Visual Studio displays the Add Web Reference dialog box. Visual Studio then creates all the necessary code behind the scenes to add the XML Web service to your project and call the properties and methods of objects provided by the XML Web service. The following code for CBBZipResolve calls the XML Web service to resolve the postal address for m_olContactItem, which represents a ContactItem object. Although the ZipResolver Web service returns the postal address in uppercase, the following code uses the built-in .NET CultureInfo class to return a proper-cased string for the postal address street and city.

   Private Sub CBBZipResolve_Click(ByVal Ctrl As _
           Microsoft.Office.Core.CommandBarButton, _
           ByRef CancelDefault As Boolean) Handles CBBZipResolve.Click
       Dim strAddress, strCity, strState As String
       Try
           If IsUSPostalAddress(m_olContactItem) Then
                strAddress = m_olContactItem.MailingAddressStreet
                strCity = m_olContactItem.MailingAddressCity
                strState = m_olContactItem.MailingAddressState
                Dim fWait As New frmWait()
                Dim oCorrectedAddress As _
                    New net.eraserver.webservices.USPSAddress()
                Dim oZipResolve As _
                    New net.eraserver.webservices.ZipCodeResolver()
                'Display fWait since you cannot display hourglass.
                fWait.Show()
                System.Windows.Forms.Application.DoEvents()
                oZipResolve.Url = _
                    "http://webservices.eraserver.net/zipcoderesolver/" &
                      _
                    "zipcoderesolver.asmx?WSDL"
                oCorrectedAddress = _
                    oZipResolve.CorrectedAddressXml _
                    ("0", strAddress, strCity, strState)
                fWait.Close()
                'oCorrectedAddress.Street is Nothing when not resolved.
                If oCorrectedAddress.Street Is Nothing Then
                    MsgBox("Unable to resolve this address.", _
                        MsgBoxStyle.Information, _
                        "Outlook COM Add-in for VB.NET")
                Else
                    Dim oContact As Outlook.ContactItem = _
                        CType(m_olApp.ActiveInspector.CurrentItem, _
                        Outlook.ContactItem)
                    With oContact
                        .MailingAddressStreet = _
                             GetProper(oCorrectedAddress.Street)
                        .MailingAddressCity = _
                             GetProper(oCorrectedAddress.City)
                        .MailingAddressState = oCorrectedAddress.State
                        .MailingAddressPostalCode = _
                             oCorrectedAddress.FullZIP
                    End With
                End If
            Else
                MsgBox("Not a valid postal address!", _
                    MsgBoxStyle.Exclamation, _
                        "Outlook COM Add-in for VB.NET")
            End If
        Catch ex As System.Exception
            MsgBox("Error invoking Zip Code Resolver Web Service." _
            & vbCrLf & ex.Message, MsgBoxStyle.Information, _
            "Outlook COM Add-in for VB.NET")
        End Try
    End Sub

    'This function converts USPS addresses (upper-cased) to initial caps.
      string
    Private Function GetProper(ByVal strInput As String) As String
        On Error Resume Next
        If Len(strInput) = 0 Then
            Exit Function
        End If
        Dim oCI As New CultureInfo("en-us")
        GetProper = oCI.TextInfo.ToTitleCase(LCase(strInput))
    End Function

Detecting Outlook Shutdown

One of the most difficult aspects of Outlook add-in programming is ensuring that your add-in does not cause Outlook to remain in memory when the user exits Outlook. Outlook shutdown occurs when there are no longer any open inspectors or explorers. Outlook is unique within the Office suite in that it has two classes for its user interface, represented by the Outlook explorer and inspector. Outlook is still open if the main Outlook Explorer window is closed but an inspector window remains open. Detection of shutdown is complicated because there are certain invocations of Outlook (such as Send To Mail Recipient from the shortcut menu of Microsoft Windows Explorer) that do not increment the count of explorer or inspector windows. If any object references are still alive in your add-in when Outlook shuts down, your add-in will cause Outlook to remain in memory.

Normally, the OnDisconnection event exposed by the IDTExtensibility2 interface would provide a means for your code to perform object clean-up and ensure a correct shutdown sequence. The OnDisconnection event, however, does not fire reliably when the shutdown mode is equal to ext_DisconnectMode.ext_dm_HostShutdown.

The known workaround to this problem is to use the Close event for both Inspector and Explorer objects. The Close event calls UnInitHandler, which in turn performs object clean-up. Since the Close event for these objects triggers the "Ambiguous across inherited interfaces" design-time error described previously, you must declare event-aware objects using ExplorerClass and InspectorClass respectively. The following code from the OutAddin class will correctly detect the shutdown of the Outlook user interface.

'Declarations made using WithEvents and coclass.
   Dim WithEvents m_olExplorer As Outlook.ExplorerClass
   Dim WithEvents m_olInspector As Outlook.InspectorClass
   'Explorer Close event code:
   Private Sub m_olExplorer_Close() _
       Handles m_olExplorer.ExplorerEvents_Event_Close
       Try
           DebugWriter("Explorer_Close Called", "")
           m_olExplorer = CType(m_olApp.ActiveExplorer, _
               Outlook.ExplorerClass)
           If (m_olExplorer Is Nothing) And _
                   (m_olApp.Inspectors.Count = 0) Then
               UnInitHandler()
         End If
      Catch ex As SystemException
         DebugWriter("Explorer_Close Exception: {0}", ex.Message)
      End Try
   End Sub
   'Inspector Close event code:
   Private Sub m_olInspector_Close() _
          Handles m_olInspector.InspectorEvents_Event_Close
      Try
          DebugWriter("Inspector_Close Called", "")
          If m_olApp.ActiveExplorer Is Nothing And _
                  m_olApp.Inspectors.Count <= 1 Then
              UnInitHandler()
          End If
      Catch ex As SystemException
          DebugWriter("Inspector_Close Exception: {0}", ex.Message)
      End Try
   End Sub

Calling UnInitHandler and Disposing of Objects

Once the Close event calls the UnInitHandler procedure shown below, you must correctly dispose of your objects. If you do not dispose of all objects declared and instantiated in your add-in, then Outlook will remain in memory after exiting. The object clean-up code is positioned in a Try...Catch...Finally...End Try block. With add-ins created in Visual Basic 6.0, the correct coding practice would be to set all object variables to Nothing during the UnInitHandler procedure. For COM objects in Visual Basic 6.0, setting the object to Nothing killed the object reference and released the object from memory. In .NET add-ins that interoperate with COM-based Outlook, disposal of COM objects is more complex. The DisposeObject procedure is called on all class or module level object variables that are still in scope. DisposeObject calls Marshal.ReleaseCOMObject(obj) until the reference count equals zero. Marshal.ReleaseCOMObject provides deterministic release of COM objects. In the Finally block of UnInitHandler, GC.Collect and GC.WaitforPendingFinalizers cause the common language runtime to clean up any remaining RCW objects and ensure a clean shutdown of Outlook.

    Friend Sub UnInitHandler()
        'You must dereference all objects in this procedure
        'or Outlook will remain in memory.
        Dim oCommandBars As CommandBars
        Dim oStandardBar As CommandBar
        Dim oCBB As CommandBarButton
        Try
            If m_blnRunUnInitHandler Then
                Exit Sub
            Else
                m_blnRunUnInitHandler = True
            End If
            DebugWriter("UnInitHandler Called")
            ' Delete the Explorer command bar button.
            If Not (CBBAbout Is Nothing) Then
                CBBAbout.Delete()
            End If
            ' Delete the Inspector command bar button.
            CbbZipResolveRemove()
            ' Next release all Outlook objects dim'ed WithEvents.
            DisposeObject(m_olMailItem)
            DisposeObject(m_olPostItem)
            DisposeObject(m_olAppointmentItem)
            DisposeObject(m_olContactItem)
            DisposeObject(m_olDistListItem)
            DisposeObject(m_olJournalItem)
            DisposeObject(m_olTaskItem)
            DisposeObject(m_olInspector)
            DisposeObject(m_olExplorer)
            DisposeObject(m_olInspectors)
            DisposeObject(m_olExplorers)
            DisposeObject(m_olReminders)
            DisposeObject(m_olResults)
            DisposeObject(m_olViews)
            DisposeObject(m_olNamespace)
            DisposeObject(m_olApp)
            DisposeObject(m_olOutlook)
        Catch ex As SystemException
            DebugWriter("UnInitHandler Exception: {0}", ex.Message)
        Finally
            ' Do final garbage collection.
            GC.Collect()
            GC.WaitForPendingFinalizers()
        End Try
    End Sub

    Private Sub DisposeObject(ByVal obj As Object)
        Dim count As Integer
        DebugWriter("DisposeObject Called")
        Try
            If obj Is Nothing Then
                Exit Try
            End If
            count = Marshal.ReleaseComObject(obj)
            DebugWriter(String.Format("DisposeObject - Release {0}, _
                RefCount: {1}", obj.ToString(), count), "")
            While count > 0
                count = Marshal.ReleaseComObject(obj)
            End While
        Catch ex As SystemException
            DebugWriter("DisposeObject Exception: {0}", ex.Message)
        Finally
            obj = Nothing
        End Try
    End Sub

Working with a Setup Project

You've learned about the critical working code of an Outlook add-in built with Visual Basic .NET. Accompanying the sample OutlookCOMAddinVBNET project is the setup project named OutlookCOMAddinVBNETSetup. There are several important setup issues that you must address when you deploy an add-in developed with Visual Basic .NET:

  • You must install the Microsoft .NET Framework (approximately 20 MB) on the target machine before you install your add-in.

  • You must install the Outlook Primary Interop Assembly on the target machine. See the "To Use the Outlook 2002 PIA in Your Solution" section above.

  • If your Setup project installs the add-in registry keys under HKEY_LOCAL_MACHINE (HKLM) instead of HKEY_CURRENT_USER (HKCU), the add-in will not be visible in the Outlook COM Add-ins dialog box.

  • The COM Add-ins dialog box will display the common language runtime engine, mscoree.dll, as the source of your add-in instead of the name of your managed code add-in as shown in Figure 7. In the case of the sample application, the actual file name is OutlookCOMAddinVBNET.dll.

    Aa155703.odc_oladdinvbnet07(en-us,office.10).gif

    Figure 7. The COM Add-Ins dialog box

Required Setup Distributions

At this point, there is no convenient solution to the requirement of installing the .NET Framework. Later versions of Microsoft Windows or Office may have the .NET Framework installed so that no additional distribution is required.

Microsoft recommends that you do not distribute solutions using the TLBIMP-generated interop assembly. The current recommendation is to use the Outlook primary interop assembly, which is currently available only for Outlook 2002. As of this writing, there is no forthcoming primary interop assembly for Office 2000 and Outlook 2000.

To distribute your add-in to other users, you must follow these steps:

  1. Be certain to add the Outlook Primary Interop Assembly to your Setup project using the steps outlined above in the "To use the Outlook 2002 PIA in your solution" section.

  2. You can exclude stdole.dll and office.dll from the list of Detected Dependencies in your Setup Project.

  3. Right-click YourProjectSetupName in the Solution Explorer, and then click Build. This creates Setup.exe and the required .msi files in the YourProjectSetupName\Debug or YourProjectSetupName\Release folder, depending on the type of build.

  4. Copy the contents of the YourProjectSetupName\Debug or YourProjectSetupName\Release folder to the target computer on which you want the add-in installed.

  5. Start Setup.exe to install the .NET Shared add-in.

    Note   In the sample OutlookCOMAddinVBNETSetup project, all these steps have been completed for you. Be aware that the OutlookCOMAddinVBNET Setup project, in order to minimize download size, does not include Windows Installer Bootstrap files.

Add-in Load Behavior

The load behavior of an Outlook COM add-in is controlled by settings in the Windows registry. If the add-in is registered under HKLM, the add-in will not be visible in the Outlook COM Add-ins dialog box. Registration under HKLM prevents a user from disconnecting the add-in through the COM Add-ins dialog box. Table 1 illustrates the DWORD values for LoadBehavior. These values apply to both HKCU and HCLM.

Table 1. LoadBehavior registry settings

Initial Load Behavior Setting LoadBehavior DWORD Behavior Description
None 0x00

0x01

(Connected)

The COM add-in is not loaded when Outlook boots. It can only be loaded programmatically.
Startup 0x02

0x03

(Connected)

The add-in is loaded when Outlook boots. Once the add-in is loaded, it remains loaded until it is explicitly unloaded.
Load On Demand 0x08

0x09

(Connected)

The add-in is not loaded until the user clicks the button or menu item that loads the add-in, or until a procedure sets its Connect property to True.
Load At Next Startup Only 0x10

(Reverts to 0x09 on next boot)

After the COM add-in has been registered, it loads as soon as the user runs Outlook for the first time, and it creates a button or menu item for itself. The next time the user boots Outlook, the add-in is loaded on demand; that is, it doesn't load until the user clicks the button or menu item associated with the add-in.

You must create registry values in your Setup project for LoadBehavior, Description and FriendlyName. These values are stored under HKCU or HKLM\Software\Microsoft\Office\Outlook\Addins\ProgID.

ProgID represents the programmatic identifier of your add-in. The keys for the sample OutlookCOMAddinVBNETSetup project are shown in the Registry Editor in Figure 8. When the add-in is installed by your Setup project, the registry settings cause the add-in to load during Outlook startup.

Aa155703.odc_oladdinvbnet08(en-us,office.10).gif

Figure 8. Use the Registry Editor to create add-in registry settings

Note   You cannot use the Outlook COM Add-ins dialog box to add a managed code add-in to the list of installed add-ins manually. Outlook will report that '<addin.dll> is not a valid Office add-in.' If you want to deploy the sample add-in, you must use the .msi file created by your Visual Studio .NET Setup Project.

If you created your add-in solution with the Visual Studio Add-in Wizard, you should understand that the choices you make in the Add-in Wizard control the registry hive (HKLM or HKCU) where your add-in is registered. For additional details, please see PRB: Visual Studio .NET Shared Add-in Is Not Displayed in Office COM Add-ins Dialog Box (Q316723) in the Microsoft Knowledge Base.

Add-ins and Outlook Security

One final setup issue is the appearance that your managed code add-in is supplied by mscoree.dll, the common language runtime engine. In fact, what you see in the COM Add-in dialog box is more than an appearance. It's actually correct that from the perspective of COM, your add-in's InprocServer32 setting points to mscoree.dll. The InprocServer32 key contains a CodeBase value that points to the location of your managed code add-in assembly. This dependence on mscoree.dll presents a problem when you attempt to add your add-in to the list of trusted add-ins in the Outlook Security Form in the Outlook Security Settings public folder. Although a complete discussion of this issue is beyond the scope of this article, you should be aware of the following:

  • All add-ins in Outlook are trusted by default, whether they are managed or unmanaged code add-ins.
  • Unlike other Office applications, Outlook Macro Security settings (Low, Medium, or High) do not determine whether or not an add-in is loaded.
  • All add-ins are subject to the object model guard restrictions imposed by the Outlook E-mail Security Update. Object model guard restrictions include programmatic send, access to recipient objects and so forth. If you want to use blocked Outlook object model methods and properties in your add-in, you can avoid the Outlook security warnings by adding your add-in to the list of trusted add-ins. For additional details, see the Knowledge Base article Q290500, OL2002: Developer Information About E-Mail Security Features.
  • Do not add mscoree.dll**to the list of trusted add-ins. If you do trust mscoree.dll, then all add-ins that use the common language runtime will be trusted. Since you want to trust only a specific add-in, you must create a COM add-in shim that will act as a proxy for your managed code add-in. See the Knowledge Base article Q322027, OL2002: COM Add-Ins Are Not Trusted If They Are Created with Visual Studio .NET for specifics, and Using the COM Add-in Shim Solution to Deploy Managed COM Add-ins in Office XP.

Conclusion

There are some important coding practices that you must follow when you create an Outlook add-in using Visual Basic .NET. You must also be aware of the correct setup requirements when you deploy your add-in to a target machine. The built-in classes in the .NET Framework offer significant advantages over writing and maintaining helper code in Visual Basic 6.0. In terms of consuming XML Web services, a .NET add-in does not require the proxy classes that are necessary for a Visual Basic for Applications or Visual Basic 6.0 add-in. If you are creating an Outlook add-in that uses an XML Web service, and you can manage the installation of the .NET Framework, you should consider writing your add-in with Visual Basic .NET.