Programming Microsoft Outlook and Microsoft Exchange 2003
This article is an excerpt from Programming Microsoft Outlook and Microsoft Exchange 2003, from Microsoft Press (ISBN 0-7356-1464-4, copyright Microsoft Press 2004 all rights reserved). A portion of Chapter 7: Office COM Add-Ins is adapted here and describes how to develop a COM add-in for Microsoft Outlook 2003 using Microsoft Visual Basic.
The author, Thomas Rizzo, is a group product manager with the Microsoft SQL Server product team. For five years, he worked in the Microsoft Exchange group, where he focused on building customer confidence and productivity with the platform. Tom also served as a Microsoft system engineer and wrote the first two editions of this popular book.
No part of this chapter may be reproduced, stored in a retrieval system, or transmitted in any form or by any means—electronic, electrostatic, mechanical, photocopying, recording, or otherwise—without the prior written permission of the publisher, except in the case of brief quotations embodied in critical articles or reviews.
When you develop Microsoft Office solutions, you probably want to extend existing Office applications by adding new functionality. In Microsoft Outlook 98, you can add new forms to your application's Outlook environment, but you cannot easily add new toolbars or program your application to respond to events other than form events such as Item_Open or Item_Read. To provide functionality beyond that of forms, you have to write an Exchange Client Extension, which involves strict requirements and coding practices, and any extensions have to be written in C/C++. With Outlook 98, a Microsoft Visual Basic or VBA developer is stuck either hacking a solution together or not enhancing the functionality at all.
Office 2000 and later includes support for COM add-ins. A COM add-in is a dynamic-link library (DLL) that can be used to add functionality to an Office application, and, as you can guess by the name, a COM add-in can be built using any COM or Microsoft .NET development tool, including Visual Basic, Visual C#, and Visual C++. Because COM add-ins are compatible with all Office products, you can design an add-in once and use it in several applications. For example, you can write a COM add-in that uses the CommandBar object model, which is shared across all the Office products, to customize the toolbars in your applications.
Note The COM add-in I describe in this chapter can't be used across all the Office applications because it calls functionality that's specific to Outlook. However, the concepts behind building this COM add-in can be applied to any add-in designed for other Office applications. Also, this chapter shows how to build COM add-ins using Visual Basic.
COM add-ins are registered to be loaded by Office 2000 and later applications. Because COM add-ins are designed as in-process DLLs, they run in the same address space as the host application. One benefit of an in-process add-in is that it has efficient access to the object model of the host application, allowing the add-in to call methods and properties quickly or to receive events from the host application. One caution about running an add-in in-process is the danger of slowing down or even crashing the host application. Keep this in mind during development.
You must consider a number of issues when deciding whether to develop a COM add-in. Some of the functionality COM add-ins provide in Outlook is similar to other Microsoft Exchange Server and Outlook development technologies, such as the Event Scripting Agent and Server Events in Exchange 2000. For this reason, I've provided three questions to help you determine whether to create a COM add-in or use another technology:
- Do you need to receive events when the Outlook client is not running? The life span of your COM add-in is controlled by Outlook. When the Outlook process is running, your COM add-in can run and receive events. When Outlook is not running, your add-in is also not running. If you need to receive events when the Outlook client is not running, consider using the Event Scripting Agent or Exchange Server Events; your agent will run on the server, so it will always receive events while the server is running. In Chapter 8, I'll examine a COM add-in that notifies users when an item in a folder changes functionality that might be better implemented with the Event Scripting Agent or Exchange Server Events.
- Is performance a big concern for your application? If so, you should use an add-in because it is loaded in-process with Outlook. However, you must use defensive coding practices to prevent Outlook from crashing. Don't create an add-in that performs expensive lookups or data retrievals when starting because Outlook will wait for the add-in to finish these operations before continuing.
- Is your application event-driven? Outlook will fire a number of new events that your COM add-in can implement and handle. These events provide you with greater control over the Outlook user interface and Outlook data.
If your answers to the three questions indicate that you should use a COM add-in, development is actually quite straightforward. Visual Basic has some features that can get you up and developing in a matter of minutes. In this section, I'll take a look at how to start developing COM add-ins, and then I'll review the features of the Outlook object model that you can employ in your COM add-ins.
Before you begin developing an add-in, you must create an ActiveX DLL project in Visual Basic (version 5.0 or later). After the project loads, select Microsoft Add-In Designer in the Project/References dialog box, as shown in Figure 1. This library contains the interfaces for your COM add-ins. (Another option is to use the add-in project; instead of selecting ActiveX DLL as your project type, select Add-in to open the Add-In Designer.)
In your Visual Basic code, you must type
Implements IDTExtensibility2 to see the IDTExtensibility2 interface's events in the Procedure drop-down list in the Visual Basic code window. Figure 2 shows the code window with the IDTExtensibility2 event procedures added.
Figure 1. The Microsoft Add-In Designer in the Project/References dialog box
Figure 2. The Visual Basic 6.0 code window with the five event procedures for the IDTExtensibility2 interface
The IDTExtensibility2 Events
As you can see in Figure 2, IDTExtensibility2 provides five events for you to use in your COM add-in: OnConnection, OnDisconnection, OnStartupComplete, OnBeginShutdown, and OnAddInsUpdate. Let's examine each of these events in detail.
The OnConnection event is called when your add-in is first loaded or a connection to it is established for example, when Outlook starts or when the user chooses to load your COM add-in. The user can select your add-in in the COM Add-Ins dialog box in Outlook (as shown in Figure 3). You open this dialog box by choosing Options from the Tools menu, clicking the Other tab, clicking the Advanced Options button, and clicking COM Add-Ins.
Figure 3. The COM Add-Ins dialog box in which users add or remove COM add-ins
The OnConnection event procedure is a great place to grab and store the Outlook Application object for use in your code later. When an OnConnection event occurs, the OnConnection event procedure is passed the following four parameters: Application, ConnectMode, AddInInst, and Custom(). The Application parameter is a reference to the Outlook Application object.
Note In Outlook 2003, to avoid displaying most security warnings, an add-in must use only the Application object passed to the OnConnection event of the add-in. If the add-in runs in an Exchange environment, the administrator must also trust the add-in by registering it in the Security Settings folder. However, there are a couple scenarios where security warnings may still be displayed: binding a control to a property derived from the Inspector object; and accessing the Body property of an item object, HTMLBody of the mail and post item objects, and IMAddress of the contact item object. For more information, see Important Security Notes for Microsoft Outlook COM Add-ins Developers.
The ConnectMode parameter describes the way in which the COM add-in was loaded. The ConnectMode parameter is a Long data type that can be set to one of the following constants: ext_cm_AfterStartup, ext_cm_CommandLine, ext_cm_External, or ext_cm_Startup. The constants ext_cm_CommandLine and ext_cm_External do not apply to Office add-ins. The ext_cm_AfterStartup and ext_cm_Startup constants are subtly different from each other. The ConnectMode parameter is set to ext_cm_AfterStartup when the add-in is connected after Outlook starts or when the Connect property of the add-in is set to True. Usually, the ConnectMode parameter is set to ext_cm_AfterStartup when the user connects the add-in manually through the user interface. The ConnectMode parameter is set to ext_cm_Startup when your add-in is connected when Outlook starts up. The AddInInst parameter passes an object that refers to the current instance of your COM add-in. The Custom parameter is an array of Variant data types that can hold user-defined data for your add-in. For Office add-ins, this parameter should be ignored.
The OnDisconnection event occurs when your COM add-in is being disconnected from the application. The OnDisconnection event procedure is passed two parameters: RemoveMode and Custom. The RemoveMode parameter, which is a Long data type, specifies how your add-in was disconnected and can be set to ext_dm_HostShutdown or ext_dm_UserClosed. As you can guess by their names, ext_dm_HostShutdown indicates that the add-in is disconnected by the host shutting down, and ext_dm_UserClosed indicates either that a user is deselecting the add-in's check box in the COM Add-Ins dialog box, or that the Connect property of the add-in is set to False.
The second parameter, Custom, is an array of Variant data types that can hold user-defined data for your add-in. For Office add-ins, this parameter should be ignored.
You use the OnDisconnection event to restore any changes made to the application or to perform general cleanup for your application. Be sure to destroy any Inspector or Explorer objects that you create because Outlook will not properly close if any instances of these objects still exist. The easiest way to do this is to check the Explorers and Inspectors collections to see if any of the objects exist.
Also note that in Outlook 2003, the Application's Quit event is fired earlier in the shutdown of Outlook. You can also use this event to destroy any references to Outlook objects that you have in your programs. However, because this applies only to Outlook 2003 and has not yet been back-ported to previous versions of Outlook, you might want to implement both options in your COM add-in by attempting to destroy objects in the Quit event and also in OnDisconnection. Just be sure to have good error handling to catch any errors that occur in either event.
If a COM add-in connects at the time the host application is started, the OnStartupComplete event fires when the host has completed all of its startup routines. The OnStartupComplete event does not fire if a user chooses to load the add-in from the COM Add-Ins dialog box after the application has already started. In that case, the OnConnection event fires. The OnStartupComplete event procedure takes one parameter, Custom, which you should ignore.
In the OnStartupComplete event procedure, you place code that interacts with the application and that should not be run until the application finishes loading. This event procedure is a good place to set some of your local and global variables to their corresponding Outlook objects. In the COM add-in example in Chapter 8, the OnStartupComplete event procedure searches the Outlook groups for a shortcut to the Account Tracking application and also has code to manipulate the command bars in the user interface.
The OnBeginShutdown event fires when the application is about to shut down and is called before the OnDisconnection event. Even after OnBeginShutdown fires, you still have full access to the Outlook object model, so you can save your settings to the registry or a file, or save any changes to your objects, before your objects are unloaded.
Note If you are using Explorer or Inspector objects in your COM add-in, listen for the Close event on these objects. When your application receives this event, it should destroy all open Explorer or Inspector objects because your Outlook COM add-in will not shut down correctly if any Explorer or Inspector objects are left open. (Chapter 6 covers the Explorer and Inspector objects.)
The OnAddInsUpdate event fires when the list of COM add-ins is updated. When another add-in is connected or disconnected, this event occurs in any other connected COM add-in. You can use this event to ensure that any other add-in that your add-in depends on is connected. Once the add-in that yours depends on is disconnected, you can disable your functionality or display a dialog box to warn the user to reconnect the other add-in. The OnAddInsUpdate event handler includes one parameter, Custom, which your application should ignore.
Now that you know which events fire for add-ins, you need to know how to register and load the add-ins. Outlook decides which add-ins to load on the basis of settings in the user's registry. If your add-in is not specified correctly in the registry, Outlook will not be able to load your add-in, nor will your add-in appear in the COM Add-Ins dialog box.
Registering Your Add-In
After you compile your add-in, you must register the add-in DLL for your add-in to work correctly. To do this, you use the Regsvr32 command and specify the path to your DLL. This registers your DLL under the HKEY_CLASSES_ROOT subtree in the registry. If you are deploying your add-in to multiple machines, you must figure out how to install your DLL on those machines. One way is to use logon scripts to copy and register the DLL. Another way is to deploy your add-in using the Visual Basic deployment and setup tools, an MSI, or Microsoft Systems Management Server (SMS).
After your COM add-in DLL is registered, you must add some settings to the registry on the local machine. These settings include the add-in's name, a description of the add-in, and the add-in's target application, initial load behavior, and connection state.
Before writing this information to the registry, you must decide how you want to deploy your add-in: you can either force all users to use your add-in or allow each user to decide whether to load it. The approach you use will determine where in the registry the information for your add-in must be written. If you want to ensure the add-in is always loaded and that every user on a machine has access to it, you must register it under the key:
Installing your add-in here effectively locks down the COM Add-Ins dialog box because the user cannot unload add-ins that are registered here.
If you want to give your users the option to specify whether to load the add-in and to choose their own settings for the add-in, install your add-in under the key:
This location allows per-user settings for the add-in. An example of a registered add-in under this key is shown in Figure 4.
Figure 4. A registry key showing an add-in loaded under the key \HKCU\Software\Microsoft\Office\Outlook\AddIns
When you register your add-in under one of these registry keys, the information written to the key includes the following name/value pairs: Description, FriendlyName, and LoadBehavior. Description is a string type that provides a short description of the COM add-in. FriendlyName is a string that contains the name displayed in the COM Add-Ins dialog box. LoadBehavior is of type DWORD, where the value is an integer that specifies how to load your COM add-in. This integer can have a value of 0 for Disconnected, 1 for Connected, 2 for load on startup, 8 for load on demand, or 16 for connect first time. You can combine these values to create different types of load sequences. For example, if you assign the value 3 to LoadBehavior, the add-in will be loaded on startup as well as connected. If you assign 9 to the add-in, the add-in will be connected and demand-loaded when necessary, such as when the user clicks a button that uses code in the add-in.
The following code shows the content of a sample registry editor (.reg) file for a COM add-in:
REGEDIT4 [HKEY_CURRENT_USER\Software\Microsoft\Office\Outlook\ Addins\Sample.MyAddIn] "FriendlyName"="My Sample Add-In" "Description"="Sample Outlook COM Add-In" "LoadBehavior"=dword:00000003
Trusting Your COM Add-Ins
You can specify whether to trust all installed COM add-ins on a machine by setting the DWORD value DontTrustInstalledFiles under the following registry key:
By assigning 0 to DontTrustInstalledFiles, you specify that Outlook should trust all installed add-ins. A value of 1 specifies not to trust all add-ins. By default, Outlook trusts all files and installed add-ins. But this does not mean that Outlook will run all macros by default. Macros are different from COM add-ins. There is another setting for macros—the security level—which you can set to high, medium, or low. This setting controls whether signed, trusted macros are run.
Debugging your add-in using Visual Basic 6.0 is easy. All you do is write your add-in, register it, set some breakpoints on the code statements you are interested in, and then run the add-in in the Visual Basic 6.0 development environment. In the Project Properties dialog box for your Visual Basic project (shown in Figure 5), you can set some debugging options. You can specify whether to wait for the component to be created by the host application or to have Visual Basic start an instance of the host application for you. I usually specify waiting for the components to be created by the host application. After Outlook starts and creates the COM add-in, the code in the add-in executes and stops on breakpoints it encounters. You can then step through your code in the Visual Basic Editor.
Figure 5. The Debugging tab of the Project Properties dialog box in Visual Basic 6.0
When you debug, be aware that message boxes in your add-in will appear in the Visual Basic development environment, not in Outlook. If Outlook stops responding, you should switch to Visual Basic to see whether a message box is visible and waiting for you to respond.
Important In your COM add-ins, watch out for references to Inspector or Explorer objects in your code. If you do not properly destroy your variables, Outlook will exit but will stay in memory. Even if in your OnBeginShutdown procedure you set the variables holding references to these objects to Nothing, Outlook will still stay in memory. For this reason, the Explorer and Inspector objects both implement a Close event. You should add code, such as code that loops through the Explorers and Inspectors collections, to explicitly close each object to this event to destroy your references and check for any remaining Explorer or Inspector objects. If you find no Inspector objects and only one Explorer object, it's a sign that Outlook is properly shutting down.
You can leverage COM add-ins from Outlook forms in many ways. One of the best ways is to use them to add functionality that isn't easy to implement in Microsoft Visual Basic Scripting Edition (VBScript) or that might be very expensive to create. For example, you can create a Collaboration Data Objects (CDO) session in a COM add-in and then share that CDO session across multiple Outlook applications so each application does not have to create and destroy a CDO session. With VBScript, you can access the collections of COM add-ins available on your local machine and call public methods or set public properties on these add-ins. Furthermore, a COM add-in can provide a library of functions that you can use in all your custom Outlook forms.
Figure 6 shows a sample Outlook form that uses a COM add-in to launch other executable programs. Although VBScript doesn't support the Shell function, you can make use of the Shell function in your COM add-in.
Figure 6. A simple Outlook form that uses VBScript to leverage a COM add-in
Take a look at the following code from the Outlook form shown in Figure 6. Notice that you can access the COMAddins collection to retrieve a list of the add-ins on your machine. You can then check whether the COM add-in you're interested in is loaded and connected in Outlook. To retrieve a COM add-in, you use the Item method on the collection and either pass in the index of your add-in in the collection or pass a string that specifies the ProgID of the add-in. Notice how I use the GetObject method with the ProgID of the COM add-in.
You might think that I could simply use the Object property of the COM add-in object that corresponds to my add-in. However, there is a known bug in Outlook that does not allow you to use this technique to get the COM add-in object. You should use the workaround in the code to make the COM add-in library work:
Dim oCOMAddinObj Dim oCOMAddin Sub cmdLaunchWord_Click Launch "winword" End Sub Sub cmdLaunchCalc_Click Launch "calc" end sub Sub cmdLaunchApp_Click Launch item.userproperties.find("strAppPath").value end sub Function Item_Open() 'On error resume next Err.Clear 'Try to get a reference to the COM add-in Set oCOMAddin = Application.COMAddIns.Item( _ "OutlookHelper.Librar y") If err.number <> 0 Then MsgBox "There was an error retrieving a reference to the " _ & "COM Add-in Helper Library! Closing form!" Item_Open = False Exit Function End If 'Check to see whether the COM add-in is connected If oCOMAddin.Connect = False Then MsgBox _ "You must connect the COM Add-in before using this app!" Item_Open = False Exit Function End If 'Get the real COM add-in object 'This doesn't work in Outlook! Set oCOMAddinObj = _ Application.COMAddins.Item("OutlookHelper.Library").object 'Workaround: use GetObject Set oCOMAddinObj = GetObject("","OutlookHelper.Library") End Function Sub Launch(strAppPath) 'Get the Windows style iStyle = item.UserProperties.Find("strWindowsStyle").Value iError = oCOMAddinObj.CustomShell(strAppPath, iStyle) If iError = 0 then MsgBox "Error launching application!" End If End Sub
In the next example, the add-in doesn't do much besides add a single public function named CustomShell that the user can call. This function leverages the Shell function in Visual Basic and allows you to shell out to another program. The function also provides a bit of error checking in case some bogus values get past the Outlook test form. If the add-in's Shell call is successful, it will return the ID of the shelled out executable; if not, it will return 0:
Implements IDTExtensibility2 Dim oApp As Outlook.Application 'Global Outlook Application Object Private Sub IDTExtensibility2_OnAddInsUpdate(custom() As Variant) 'Not used, but must be defined. End Sub Private Sub IDTExtensibility2_OnBeginShutdown(custom() As Variant) Set oApp = Nothing End Sub Private Sub IDTExtensibility2_OnConnection( _ ByVal Application As Object, _ ByVal ConnectMode As AddInDesignerObjects.ext_ConnectMode, _ ByVal AddInInst As Object, custom() As Variant) Set oApp = Application End Sub Private Sub IDTExtensibility2_OnDisconnection( _ ByVal RemoveMode As AddInDesignerObjects.ext_DisconnectMode, _ custom() As Variant) 'Not used, but must be defined. End Sub Private Sub IDTExtensibility2_OnStartupComplete(custom() As Variant) 'Not used, but must be defined. End Sub Public Function CustomShell(strAppPath, iWindowStyle) If strAppPath = "" Then 'Return back an error Err.Raise vbObjectError + 513, "Shell Function", "A blank " _ & "pathname was passed!" Exit Function Else 'Check iWindowStyle If CInt(iWindowStyle) < 0 Or CInt(iWindowStyle) > 6 Then 'Make it normal with focus iWindowStyle = vbNormalFocus End If 'Try to execute the command and return the value iReturn = Shell(strAppPath, CInt(iWindowStyle)) CustomShell = iReturn End If End Function
Another way to fix this problem is to explicitly set the object reference for your COM add-in in Outlook's OnConnection event. The following is an example of how to use this workaround. You replace YourAddin.Connect with the ProgID of your COM add-in:
Private Sub IDTExtensibility2_OnConnection( _ ByVal Application As Obje ct, _ ByVal ConnectMode As AddInDesignerObjects.ext_ConnectMode, _ ByVal AddInInst As Object, custom() As Variant) Application.COMAddIns.Item("YourAddin.Connect").Object = Me End Sub
To reference the COM add-in object in your Outlook form, you use the following code:
Set myObj = Application.COMAddIns.Item("YourAddin.Connect").Object
Outlook 2000 and later supports VBA. Now, you might be thinking that your Outlook forms already support VBA, but this is not the case. In fact, you still need to write VBScript behind your Outlook forms. VBA support in Outlook provides a way to customize the Outlook environment using the Outlook object model and all the events just discussed without using a separate development tool such as Visual Basic.
When you write your VBA applications in Outlook, you must contain your code in a VBA project. Each project is associated with a particular user, which means that different users on the same machine can customize Outlook differently using VBA. These projects can contain code modules or user forms. (User forms are different from Outlook forms.) To share information among these VBA projects, you must export your file and have the receiving user import your file into her VBA project.
Creating a VBA Application
The first step in creating a VBA application is to launch the Visual Basic Editor in Outlook. From the Tools menu, select Macro. The Visual Basic Editor appears, as shown in Figure 7. You can use the editor to add class modules, code modules, and user forms, depending on the needs of your application. You can even write code that responds to Outlook events by declaring your variables using the WithEvents keyword.
The Outlook object model is automatically available to your VBA application. After you finish writing your macro in the editor, you can explicitly run it or create a button on your Outlook toolbar that runs the macro when clicked. Figure 8 shows a sample application that converts incoming mail to a specific message class using a VBA macro and the Outlook object model.
Figure 7. The Visual Basic Editor in Outlook
Figure 8. The sample mail conversion code in the Visual Basic Editor
Choosing What to Write: COM Add-In or VBA Program?
By now you must be wondering whether you should write VBA programs or COM add-ins to customize your Outlook environment. Both technologies have their merits, but if more than one user will run your program in an Outlook client, you should write a COM add-in. COM add-ins are easily distributed, and you can control a user's ability to run them.
If you want to customize only the Outlook client, writing a quick VBA program is easier than writing a full-blown COM add-in. To deploy your application in VBA, however, users must import the VBA file into their Outlook client, which is not the best deployment method. I predict that if you use COM add-ins, many users will install your application.
In this chapter, you learned how to build COM add-ins and use them to extend your Outlook applications. In the next chapter, we'll take what you have learned in the previous chapters on Outlook to create a rich, collaborative application that uses Outlook forms, views, folders, fields, and (of course) COM add-ins.