Introduction

In this article, you will learn how to intercept an Outlook dialog and replace it with your own .NET form. The Outlook Object Model itself exposes no events and objects to replace the built-in dialogs with your own. However, by combining VSTO, P/Invoke, and .NET technologies, you have the ability to replace any kind of Outlook built-in dialog you can imagine.

Your own business logic in address selection, such as searching and resolving names

Drawbacks

Use of complicated unmanaged code

Depends on the version and language of your installed Office

Background

The Microsoft Outlook Object Model (OOM) is powerful, and provides access to many features that use and manipulate the data stored in Outlook and on the Exchange Server. Here are some common options to use external data in Microsoft Outlook:

Importing the data into Outlook Items (Contacts)

Importing data into the Exchange store using WebDAV or CDOSys

Creating your own address book provider

Importing / Exporting data is time consuming and a reason for synchronization conflicts. The data is outdated and out of sync. Here is a new scenario that shows how to intercept an Outlook built-in dialog and replace it with your own.

The idea

What you definitely can get from the Outlook Object Model are Explorer and Inspector objects which represent application and data item windows. Luckily, whenever such a window is activated by the user (when someone clicks on it), or when it is deactivated (when another window comes to the front), you will receive events from these objects. You can use these events to get notified when windows are activated or deactivated. This is also true when a user clicks on the "To" or "Cc" button to select a recipient from the Recipient dialog. Whenever the Address / Recipient dialog is shown, your inspector window is deactivated. You can intercept and search for the opened Recipient dialog in the Deactivate event handler, close the Recipient dialog, and open your own .NET form instead.

Set up the solution

Before you can start hacking into Outlook, you have to install the minimum requirements on your development machine.

Create a solution

After you have created the project, you will find a skeleton class called ThisAddIn with two methods where the application is started and terminated. Here, the journey begins, and we will start to code the application.

The Outlook InspectorWrapper template

As you can read in many articles, one of the most common problems when programming Outlook add-ins is the fact that you can have multiple Explorers and Inspectors opened and closed at any time during the lifetime of your Outlook session. One method of handling this situation correctly is by using an Explorer/Inspector wrapper which encapsulates each of these windows and traps the events and states of the different windows during their lifetime, and which does a proper cleanup to avoid ghost instances and crashes of your Outlook application. This technique is also common for IExtensibility add-ins. Reference: InspectorWrapper Sample(H. Obertanner).

The Inspector/Explorer wrapper template is basically a wrapper class which has a unique ID, holds a reference to the wrapped object inside the class, monitors the object state, and informs the application when the object has been closed. Here it goes:

The Explorer wrapper class is similar to the Inspector wrapper class. Refer to the sample solution to see additional details. In fact, what you now have is a small framework which could be used to successfully build your VSTO add-ins.

Search the built-in Recipient dialog

Now that you have arranged to be informed when your Inspector window becomes inactive (because you will receive the Deactivate event), you can search for the Recipient dialog now. You can't do it with .NET managed code - you have to use the good old Windows API for it. This technique is called P/Invoke, and it's the way to access unmanaged API DLL functions, methods, and callbacks from your managed code. The best online resources for information about P/Invoke are the MSDN Windows API documentation and a website called pinvoke.net.

Before you can search for the dialog/window, you have to know what to search for. Luckily, with Visual Studio, you get a small tool called Spy++. You can use this tool to search for windows, messages, and even to find the parent and child windows of any window. Start Microsoft Outlook, create a new mail, and select a recipient. When the built-in Recipient dialog is shown, start the Spy++ tool. It's usually located under "C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\Tools folder" on your hard disk. Now, you can use the "Find Window" function and drag the target onto your Recipient dialog.

Using Spy++ to get information about the Recipient dialog:

What you will get is some information about the window you selected. You will get information about the window text, the class name, and the handle. The handle is a dynamically assigned unique address of the window in your system. Because it's dynamically assigned, it changes every time the dialog is opened, and therefore isn't helpful here. The caption (title or window text) changes depending on the application context, and doesn't help us here either. How can you identify the window? The answer is not 42 - it's by the class name and by its child windows.

Here now is a small challenge for you: Since I coded this sample with a localized version of Outlook, you have to modify the code to suit your needs and locality. All controls on the Recipient dialog are windows too, they are child windows of the Recipient dialog. The next snippet demonstrates how to use some Windows API functions to:

search for a window handle with the class name #32770

enumerate all child windows of the dialog

retrieve the window text of all the child windows

see if all the required children are there to successfully identify the Recipient dialog

The method to retrieve a list of all child windows and their window text is encapsulated in a managed method to keep all API calls inside of a class.

The interesting thing here is how to pass a managed generic list to an unmanaged API function by allocating an unmanaged handle to your managed object.

Now, you want to use it in your InspectorWrapper to identify the window. Every time your Inspector is deactivated, let's go and search for the dialog.

The Event sink for the Inspector Deactivate method will look like this:

///<summary>/// Event sink for the Deactivate event
///</summary>void InspectorWrapper_Deactivate()
{
// check for a Dialog class
IntPtr hBuiltInDialog = WinApiProvider.FindWindow("#32770", "");
if (hBuiltInDialog != IntPtr.Zero)
{
// ok, found one
// let's see what child windows there are
List<intptr> childWindows = WinApiProvider.EnumChildWindows(hBuiltInDialog);
// Let's get a list of captions for the child windows
List<string> childWindowsText = WinApiProvider.GetWindowNames(childWindows);
// now check some criteria to identify the built-in dialog..
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// !!! This part is only valid for German Outlook 2007 Version !!!
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
if (!childWindowNames.Contains("Nur N&ame")) return;
if (!childWindowNames.Contains("&Mehr Spalten")) return;
if (!childWindowNames.Contains("A&dressbuch")) return;
// you can even check more criteria
// OK - we have the built-in Select Names Dialog
}
}</intptr>

Closing the built-in dialog

You have mastered the first exercise - identify the built-in dialog. You have the handle to it, and now you have to close the dialog. When you have a managed .NET form, this is easy - but if not, it's a little trickier. In the Windows API, two methods are documented:

You can't use either of them. Why? When you are receiving this event, the built-in dialog is not initialized completely and it runs in another thread. But, the whole Windows system is based on a message loop where windows exchange messages to interact together. So, you simply send the built-in dialog a Close message. This is the same effect as pressing ESC on the visible window. The window frees all used resources and closes properly. When the window has been closed, your Inspector window will become active again and you will receive an Inspector_Activated event.

In the next code block, you will see how to close the window and the activate method that is used to display our own dialog:

The picture below shows the design of the .NET form used to replace the built-in dialog.

Basically, it has a DataGridView, To, Cc, and Bcc buttons with corresponding textboxes, and a Search button with a combo box. You want to have cool looking modern functionality, and so we want to filter the Recipient list while typing into the combo box. The combo box should display the last used search phrases.

To realize the filtering, you can use a DataView with an attached DataSet. The DataSet can be easily designed with Visual Studio and used as the DataSource for the DataView. So, go ahead and add a new DataSet to the application with the specific fields for the recipients.

Different data sources

The implementation of this dialog is whatever you can imagine - it depends on what your requirements are. Just to give you a start, you will use three different data sources to fill up your new Recipients dialog in this sample.

Internal Outlook data by using the Table object (Outlook 2007 only)

An external XML file

SQL data using LINQ to SQL with a corresponding database

First, you will access the data of the Contacts folder. In the past, you had one of these options to access the Outlook internal data:

New in Outlook 2007 is a Table object which provides fast access to an Outlook folder's contents. You will use it as shown below to get the contents of the personal Contacts folder and populate the custom dialog. The helper methods are in a class called OutlookUtility. You have to pass the name of the columns you want to retrieve, and you can apply a filter on the table items.

Now, take a closer look at the implementation of the .NET form. As mentioned earlier, you will use a backgroundworker to pump the data into your new dialog. Also, you have to connect the dialog to your Inspector's data, so that the recipients that you have selected shows up in the mail somehow and vice versa.

You can achieve this by passing the Inspector's CurrentItem object to the form and by modifying the item directly within the Recipient dialog. The corresponding code is shown below:

Now, you have the dialog connected to your Inspector and you should fill it with data. You want to maintain a responsive application, so the decision is to use a backgroundworker for your application. You start with Outlook Contacts Folder data. The theory says: create a background thread, get the folder table, loop over the data, and add it to your dataset. While looping over the data, show the progressbar. When finished, enable all user-elements.

Take a break now. You should have an initial functional add-in now, and the design goals are reached to this point (with minor bugs). You can download this solution as Part 1 from here now and study the code.

Using LINQ to SQL to query external data

You want to use an external SQL database for your addresses. Fine - let's create one. In an enterprise, usually, you would use a central SQL Server. Here you use a local database, created by yourself with the SQL Server Express Edition and the new LINQ language extensions.

First, you need to add a reference to the System.Data.Linq DLL.

In this scenario, you have just one simple entity, so call it "Customer". You will create a fresh database if one doesn't exist already and add some customers to it. Also, two methods to retrieve the data back from the database into entities would be helpful for the application. Note: All DB related classes are placed in a subfolder/namespace called "Database".

In your customized dialog, you will use the new data source and create a new backgroundworker that will load the data from the database and fill up your Address dialog with data.
What you are missing is the connection string for your database. You have to save the database somehow where you have the possibility to write files. Where depends on how restricted your account on your system is - at the minimum, you can write to the MyDocuments folder. How can you get it? In .NET 3.5, this is easy with:

Caution! There is a problem here when you try to create your database on the fly - you will receive a no access exception. This is because the SQLEXPRESS instance has no rights to access your personal directory, by default. But, you are smart and give the service the rights to do something in this directory.

Just to make it easy, you give control to the Networkservice here (in German, Netzwerkdienst):

Outlook specific tweaks

As you can see, I'm a German guy, and I'm using a localized version of Microsoft Outlook. However, how do you find out if you have a German or an English version currently running? In the Outlook 2007 Object Model, there is a property called Application.LanguageSettings. A language ID of 1031 (0x407) stands for German - an ID of 1033 (0x409), for the English version of Outlook. Many thanks to Ken Slovak here for providing me with the English screenshots of the Select Recipients dialog. The following code snippet will give you an idea of how you can retrieve the local language:

Now, whenever you click the "To", "Cc", or "Bcc" button on an Inspector window, you will notice that before your own dialog shows up, you will see the original Select Recipient dialog flashing up in the background. The only way to suppress this window showing up is by using a bad trick. We have to create a new invisible window and make the original window a child of it. You need two additional API calls - one for creating a new window and one for changing the parent of a window.

Here are the needed API calls:

///<summary>/// Set a new parent for the given window handle
///</summary>///<paramname=""""hWndChild""""/>The handle of the target window</param/>///<paramname=""""hWndNewParent""""/>The window handle of the parent window</param/>[DllImport("user32")]
publicstaticexternIntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
///<summary>/// Create a new window.
/// Description see http://msdn2.microsoft.com/en-us/library/ms632680.aspx
///</summary>///<paramname=""""dwExStyle""""/>Specifies the extended
/// window style of the window being created</param/>///<paramname=""""lpClassName""""/>A class name -
/// see http://msdn2.microsoft.com/en-us/library/ms633574.aspx</param/>///<paramname=""""lpWindowName""""/>Pointer to a null-terminated
/// string that specifies the window name</param/>///<paramname=""""dwStyle""""/>Specifies the style
/// of the window being created</param/>///<paramname=""""x""""/>The window startposition X</param/>///<paramname=""""y""""/>The window startposition Y</param/>///<paramname=""""nWidth""""/>Width</param/>///<paramname=""""nHeight""""/>Height</param/>///<paramname=""""hWndParent""""/>Parent window handle</param/>///<paramname=""""hMenu""""/>Handle to a menu</param/>///<paramname=""""hInstance""""/>Handle to the instance
/// of the module to be associated with the window</param/>///<paramname=""""lpParam""""/>Pointer to a value
/// to be passed to the window through the CREATESTRUCT structure </param/>///<returns>If the function succeeds,
/// the return value is a handle to the new window</returns>[DllImport("user32.dll")]
publicstaticexternIntPtr CreateWindowEx(
uint dwExStyle,
string lpClassName,
string lpWindowName,
uint dwStyle,
int x,
int y,
int nWidth,
int nHeight,
IntPtr hWndParent,
IntPtr hMenu,
IntPtr hInstance,
IntPtr lpParam);
}

Now, the annoying original dialog is gone. That's it for now. With this technique, now you have the opportunity to change any dialog within Outlook. This is also true for Print dialogs, etc. Now, go on and extend your Outlook customization with more functionality and added benefit for your customers.

Resume of Part 3:

Determining the current UI setting of your Outlook instance

Suppress the annoying original Select Recipients dialog flashing up by changing the parent window

Share

About the Author

I'm a 1968 model, made in Germany.
After School transformed into an electronic engineer and started repairing computers in 1986. From PET till now In the IT-world. Currently employed as softwaredeveloper in a company creating solutions for pharmacies.
MCSE2000, MCSD, MCTS - Team Foundation Server, MCTS - Visual Studio Tools for Office.
Specialized in Outlook/Exchange custom development. Languages: german, english, C#, C++, VB.Net, VB6, SQL.
In 2006 received an award as MVP - Visual Developer VSTO by Microsoft.
Homepage: [http://www.x4u.de]

Hey, in this Project there is no "FindRibbons" Task.
This has nothing todo with "Ribbons".
This Solution just wotks with Inspectors and Explorers and it slightly modified it works also for OL 2012-2013.

The basic princip is monitoring Inspector- and Explorer-Windows for Deactivate and Activate events, and if the Outlook Addressbook-Window is shown, you can find it, close it and display your own.

I tested it, took me 30 Minutes to make it work.
Drop me a Mail on flash at x4u de, I can send you a working Sample back.

If I'm correct the three zips contain full projects that are already "spy" enabled, yes?

I can see (the) Add-In in Outlook 2007 Security list. Your talk about the need for identification of the process that the Dialog Intercept is going to hook in order to show itself, as a replacement for the default dialog is wonderful. But I'm thinking that enclosing the three projects, with the projected spy results already contained in them, is muddying any concepts I may have missed.

What I mean is, I see the Add-In(s) but there is no functional dialog replacement occuring, even after restart.

Do I actually have to add something else to any of these three projects in order to get them to work or are they patent "as-is"?

basically it's everyting in there to get it working. The Demo has been written on a localized german Version for Outlook.
So in the Inspector_Deactivate method you need to search for the correct buttons and window names:

Hi ,
First of all Thank you very much for the article.
I want to add two buttons on the General Section of the Dialog that pops up ,when you click on the name of a contact (from address book).That is when you want to see more details of the person,who sent you the mail , you normally double click on the name of the person. If the person contact is already existing in your contact list a Properties dialog with sections General, Phone\Fax, Company , E-mail Addresses pops up. I want to modify the general section by adding two buttons.
I did not find any information on net, related to this.
Please let me know,how to go about this ?
Thanks

Appu..
"Never explain yourself to anyone.
Because the person who likes you does n't need it.
And the person who dislikes you won't believe it."

Hi Helmut,
Thank you for excellent article.
I am in progress of making customization to meeting request window.
I want to have custom button in meeting request and i have to popup new form from that button.
use informations entered in meeting request and do some functionality in new form opened.
Please guide me how to do this ??
it will be of great help.
Thanks
Shiva.

I have downloaded the project and tested in Outlook 2007 running in Windows Vista. I also created a project as
Outlook 2003 add-in to run in Windows XP. In both situations I got the same problem: After windows restarted, the add-in
stop working. It is loaded but childWindowNames does not get right child windows. In debug mode, I can see the object
childWindowNames has 2 child windows before execute the three "If (Not childWindowNames.Contains("...")) " code.
Child window (0) has the value "OK" and Child window (1) has the value "Please restart the IIS. CSA uses Windows Filtering Platform and IIS 7.0 may not work as expected until the IIS is restarted. ". The problem remains even if I restarted IIS.
If I remove the three lines of code "If (Not childWindowNames.Contains("...")) " and then run the project, both built-in dialog
and custom dialog will appear. After that, I put the three lines of code back. Then it would be working until windows restarted.
The problem comes back after windows restarted.

this has nothing todo with Outlook or your Add-in by default.
There's maybe a Group-Policy or another AddIn that causes a Dialog to show up that requires you to restart an IIS - instead of the normal Dialog.

Maybe it's a problem with your code. Are you registering a Webservice or a HTTP site by code from within your Add-in?

I would serach for "CSA uses Windows Filtering Platform" and see what is causing this message.

A workaround would be to handle this Dialog first, (close) and then check for the real SelectNamesDialog.

yes, as I know you can use it for Outlook 2003, too. It's the same technic.
The Dialog Class should be the same as I know. If not, you can find it with Spy++.
Just open Outlook, show it and have a look at the child-windows of Outlook.

There must be a Dialog with all the Labels and Buttons as Child-Windows.
This is how you can Test if it's the correct Window.

Thank you very much for your help. I have got it to work with Outlook 2003 for the Message screen to add recipient in the To box. However, it is not working with the Meeting screen when click the To... button. This is because the name built-in control
in the Meeting for the To box is not "To". An error would be generated when call OutlookUtility.PropertyGet and pass in "To" as
c.Tag. How Can I know the name of the built-in control in the Meeting screen?

hi Helmut Obertanner nice article
i want to ask u some thing. have create Outlook addin that save and update contact from my client website. i Save the ContactId of the Client Contact in "CustomerID" field of outlook and it work fine. but when my client test it on his machine i give error Property "CustomerID" unkown. My Client outlook Language is Spanish. Plz help me

WANTED wasim khan(Killed 50 Innocent Buggs, Distroyed 200 Exception, make 5 Project Hostage) any Compnay Hire him will pay 30,000. Best place where u can get him is Sorcim Technologies Murre Road RWP

The Property "CustomerID" on a ContactItem should also exist in a spanish Outlook.
How do you access the Property ?

can you try to connect to the customers machine (Remote Desktop), Open the VBA editor in Outlook (Alt+F11) -> open Object Bowser (F2) and select Outlook.ContactItem
Here you should see all possible Properties of the ContactItem

hi Helmut
Thnx for ur replay.
There is about 150 contact in my client Website and about 10 contact is Synch with no problem and all the remaining give the above Error that i mension.
I use the Find Method of ContactItem Folder

Great article, I was wondering if the code can be used somehow to fire when the Category Dialog is closed (I wish to capture new category items)? I tested the code and the events do not fire when the Category dialog opens, any ideas?