Microsoft Outlook has an object model that's useful for automating any of the objects that it manages. Items like calendar entries, e-mails and tasks are well designed, but sometimes they don't provide all the functionality that we'd like. For instance, Outlook doesn't provide an easy way to assign a different e-mail background for each of your contacts. Thankfully, Outlook exposes its object model so you can build your own extensions to it. Unfortunately, because it has such a well-designed extensible object model, it's the target of a hoard of viruses and is therefore protected with a security model. This article is designed to help you understand some of the problems involved in developing for Outlook with the .NET Framework, as well as give you some pointers to helpful articles for future development.
Creating Your First Outlook Plug-in
Creating plug-ins for Outlook is identical to creating plug-ins for any other Office application. Follow these steps to create your application:
The entry point for your application is in the Connect class. This class implements the Extensibility.IDTExtensibility2 interface. This interface must be implemented for a class to be an Add-in to another application. This doesn't mean every class in your application must implement this interface. In fact, the Connect class should be the only class that implements this interface in your application. When your plug-in is registered with the application that will launch it, the application is told which object will be instantiated, and there can only be one.
To make sure everything is ready to go, place a breakpoint inside the OnConnection routine, and try to run the application. Outlook should launch, and then should pause (after a few seconds) at that breakpoint. If Outlook doesn't launch, and some other application like Visual Studio does, then you need to change the "Start Application" inside the "Configuration Properties/Debug" section of your project properties to the path and file name of Outlook.
The Outlook Security Model
When you try and run your Add-in, Outlook is launched. The first thing Outlook does when it launches is discover what Add-ins it's supposed to create, and then creates them. To run your Add-in, an instance of your Connect class will be created, and then each method in the Extensibility.IDTExtensibility2 interface will be called in a particular order. The only ones that I use, and probably the only ones you will use, are the OnConnection and OnBeginShutdown methods.
When Outlook calls OnConnection, it will pass on the most important object your Add-in will use: the Application object representing the Outlook Application. This object is important because this is the only instance of the Application class your Add-in is allowed to use. Most objects in the Outlook model have an Application property that gets a pointer to the Application object. If you use any of these properties to get at the Application object, your Add-in will be shut down because Outlook can't tell if your Add-in has authorized access. It can't tell the difference between your Add-in and some VB Script.
The first thing I do once a project is up is to open my Solution Explorer, right-click on "references" in the main project, click "add reference" and add a reference to the "Microsoft Outlook Objects" COM library. This makes the Outlook objects easier to get and manipulate in code. Then I create a static Application property (shared in Visual Basic) on the Connect object that exposes an Outlook.Application object. Then I change the OnConnection routine to store the application parameter in that property. This way, any other class in my project that needs access to the Application object has easy access to it:
//In constructor
Garbage Collection Gone Bad
Outlook is mostly used to send and receive e-mails. To get our code to register for events, such as when a new e-mail message is being created, we need to use Inspectors. An Inspector is a window that appears when you create a new Outlook object interactively. The Application object has a property called "Inspectors" that contains a list of all the currently open inspectors as well as an event that is raised when a new inspector is created: NewInspector. This event is easy to register for:
But, this event is raised whenever an item is opened, not just when a new item is created, so we need some filtering code to ensure that the new item is a new e-mail:
Notice that I catch any exceptions raised in this event handler. If Outlook finds your Add-in throws exceptions, it will disable it. So, in every routine that handles an event raised by Outlook, I put a try-catch block to ensure my application continues to operate.
Now, run your application (using the debugger) and create a new e-mail item. You will find that it will output "New Mail Item!" to the console. Close that Inspector, and create another mail item. No new text will be written to the console.
What happened? The Garbage Collector collected the delegate registered for the event. This problem is caused by a combination of the COM Callable Wrapper (CCW) generated by the .NET Runtime for interoperability with Outlook and the way we referenced the NewInspector event. When the Inspectors collection is accessed from the Application object, Outlook hands back an Inspectors object that the .NET Runtime tracks through a Runtime Callable Wrapper (RCW). Then when the application registers for the NewInspector event, a delegate is added to that event through a CCW. When the first event is raised, the delegate is called as expected. Unfortunately, as soon as the Runtime notices that the event has finished it runs the garbage collector; it cleans up the instance of the Inspectors class originally handed to the Runtime because there's no longer a reference to it. As a result, the delegate that points to the event handler is also cleaned up, and you no longer receive the event. This is illustrated in Figure 1. To fix this, the registration code in Snippet 2 needs to be changed to Snippet 4. Now the event will always be raised whenever a new e-mail is created.
Unfortunately, the Inspectors object is not the only place that this problem can occur. Any object that you register for an event on, but don't keep a reference to, can fail to raise the events you expect. To overcome this, I suggest a coding structure that keeps a reference to every Outlook object that gets returned by any de-reference of another Outlook object existing item. To do this we can create a series of wrappers that keeps track of a single instance of an Outlook object. Accompanying this article is an EmailWrapper class that I use to track all e-mails that I have open. Listing 5 shows code from my EmailWarapper constructor.
Notice that the two lines of code that register for events are substantially different. There's a problem with many of the objects exposed by Outlook; many classes in the Office object model have methods and events with exactly the same name. When the RCW definitions are generated for the office objects, the TlbImp tool (Type Library Importer) favors the methods in the definition. Thankfully, the TlbImp tool provides a way to get at the events: in this case the ItemEvents_Event interface that lists all the events exposed by that object. I can cast to this interface, and then register for the event I want.
More Outlook Security Model Pickiness
Starting with Outlook 97 Service Pack 2, Outlook got very picky about the objects that attach to it and use its objects. This is meant to help prevent scripts and viruses from spreading themselves through Outlook. But this security system gets in the way of legitimate applications that want to use the Outlook object model for constructive reasons. Luckily, there are some ways around the security system.
If you are producing add-ins for a corporate environment then there's a very straightforward solution. Create a COM shim to your add-in. You can look up the details for how to do this on msdn.microsoft.com, but be careful how you use it. A shim can be used in a corporate environment if the corporation has an Exchange server that specifies the security settings of each client. The Exchange server can be told that your particular shim is a valid add-in, and the shim will pass all of its calls to your .NET add-in. But be warned. Read the warnings associated with the shim carefully, otherwise you may make Outlook less secure.
If you are developing Add-ins for home users, who don't use an Exchange server to configure their client's security settings, then you should buy a program called "Outlook Redemption" (www.dimastr.com/redemption/). Because the Outlook security model only applies to the COM connectivity of Outlook, this allows you to connect through the MAPI protocol.
Outlook communicates with an Exchange server through MAPI, and everything that Outlook exposes through COM is also exposed through MAPI. Unfortunately, MAPI is a difficult protocol to learn and use and is usually developed through C and C++. However, Outlook Redemption communicates with Outlook through MAPI and exposes its actions through COM. This allows you to code as if you were interacting directly with Outlook through COM, but since it's really communicating through MAPI, Outlook doesn't stop your application because of security concerns.
The reason Outlook isn't concerned about MAPI-based automation is because it's difficult to build and use MAPI applications and most (if not all) virus writers would like to create and use the easy COM-based object model. If you are really courageous, you could learn MAPI so that you can write code that interfaces with Outlook directly, avoiding these security issues, but I wouldn't recommend it.
Conclusion
Outlook has a powerful object system, but its security system and COM/.NET interoperation get in the way of producing useful automation applications. The security system can be worked with through a shim or with "Outlook Redemption." By always having references to all the COM objects you get from Outlook, you can avoid problems that garbage collection can cause to surface. Knowing about these problems before you create .NET add-ins can help you avoid headaches during development and deployment.