Integrating with Outlook

In this article I explore the possibilities of using Microsoft Outlook as an extension of your program, or vice versa, your program as an extension of Outlook. I assume that people are familiar with Outlook. If you have read this article you will be able to control Microsoft Outlook from your Delphi programs. You will also have a good idea of Outlook's object model. Finally you will be able to integrate straight into Outlook itself by extending Outlook's menu and toolbars.

There are two ways of integrating with Outlook:

  1. Your program calls Outlook when needed. Both programs are separate executables. You can do call Outlook, through COM, to do things like creating a message or reading the Contacts folder.
  2. Outlook calls your program when needed. Your program is a DLL which runs within Outlook. You can do everything you can with first option, but you can also add buttons and menu items to Outlook to Outlook's buttonbars and menus.

Why integrate with Outlook?

Both methods are discussed in this article. But why do you want to program Outlook? There are several ways in which Outlook can be used: However, don't be too ambitious. Outlook is not really multi-user aware, nor does it have folder synchronization capabilities. If you want to synchronize multiple Outlook Contacts folders without an intermediate database, you're better off with a different solution.

Programming Outlook through COM

Thanks to COM, it's easy to program Outlook to do things for you. The COM interface for Outlook 97 can be found in the type library MSOUTL8.OLB. On the disk you find it as Outlook8_TLB.pas. Outlook 98 understands this interface perfectly well, but also supports a new interface, see the type library MSOUTL85.OLB. As differences are small and most of you probably still have to support Outlook 97, I work with the Outlook 97 COM interface in the examples.

Every item in Outlook is covered by a separate object. The application as a whole is covered by the Application_ object. You always create an instance of this object first. Another object is the MailItem object, which covers mail messages, the ContactItem object covers contacts.

The first step in accessing Outlook is creating an application object. Through this object you can get the other objects. Creating the application object is as easy as:

  var
    app: Application_;
  begin
    app := CoApplication_.Create;
  end;

Sending a mail message

We have to ask the application object to create other objects for us. Let's create a mail message object and send someone a message:

  var
    app: Application_;
    mi: MailItem;
  begin
    app := CoApplication_.Create;
    mi := app.CreateItem(olMailItem) as MailItem;
    mi.To_ := 'berend@nothere.nl;
    mi.Subject := 'This is the subject';
    mi.Body := 'This is the message itself';
    mi.Send;
  end;

Through the method CreateItem we create a new mail message. We set its recipient, the subject and the message itself and send it. The message will be send under the identity of the currently logged on user.

Creating a contact

Contacts are created exactly the same:

  var
    app: Application_;
    ci: ContactItem;
  begin
    app := CoApplication_.Create;
    ci := app.CreateItem(olContactItem) as ContactItem;
    ci.FullName := 'Test person';
    ci.Companies := 'NederWare';
    ci.Save;
  end;

Outlook's object model

Let's take a look at Outlook's object model. It's shown in the following figure:

As shown, the Application object is the only object that's exposed. You access the other object through the Application object. The Application object has the following methods and properties to give access to other objects:

  1. CreateItem: method to create new objects of a certain class, i.e. create new journal entries, new tasks or new messages.
  2. ActiveExplorer: the window in which the contents of a folder are displayed, e.g. the list of email messages, or a calendar view. It gives you control over this window, letting you change the folder being viewed, access the command bars associated with a window, and close the window.
  3. ActiveInspector: the Inspector object is similar to an Explorer object, but at a lower level. It's the window in which an Outlook item is displayed, such as the contents of an email message. Use this object to get access to the item for manipulation, the Pages collection used to display the item, and change how the item is displayed. These names take a bit of getting used to, but they describe the objects well.
  4. Assistant: The Assistant is the shared Office Assistant object, a fully programmable object, e.g. "Clippit" the paper clip and "Earl" the cat.
  5. GetNamespace: the NameSpace object provides access to all the items stored in Outlook in the various folders. You'll use this object to create, catalog, and manipulate mail messages, journal entries, contacts, and every other Outlook item. You can also use NameSpace to create and delete folders. Microsoft describes this as an "abstract root object for any data source," used for logging in and out of Outlook and selecting a profile, gaining access to the default folders for each Outlook item type, and accessing other users' data stores. You're likely to spend much of your programming effort working with the NameSpace object.

We leave the Inspector and Assistant objects for another article. First an example of using GetNamespace. Although Outlook might support several name spaces in the future, currently only the "MAPI" name space is supported. You will always call GetNamespace with 'MAPI' as parameter.

Through GetNamespace you can retrieve folders. A particularly useful method is GetDefaultFolder. It returns the default folder where Messages, Contacts, Journals or Tasks are stored.

Displaying a contact

As soon as you have a certain folder you can either browse through its subfolders, or through its items. In the following example we demonstrate how you can go to the Contacts folder and display the first contact.

  var
    app: Application_;
    ns: NameSpace;
    cf: MAPIFolder;
    ci: ContactItem;
  begin
    app := CoApplication_.Create;
    ns := app.GetNamespace('MAPI');
    cf := ns.GetDefaultFolder(olFolderContacts);
    ci := cf.Items.Item(1) as ContactItem;
    ci.Display(0);
  end;

If you give a not-zero value to ContactItem.Display the contact is displayed modally.

Browsing through the tasks folder

In the next example we use the Items property of a folder to loop through all tasks and write them to a file.

  var
    app: Application_;
    ns: NameSpace;
    tf: MAPIFolder;
    i: Items;
    j: integer;
    ti: TaskItem;
    f: TextFile;
    s: string;
  begin
    app := CoApplication_.Create;
    ns := app.GetNamespace('MAPI');
    tf := ns.GetDefaultFolder(olFolderTasks);
    i := tf.Items;
    AssignFile(f, 'tasks.txt');
    Rewrite(f);
    for j := 1 to i.Count do  begin
      ti := i.Item(j) as TaskItem;
      s := ti.Subject;
      writeln(f, s);
    end;
    CloseFile(f);
  end;

Browsing through a restricted view to the tasks folder

You can also filter or restrict the items you access. Using the Restrict method of a folder you get a list of items restricted to the items of that folder that pass the given filter.

In the following example we loop through all active tasks and write them to a file.

  var
    app: Application_;
    ns: NameSpace;
    tf: MAPIFolder;
    i: Items;
    ri: Items;
    j: integer;
    ti: TaskItem;
    f: TextFile;
    s: string;
  begin
    app := CoApplication_.Create;
    ns := app.GetNamespace('MAPI');
    tf := ns.GetDefaultFolder(olFolderTasks);
    i := tf.Items;
    ri := i.Restrict('[Status]=0 ' +
                     'or [Status]=1 ' +
                     'or [Status]=3');
    AssignFile(f, 'tasks.txt');
    Rewrite(f);
    for j := 1 to ri.Count do  begin
      ti := ri.Item(j) as TaskItem;
      s := ti.Subject;
      writeln(f, s);
    end;
    CloseFile(f);
  end;
Both in the Restrict and Find call, you use the same filter expression language. Property names are identified and delimited by square brackets. You can use the comparison operators `>', `<', `>=', `<=', `=' and `<>'. Logical operators allowed are `and', `not' and `or'.

Sending an email with an attachment

The last Outlook COM example shows how to send an email message with an attachment. It looks just like the first example, but using the Attachments property (another object), a file is send, by value, with this message.

  var
    app: Application_;
    mi: MailItem;
  begin
    app := CoApplication_.Create;
    mi := app.CreateItem(olMailItem) as MailItem;
    mi.To_ := 'berend@nothere.nl';
    mi.Subject := 'Things to do';
    mi.Body := 'All active tasks are attached to this message.';
    mi.Attachments.Add('tasks.txt', olByValue, 0, 'To do list');
    mi.Send;
  end;
All examples in this section have been gathered in one program: comdemo.dpr. At the end of this example you find some information about how to compile it.

Extending Outlook's menu and toolbar

Controlling Outlook from the outside is not the only thing you can do. You can also hook inside. This technique already was available for Outlook's precursor, Exchange Client. That's why it is known as Exchange Extensions.

There are four different types of Exchange/Outlook client extensions:

  1. Command extensions: add new commands to Outlook's menubar or toolbar.
  2. Event extensions: Event extensions enable developers to add to or override behavior such as the arrival of new messages, reading and writing messages, sending messages, reading and writing attached files and tracking selection changes in a window.
  3. Property sheet extensions: useful for displaying custom form property sheets.
  4. Advanced criteria extensions: extend Outlook's search capabilities.

In this article we'll cover the first item. So we learn how to add menu items to Outlook's menubar, and how to add buttons to its toolbar.

Every Outlook Extension is written by implementing a COM interface, the IExchExt interface. As you see from the naming, this interface is inherited from the Exchange Client days so a lot will work under Exchange too.

Example 1: adding a menu item to Outlook

Let's first implement the famous Hello World example. We'll add a menu item to Exchange, see the following figure.

When you choose it, you will get a Hello World dialog box, see the following figure.

This example does work with both Outlook and Exchange so I'll use the word Exchange in this example and mean both Microsoft MAPI clients. There is a minor difference between Outlook and Exchange in this respect. Exchange loads extensions at startup, Outlook can load them when required. This will not affect the examples I present here in anyway.

Exchange client extensions live in DLL's. This DLL needs to have an entry point, a procedure which is exported with the ordinal 1. Exchange calls this procedure when it loads the DLL. The name of this entry point is not important, only its ordinal is. I've called it ExchEntryPoint. ExchEntryPoint needs to return the COM object which implements the extensions. This COM object should implement the IExchExt interface (see table 1).

Table 1: IExchExt

Method

Description

Install

Enables an extension object to determine the context into which it is being loaded, along with information about that context.

The most simple entry point looks like this:

function ExchEntryPoint: IExchExt; cdecl;
begin
  Result := TExchExt.Create;
end;

Unfortunately, it's not as simple as this. Due to a bug in Delphi's compiler (all versions) we need to write a bit more complicated code as you can see in the example code.

The IExchExt interface has just one method: Install. This method returns True (or in COM terms S_OK) when this extension runs in a certain context. Examples of contexts are the viewer context, that's Exchange main window, or the remote viewer context, or the address book context, or the property sheet window context. So if you want to add a menu item you should return S_OK when Exchange asks you if you run in the EECONTEXT_VIEWER context. Every context has a unique number, assigned by Microsoft, prefixed by EECONTEXT_. A simple implementation of Install which wants to be active in Exchange main window looks like this:

function TExchExt.Install(
  eecb: IEXCHEXTCALLBACK;
  mecontext: ULONG;
  ulFlags: ULONG): HResult;
begin
  case mecontext of
    EECONTEXT_VIEWER: Result := S_OK;
  else
    Result := S_FALSE;
  end; { of case }
end;

So now you have informed Exchange that you can do something in the context viewer window. To actually do something there, you have to implement another interface, the IExchExtCommands interface (see table 2). This interface has some more methods, 7 in total.
IExchExtCommands (table 2)

Method

Description

InstallCommands

Enables an extension to install its menu commands or toolbar buttons.

InitMenu

Enables an extension to update its menu items when the user begins using the menus. You could for example enable/disable menu items.

DoMenu

Carries out a menu or toolbar command chosen by the user.

Help

Provides user help for a command. It's called when a user chooses What's this from the Help menu and clicks the menu item or toolbar button.

QueryHelpText

Provides status bar or tool tip Help text for a command. Only visible with the Exchange Client, not with Outlook.

QueryButtonInfo

Provides information about the extension's toolbar buttons.

ResetToolbar

Enables an extension to restore its toolbar buttons to their default positions.

In this first example, we only need to implement two of them. The first is InstallCommands. In InstallCommands you can add menu items to Exchange or Outlook's menubar. Code looks like this:

function TExchExt.InstallCommands(
  eecb: IEXCHEXTCALLBACK;
  hwnd: HWND;
  hmenu: HMENU;
  var cmdidBase: UINT;
  lptbeArray: LPTBENTRY;
  ctbe: UINT;
  ulFlags: ULONG): HResult;
var
  r: HResult;
  hMenuTools: Windows.HMENU;
begin
  r := eecb.GetMenuPos(EECMDID_ToolsOptions, hMenuTools, nil, nil, 0);

  // add our extension command
  MyCommandNum := cmdidBase;
  AppendMenu(
    hMenuTools,
    MF_BYPOSITION and MF_STRING,
    MyCommandNum,
    'Hello World 1');
  Inc(cmdidBase);

  Result := S_OK;
end;

In the first line we use the Exchange callback interface to retrieve the handle to the Tools submenu. The Exchange callback interface can be used to retrieve information about Exchange's current state, see table 3 for its interface. In the next lines we append the menu item "Hello World 1" to this menu. You can use the standard Win32 API command AppendMenu to accomplish this. You may not choose any command number you want. Exchange passes the first command you want to use in cmdidBase. If you have added a menu item you must increment it, so Exchange knows that you did add something. Outlook even deletes menu items you have added, if you didn't increment cmdidBase properly! I store the command number I my menu item is bound to in MyCommandNum. We need it later.

The menu item is now shown, but nothing happens when a user selects it. When a user chooses a menu item (or toolbar button) method DoCommand is called with the command number. You need to implement DoCommand to add functionality to your menu items. An example DoCommand implementation looks like this:

function TExchExt.DoCommand(
          eecb: IEXCHEXTCALLBACK;
          cmdid: UINT): HResult;
begin
  if cmdid = MyCommandNum
    then  begin
      ShowMessage('Hello World 1!');
      Result := S_OK;
    end
    else
      Result := S_FALSE;
end;

As you see I've used the command number I'd saved in MyCommandNum to see if it really is my command. You need to return S_OK if you did handle a command, else return S_FALSE.

For this first example I've implemented two more methods: QueryHelpText and Help. QueryHelpText shows status line help in the Exchange Client (not within Outlook). And Help is called when a user chooses "What's this" and selects your menu item or toolbar button. Both get the command which is selected and you use an if statement similar to the one shown in DoCommand to see if it really is your command.

IExchExtCallback (table 3)

Method

Description

GetVersion

Returns the version number of the Microsoft Exchange application.

GetWindow

Returns a window handle corresponding to the specified flag.

GetMenu

Returns the Microsoft Exchange menu handle for the current window.

GetToolbar

Returns a toolbar's window handle.

GetSession

Returns an interface to the current open MAPI session and associated address book.

GetObject

Returns an interface and store for a particular object.

GetSelectionCount

Returns the number of objects selected in the window.

GetSelectionItem

Returns the entry identifier of a selected item in a Microsoft Exchange window.

GetMenuPos

Returns the position of a command or set of commands on the Microsoft Exchange menu.

GetSharedExtsDir

Returns the Microsoft Exchange shared-extensions directory.

GetRecipients

Returns a pointer to the recipient list of the currently selected item.

SetRecipients

Sets the recipient list for the currently selected item.

GetNewMessageSite

Returns interface pointers to the message site and view context of the selected message.

RegisterModeless

Enables extension objects that display modeless windows to coordinate with windows displayed by the Microsoft Exchange client.

ChooseFolder

Displays a dialog box that enables users to choose a specific message store and folder.

The final step is to register your DLL within Exchange Client or Outlook. Make sure the following registry entry is present:

  HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Exchange\Client\Extensions

Sometimes the Extensions key is not present, so you need to create it. Next add a registry value to the Extensions key. The name of the value is your description. The value itself looks like this:

  4.0;e:\winnt\system32\helloworld1.DLL;1;010000;1000000

The value is a semicolon separated file. The first value is the Exchange Client level you want to be active in. The value can be either 4.0 or 5.0. Next comes your DLL location. The 3rd field contains the ordinal of the exported function which Exchange should call. This value can be left empty, it defaults to 1. This is the ordinal of our ExchEntryPoint function.

The bit values in position 4 and 5 are optional. Field 4 is called the ContextMap, field 5 the InterfaceMAP. The ContextMAP tells Exchange in which contexts your DLL is active, so Exchange can load the DLL only when needed, see table 4. The InterfaceMAP tells Exchange which interfaces you have implemented, see table 5. Exchange is able to find out all this, but telling it beforehand could improve performance.

Context Map Bit Positions (table 4)

Position

Context

1

EECONTEXT_SESSION

2

EECONTEXT_VIEWER

3

EECONTEXT_REMOTEVIEWER

4

EECONTEXT_SEARCHVIEWER

5

EECONTEXT_ADDRBOOK

6

EECONTEXT_SENDNOTEMESSAGE

7

EECONTEXT_READNOTEMESSAGE

8

EECONTEXT_SENDPOSTMESSAGE

9

EECONTEXT_READPOSTMESSAGE

10

EECONTEXT_READREPORTMESSAGE

11

EECONTEXT_SENDRESENDMESSAGE

12

EECONTEXT_PROPERTYSHEETS

13

EECONTEXT_ADVANCEDCRITERIA

14

EECONTEXT_TASK

Interface Map Bit Positions (table 5)

Position

Interface

1

IExchExtCommands

2

IExchExtUserEvents

3

IExchExtSessionEvents

4

IExchExtMessageEvents

5

IExchExtAttachedFileEvents

6

IExchExtPropertySheets

7

IExchExtAdvancedCriteria

Outlook has an additional way to register extensions. They are called Extension Configuration files (.ECF). I suggest you stay away from them as they are not well documented and do not really add something important. But if you want to give it a try, see the section called "Further Reading" at the end of this article.

To summarize: the steps one needs to take to implement an Exchange Client extension are:

  1. Create a DLL with the ExchEntryPoint. This DLL should return a pointer to the IExchExt interface.
  2. Implement the IExchtExt interface.
  3. Implement the IExchExtCommands interface.
  4. Register your DLL so Outlook/Exchange Client knows about it.

You can find the complete implementation in HelloWorld1.dpr, our DLL, and ComHelloWorld1, the implementation of IExchExt and IExchExtCommands. The implementation could be quite simple because Delphi allows us to implement more than one interface in an object (no need to implement IQueryInterface for example).

Example 2: adding buttons to Outlook

In the second example I expand a bit on the previous example. I'll show you how to add a button to the toolbar and how to add a menu item in a different context. This example also shows that you can as easily program Outlook using the techniques from the first section of this article.

Let's take a look at the source code for the second example. Example2.dpr is the DLL, and ComHelloWorld2, the implementation of IExchExt and IExchExtCommands. Because this extension should run only within Outlook, IExchExt.Install has a check to see if it's called from Outlook or not.

Adding menu items is done as in the first example. One thing is different, I add a menu item only to the New Message window, not to Outlook's main window. To add a menu item (and toolbar button) only in the new message window, and not in Outlook's main window , I store the current context within IExchExt.Install. When IExchExtCommands.InstallCommands is called next, I use it to decide if I have to add the menu item or not.

Adding a button to the toolbar is a two step process. First make the button image available, and second install the button. Making the button image available is done when IExchExtCommands.InstallCommands is called. In the main resource file of example 2, Example2.RES, I've added two button images which the names 101 and 102. Within InstallCommands I use these names to add the proper image to the toolbar list of images. Reserving a command number, incrementing the cmdidBase parameter, is still done within InstallCommands.

After that IExchExtCommands.QueryButtonInfo is called with various parameters, the most important is ptbb, a pointer to a PTBButton structure. The properties of ptbb have to be set to display the button.

IExchExtCommands.DoCommand contains the actual implementation of manipulating Outlook. I've written three examples. In Outlook's main toolbar a new button has appeared with the number 1 on it. If you click it, the subjects of all messages in the current folder are written to the file dump.txt in your temporary directory. In the new message window a new button has appeared with the number 2 on it. If you click on it, a journal item of type 'E-mail message' is created and displayed. You can save or cancel it. As the last item under the Tools menu of the new message window you find the 'Create a contact' item. If you select it, a new contact is created and saved immediately. Go to your Contacts folder to see it.

Registering example2.dll is done like in the first example. The correct registration key is:

4.0;c:\winnt\system32\Example2.DLL;1;010001;1000000

Example 3: show Delphi forms within Outlook

It's just as easy to show any Delphi form instead of the "Hello World" in Example1. Just add a form to your Extension and in DoCommand create and show it.

The following piece of code demonstrates this technique:

function TExchExt.DoCommand(
          eecb: IEXCHEXTCALLBACK;
          cmdid: UINT): HResult;
begin
  if cmdid = MyCommandNum
    then  begin
      Form1 := TForm1.Create(nil);
      Form1.Show;
      Result := S_OK;
    end
    else
      Result := S_FALSE;
end;

Example 4: add Delphi ActiveX controls to Outlook forms

Adding ActiveX controls written in Delphi is also possible. Any ActiveX control you've created with Delphi can be shown in an Outlook form. The steps you need to take:
  1. Create an ActiveX control.
  2. Register it.
  3. Choose Design a form within outlook.
  4. Choose the Control Toolbox.
  5. Left click it. A menu pops up.
  6. Choose "Custom Control...".
  7. Choose your ActiveX control from the list.
  8. Drop it on the form.
For example, you could create an ActiveX control which can display a Paradox table so the user can choose a value from it. Or you could add a button to store the contents of the current form in a database.

Compiling the examples

Five example programs are provided with this article: COMDemo.dpr demonstrates accessing Outlook using its COM interface. The programs Example1.dpr through Example4.dpr demonstrate various Exchange Extension techniques.

To be able to compile the examples, you need some libraries on your search path. You can translate the outlook object library from MSOUTL8.OLB, included with Office 97 or later, but it's also included with the source for this article as Outlook8_TLB.pas in the Outlook subdirectory. This directory also contains two other files you need as MSForms_TLB.pas and Office_TLB.pas.

For the standard Exchange Extensions you need access to the MAPI headers. Inprise didn't translate them, but luckily a good guy named Alexander Staubo provided a very complete translation. This translation is included in the mapi subdirectory.

You also need the header for the Exchange Extension interface which I've partly translated. You find it also in the mapi subdirectory as ExchExt.pas.

All directories have a compileit.bat file and a dcc32.cfg file. This should make compiling the sources a breeze.

Further reading

Outlook's object model

At http://www.microsoft.com/OutlookDev/Articles/Outprog.htm you get a good understanding of how to things ought to be done when programming Outlook. From browsing through Outlook8_TLB.pas you get a good understanding of things which are possible with Outlook. There also exists a help file, vbaoutl.hlp, which covers all objects and properties. This helpfile can be found on the Office 97 CD in the /VALUPACK/MOREHELP directory.

Some parts of this article have been taken from "The Outlook on Access: Using Outlook 97 from Access 97 (and Vice Versa)" by Don Kiely, which has some interesting samples. Another article with samples is "Automating Microsoft Outlook 97" by Mike Gilbert. An aricle giving showing examples for new Outlook 98 features is "Automating Microsoft Outlook 98" by Mindy Martin, also on the MSDN CD. There also is a book devoted to Outlook: "Building Applications with Microsoft Outlook 98, New Edition". Krebs, Peter, Microsoft Press, 1998, ISBN 1-57231-718-3.

Exchange Extensions

The main guide for the Exchange Extension programmer is the "Extending the Microsoft Exchange Client" guide. You find it on the MSDN CD. It's also available at Microsoft's website.

Another article on the MSDN CD is "Microsoft Outlook and Exchange Client Extensions" by Sharon Lloyd.

Using ECF files to install Exchange Extensions

More information about using .ecf files to install Outlook extensions is in the article On the MSDN CD you can find an article called "Outlook Extension Configuration File Format". You can find it at http://www.microsoft.com/office/ork/ORKTools/Document/Outlook/Ecf.htm.

Sites devoted to Outlook and MAPI programming

At http://www.angrygraycat.com/goetter/mdevfaq.htm you find a good FAQ regarding MAPI and Exchange Extension programming.

Conclusion

You should be able to program Outlook, sending mail and reading contacts. Also you should have a good start in writing Exchange Extensions.