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:
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;
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.
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;
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:
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.
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.
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;
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'.
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.
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:
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.
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).
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.
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.
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.
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 |
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:
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).
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
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;
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.
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.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.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.
At http://www.angrygraycat.com/goetter/mdevfaq.htm you find a good FAQ regarding MAPI and Exchange Extension programming.
You should be able to program Outlook, sending mail and reading contacts. Also you should have a good start in writing Exchange Extensions.