Click here to Skip to main content
15,860,972 members
Articles / Desktop Programming / ATL
Article

Extending Outlook with new features

Rate me:
Please Sign up or sign in to vote.
4.94/5 (40 votes)
25 Oct 2004CPOL19 min read 344.8K   1.5K   89   163
Learn how to add buttons and how to interact with Outlook.

Introduction

First of all, if you are thinking Outlook Express, just forget it. The only three ways of integrating with Outlook Express are (and this is just my guessing) by paying Microsoft a lot of money and by giving them a really good reason to why you want to do that, or by hooking in through the encryption-entry, or by creating a proper hook and hacking your way in there. I've done the hacking (due to missing funds) and it's not a pretty sight, it works but it's everything from clean.

So, this is not for Outlook Express, this is only for Outlook from Office.

Ever thought of extending Outlook to add that extra functionality that you so much wished for? Well, look no further. I will try to give you enough information so that you can do that. There are two ways to extend Outlook, one is pure ATL where the other is just about some COM-interfaces in a plain DLL. We could have discussion about what way to do it, and I'm sure there are people who prefer the ATL-version to the COM-version.

Referring to it as COM-version is wrong, well, not really, it is COM, but just for the sake of it, I'll refer to it as the Exchange-way. So why Exchange when we're talking about Outlook? It started with the old Exchange 4.0 client and Microsoft exposed a way to hook in to their mail client, and since Outlook is using the same model as Exchange, that entry point to the client has stayed and we should be grateful for that because its been tested through out all these years and it's 99.9% bug free where as the ATL-version isn't really that well tested.

I guess you've understood by now that this article isn't about the ATL-version,...

Also, I need to notify you that parts of this article are copied from MSDN, but with minor changes to allow people to actually understand what's written. Microsoft have never been really good at explaining their more complicated API's, don't know why but that's the way it is...

I've decided not to start out with code, but with some background information first, once that is covered, we will make a small addin that adds a button to a new toolbar. This addin will not cover the entire architecture, but it will not only give you an idea to what you can do with an addin, but more importantly, it will (hopefully) explain the basics so that you can continue on your own in less time than it took for me...

Background

You need to at least know what a COM-interface is, and no, I'm not talking about that IDispatch-derived thing that VC generates for you when you create an ATL-project, I'm talking about proper COM, the old one. You will also need to know a bit about MAPI, you don't have to be a guru, but you should at least know what it's all about. If you don't know what you just read, don't worry, you'll get enough information so that you know what you should search for.

Before I get going, when and if you want to debug your addin, select Outlook.exe to be the executable in Dev-studio.

Extending Outlook - the basics...

When you want to extend Outlook, what you do is basically create a DLL, export an entry-point, register the addin with Outlook by modifying the registry. But before I start talking about how that is done, let's go through the basics of how an addin is loaded, why and when it is released.

An addin is loaded several times, every time in a different context. Now, Outlook is nice enough to pass you enough information so that you can figure out in what context you've been loaded and by simply identifying the context, you can also figure out what you can do and what you shouldn't do.

Image 1

In the table above, you can see three bars (five bars in total), each bar represents a different context. Each context has a timeline where the length of the bar is the time that context is loaded.

Before we look at this, its worth mention that Outlook keeps an array of addins, and that each addin has its own map of when it wants to be loaded.

So, if we take them in the load-order.

  • TASK:

    As soon as you start Outlook, Outlook loads your addin in this context, This context is the one that last the longest, it will keep your addin loaded for as long as Outlook is running.

  • SESSION:

    This context is created as soon as the user has logged on, that is selected a profile to use if multiple profiles exists, and that the user has established a session with MAPI (This could involve setting up a connection to Exchange server but that is not necessarily the case as you can configure Outlook to use Pop3 only, also referred to as Internet mail only)

    This is a good place to figure out if you should continue to be loaded, an example could be that you installed your addin but you only want to be loaded in the profile X or if a certain condition is met, like if you can connect to some external resource such as a server)

  • VIEWER:

    This context is the context that you will fight with most of the time because this context is created for each item you open in Outlook, but not only items, it can also be a new window you open in Outlook, basically every time you see a new window popup, there is a new VIEWER-context behind it. It's up to you to synchronize your resources here since your addin is not considered as a singleton.

So, how does Outlook know when to load your addin then? The best way to explain this is to look at how you register the addin in the first place.

Extending Outlook - registering the addin...

There are two ways of telling Outlook about your addin and that is either by using an ECF-file or through the registry. I won't describe the ECF-format because that is too long and not the purpose of this article, so open up your registry and browse to HKEY_LOCAL_MACHINE\Software\Microsoft\Exchange\Client\Extensions.

What you see here is, a list of addin's. Each entry in this list is an addin and apart from the name, the entry also tells Outlook when it wants to be loaded, what interfaces the addin implements and some quite uninteresting information (see MSDN: )

Let's take an example:

Exchange Scan - 4.0;C:\Program Files\Network Associates\VirusScan\scanemal.dll;1;11000000000000;1110000;

So, what can we see from this value? Let's break down the value to smaller pieces and I'll try to explain what they mean.

4.0

 

The version of Extension object implemented in the DLL (this is always 4.0).
C:\Program.......\scanemal.dll

 

The location of the DLL.
1

 

The ExchExtEntryPoint ordinal.
11000000000000

 

The list of contexts for which Outlook should install an Extension object.
1110000

 

The list of interfaces that the Extension object exposes.
 

 

The particular mail service that an Extension might use (not present in this example).

Now the first three items are not very hard to understand, so we'll look into the two items that are interesting instead.

First the context-map, the map that will tell Outlook when to load a given addin :

Context nameMap-positionDescription
EE_CONTEXT_SESSION 1Logged on to MAPI.
EE_CONTEXT_VIEWER2Looking at an object (Folder, perhaps).
EE_CONTEXT_REMOTEVIEWER3Using Remote Mail feature.
EE_CONTEXT_SEARCHVIEWER4Using Find feature.
EE_CONTEXT_ADDRBOOK5Address book is open.
EE_CONTEXT_SENDNOTEMESSAGE6Composing a Send message.
EE_CONTEXT_READNOTEMESSAGE7Reading a message.
EE_CONTEXT_SENDPOSTMESSAGE8Composing a Post message.
EE_CONTEXT_READPOSTMESSAGE9Reading a Post message.
EE_CONTEXT_READREPORTMESSAGE10Reading a delivery report, read report.
EE_CONTEXT_SENDRESENDMESSAGE11Re-sending a returned message.
EE_CONTEXT_PROPERTYSHEETS12Viewing the properties of an object.
EE_CONTEXT_ADVANCEDCRITERIA13Using Advanced Search.
EE_CONTEXT_TASK14The Client is running.

Next in line is the interface-map, this map tells Outlook what interfaces you implement. If you are unsure (don't know how you can be unsure to what interface you implement, but hey, who am I to judge...) you can always fill this map with 1's, Outlook will then query your base interface for all the interfaces it might need and it's up to you to give the right response to that. This map contains 9 interfaces that you decide if you want to implement (where you can map 7 of these 9) and there is 1 that is mandatory and therefore can't be put in this map.

Interface (linked to MSDN)Map-positionDescription
IExchExtCommands1If you want to add some buttons or menu-items, then this was the interface, today you would use a different interface, but it might be useful to implement this if you want to catch standard buttons. More on this later.
IExchExtUserEvents2If you need to enable / disable items or catch a selection-change, then this is what you need.
IExchExtSessionEvents3The magic one, just one function : OnDelivery. Each time you get a new mail, this function will be called.
IExchExtMessageEvents4If you want to do some custom processing before sending or opening a mail (among other things), then this is what your looking for.
IExchExtAttachedFileEvents5If you want to do some custom processing before attaching objects to a mail, then this is where you should start.
IExchExtPropertySheets6Need an extra propertysheet in the menu Tools \ Options? Then you will need to implement this interface.
IExchExtAdvancedCriteria7If you need to enhance the advanced search in Outlook, then this is the place to begin.
IExchExtNot MappedThis is the main entrance, just have one function and that is Install
IExchExtModelessNot MappedSee MSDN, I never had the need to touch this one.
IExchExtModelessCallbackNot MappedSee MSDN, I never had the need to touch this one.

When you add an addin to the registry, Outlook caches the majority of the settings so that you need to tell him to reload the cache. There are three ways of doing this and I usually end up doing the first and the last, that way I won't have any surprises. Maybe not very clean but it works for me.

  1. Add a magic value to the registry, Outlook is supposed to reload its cache when he finds this value.
    • The name of this value should be: Outlook Setup Extension.
    • And the value itself should be: 4.0;Outxxx.dll;7;00000000000000;0000000;OutXXX.

    The only problem with this is that when you install the addin, you need to have full rights of the machine (or atleast read / write rights to HKEY_LOCAL_MACHINE)

  2. Use an ECF-file. (See links for a description of the file format) By using this file, you don't have to edit the registry.
  3. Delete the cache, (the file is named 'extend.dat'), that way you should be home safe. This is not recommended as this may have some side-effects, I have not seen any of these effects so far (and I've been developing addins for more than 5 years now) but there are supposed to be some.

Typical code for this would be:

char szAppPath[MAX_PATH]={0};
SHGetSpecialFolderPath(NULL, szAppPath, CSIDL_LOCAL_APPDATA, FALSE);
strcat(szAppPath, "\\Microsoft\\Outlook\\extend.dat"); 

Ok, so now you know how to register your addin and how the addin is loaded but you still don't know how Outlook loads your addin, so let's talk about that now.

Your DLL needs to export one function, just one tiny function to have your addin up and running. So what does this function look like then?

LPEXCHEXT CALLBACK ExchEntryPoint()
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());
    CMyAddin *pExt = new CMyAddin();
    return pExt;
}

As you can see, it's a callback that returns an instance of your addin-class. Now that doesn't really look like COM, does it? Well, it is and it is done the hard way. Outlook will actually query your object for different interfaces (see above) later on. What more can you see in this code? Well, to begin with, its a DLL that uses MFC. Now this is totally up to you if you want to use MFC or not, the addin itself doesn't require MFC, it's just me using it because I find it a lot easier to use and a lot less hard work than using AYL (MFC is Buggy, yes, I know, but this is just my personal opinion so please don't flame me for that...)

Extending Outlook - developing the core of your addin...

Start up your Developer Studio now and create a new project, make it a Regular DLL using MFC and call it OutlookAddin.

First of all, open OutlookAddin.cpp and create a function as follows:

LPEXCHEXT CALLBACK ExchEntryPoint()
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());
    CMyAddin *pExt = new CMyAddin();
    return pExt;
}

Now, open your def-file and add the exported function

ExchEntryPoint                @1

Not very hard, so now we have an entry-point to the addin. Now we need to add a class, a generic one called CMyAddin, let Dev-studio generate it for you, that way you will have it in a new pair of files.

Start by changing the class so that it derives from IExchExt, also, add the following functions so that your class looks like this :

class CMyAddin : public IExchExt
{
public:
   CMyAddin();
   ~CMyAddin();

   STDMETHODIMP_(ULONG)    AddRef();
   STDMETHODIMP_(ULONG)    Release(); 
   STDMETHODIMP        QueryInterface(REFIID    riid,
                      void**    ppvObj);
   STDMETHODIMP        Install(IExchExtCallback    *lpExchangeCallback,
                    ULONG        context,
                    ULONG        ulFlags);
    
   ULONG            m_ulRefCount;    // Basic COM stuff, needed to know when 
                                     // it's safe to delete ourselves
   ULONG            m_ulContext;     // Member to store the current context in.
};

It's worth mention that the functions AddRef, Release and QueryInterface comes from the standard IUnknown.

Now that you have the core of an addin, implementing these functions shouldn't be very hard, so let's do it: (I've skipped everything except the Install function, for the other functions, see the source-code for this article).

STDMETHODIMP CMyAddin::Install(IExchExtCallback    *lpExchangeCallback,
                 ULONG        mcontext,
                  ULONG        ulFlags)
{
   AFX_MANAGE_STATE(AfxGetStaticModuleState());
   HRESULT hRet = S_FALSE;
   try
   {
      m_ulContext = mcontext;
      switch(m_ulContext)
      {
         case EECONTEXT_TASK:
            {
               hRet = S_OK;
            }
            break;
         case EECONTEXT_SESSION:
            {
               hRet = S_OK;
            }
            break;
         case EECONTEXT_VIEWER:
            {
               hRet = S_OK;
            }
            break;
         case EECONTEXT_REMOTEVIEWER:
         case EECONTEXT_SENDNOTEMESSAGE:
         case EECONTEXT_SENDPOSTMESSAGE:
         case EECONTEXT_SEARCHVIEWER:
         case EECONTEXT_ADDRBOOK:        
         case EECONTEXT_READNOTEMESSAGE:        
         case EECONTEXT_READPOSTMESSAGE:
         case EECONTEXT_READREPORTMESSAGE:
         case EECONTEXT_SENDRESENDMESSAGE:
         case EECONTEXT_PROPERTYSHEETS:
         case EECONTEXT_ADVANCEDCRITERIA:
         default:
            {
               hRet = S_FALSE;
            }
            break;
      };
   }
   catch(...)
   {
   }
   return hRet;
}

You will also need to create a new CPP-file, call it MapiDefines.cpp that looks like this. The reason for this being put in a CPP-file is that these lines should not be compiled more than once.

#include "stdafx.h"
#define INITGUID
#define USES_IID_IExchExt
#define USES_IID_IExchExtAdvancedCriteria
#define USES_IID_IExchExtAttachedFileEvents
#define USES_IID_IExchExtCommands
#define USES_IID_IExchExtMessageEvents
#define USES_IID_IExchExtPropertySheets
#define USES_IID_IExchExtSessionEvents
#define USES_IID_IExchExtUserEvents
#define USES_IID_IMessage
#define USES_IID_IMAPIContainer
#define USES_IID_IMAPIFolder
#define USES_IID_IMAPITable
#define USES_IID_IMAPIAdviseSink
#define USES_IID_IMsgStore
#define USES_IID_IMAPIForm
#define USES_IID_IPersistMessage
#define USES_IID_IMAPIMessageSite
#define USES_IID_IMAPIViewContext
#define USES_IID_IMAPIViewAdviseSink
#define USES_IID_IDistList
#define USES_IID_IMailUser
#include <mapix.h>

You will also need to add the following includes to your stdafx.h (these files should have been installed when you installed Dev-Studio).

#include <atlbase.h>
#include <mapix.h>
#include <mapiutil.h>
#include <mapitags.h>
#include <mapiform.h>
#include <initguid.h>
#include <mapiguid.h>
#include <exchext.h>
#include <exchform.h>
#include <mapidefs.h>
#include <mapispi.h>
#include <imessage.h>
#include <ocidl.h>

#pragma comment(lib,"mapi32")
#pragma comment(lib,"Rpcrt4")
#pragma comment(lib,"Version")

Ok, so now we have a basic extension that doesn't do anything except that it gets loaded into Outlook at startup and released when Outlook is shutdown.

Now, compile the project and sort any errors or warnings out before you go on to the next part of this article.

You will not find the includes if you are developing in Visual Studio .NET, you will need to find either the Platform SDK for MAPI or the includes from Visual Studio 6.


Extending Outlook - adding some buttons to the addin...

First, lets extend our class to add a toolbar and a button at startup. For that, we need to ask Outlook for an object called <name>. We will also need to import two DLL's that defines what the "Outlook Object Model"-objects (also referred to as OOM) look like.

// If you have Outlook 2000 installed on your machine, then use the 
// following lines
#import "mso9.dll" 
#import "msoutl9.olb"

// If you have Outlook 2002 installed on your machine, then use the 
// following lines
#import "mso.dll" 
#import "msoutl.olb"

// If you have Outlook 2003 installed on your machine, then use the 
// following lines
#import "mso11.dll" 
#import "msoutl11.olb"

I have used the outlook 2000 version but it doesn't matter which version you import, (you don't have to have the version you are importing installed on your machine, it's enough to have these DLL's, but it's a lot easier to debug if you have Outlook installed...anyway, I've put the import line in my stdafx.h.

I have not included these files in the ZIP-file, you will have to locate them from your Outlook-installation folder. Apart from Copyright issues, they are big (Msox.dll is about 5.3 Mb).

One thing you have to remember is that if you are using the functionality that only exists in Outlook 2003, then obviously, that won't work in Outlook 2000, but you will still be able to run your addin as long as you don't execute that piece of code. Also, forget about this whole thing if you intend to run under Outlook 97 and Outlook 98. OOM existed back then but wasn't fully implemented. You will though be able to create a toolbar under Outlook 98 but it's not straight forward and there are no events for the buttons (among other things), so you will need to do some hacking (hook's and subclassing) if you want to detect when a button is clicked.

If you still want to be able to run this under Outlook 97 or 98, then look up the IExchExtCommands- interface and more specifically the function InstallCommands. (I will write more about this in another article, but only if demanded as this is not the most common thing to do these days as most users are running Outlook 2000 or later).

Before you can obtain the Outlook-object, you need to add another header-file called OutlookInterface.h in which you put the following:

#ifndef _OUTLOOKINTERFACE_H
#define _OUTLOOKINTERFACE_H

#if defined(WIN32) && !defined(MAC)
#ifndef __IOutlookExtCallback_FWD_DEFINED__
#define __IOutlookExtCallback_FWD_DEFINED__
typedef interface IOutlookExtCallback IOutlookExtCallback;
#endif    /* __IOutlookExtCallback_FWD_DEFINED__ */ 

// Outlook defines this interface as an alternate to IExchExtCallback.
#ifndef __IOutlookExtCallback_INTERFACE_DEFINED__
#define __IOutlookExtCallback_INTERFACE_DEFINED__

EXTERN_C const IID IID_IOutlookExtCallback;
    interface DECLSPEC_UUID("0006720D-0000-0000-C000-000000000046")
    IOutlookExtCallback : public IUnknown
    {
    public:
        virtual HRESULT STDMETHODCALLTYPE GetObject( 
            /* [out] */ IUnknown __RPC_FAR *__RPC_FAR *ppunk) = 0;
        virtual HRESULT STDMETHODCALLTYPE GetOfficeCharacter( 
            /* [out] */ void __RPC_FAR *__RPC_FAR *ppmsotfc) = 0;
    };

DEFINE_GUID(IID_IOutlookExtCallback,
    0x0006720d,
    0x0000,
    0x0000,
    0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46);

#endif    /* __IOutlookExtCallback_INTERFACE_DEFINED__ */ 
#endif // defined(WIN32) && !defined(MAC)
#endif // _OUTLOOKINTERFACE_H

So, to obtain that precious Outlook-object, you would add the following to your class:

#include "OutlookInterface.h"

class CMyAddin : public IExchExt
{
public:
   CMyAddin();
   ~CMyAddin();
   :
   :
   :
    
   HRESULT GetOutlookApp(IExchExtCallback         *pmecb,
               Outlook::_ApplicationPtr     &rOLAppPtr);
   Outlook::_ApplicationPtr     m_OLAppPtr;
};

And implement it as follows:

HRESULT CMyAddin::GetOutlookApp(IExchExtCallback         *pmecb,
                 Outlook::_ApplicationPtr     &rOLAppPtr)
{
   AFX_MANAGE_STATE(AfxGetStaticModuleState());
   try
   {
      IOutlookExtCallback *pOutlook = NULL;
      HRESULT hRes = pmecb->QueryInterface(IID_IOutlookExtCallback,
                                            (void **) &pOutlook);
      if (pOutlook)
      {
         IUnknown *pUnk = NULL;
         pOutlook->GetObject(&pUnk);
         LPDISPATCH lpMyDispatch;
         if (pUnk != NULL)
         {
            hRes = pUnk->QueryInterface(IID_IDispatch,
                                        (void **) &lpMyDispatch);
       pUnk->Release();
         }
         if (lpMyDispatch)
         {
            OLECHAR * szApplication = L"Application";
       DISPPARAMS dispparamsNoArgs = {NULL, NULL, 0, 0};
       DISPID dspid;
       VARIANT vtResult;
       lpMyDispatch->GetIDsOfNames(IID_NULL, &szApplication, 1, 
                                   LOCALE_SYSTEM_DEFAULT, &dspid);
       lpMyDispatch->Invoke(dspid, IID_NULL, LOCALE_SYSTEM_DEFAULT, 
                             DISPATCH_METHOD, 
                            &dispparamsNoArgs, &vtResult, NULL, NULL);
       lpMyDispatch->Release();

       rOLAppPtr= vtResult.pdispVal;
            return S_OK;
         }
      }
   }
   catch(...)
   {
   }
   return S_FALSE;
}

So, go back to your Install function, and add the line in bold.

STDMETHODIMP OlemAddin::Install(IExchExtCallback    *lpExchangeCallback,
                 ULONG        mcontext,
                  ULONG        ulFlags)
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());
    HRESULT hRet = S_FALSE;
    try
    {
        m_ulContext = mcontext;
        GetOutlookApp(lpExchangeCallback, m_OLAppPtr); // <--Add this line here

Ok, so what do we have here? We have an extension, that at 'Install'-time obtains an Outlook-object.

Let's see what we can do with it now and how we can actually create a toolbar and add a button to it.

First step is to create some sort of container-class for the button(s) we are going to add, this class must be derived from IDispatch. This class will hook in to the IConnectionPoint of the button itself, and will that way be notified when the button is clicked. Now, this is just an example. So I won't put very much effort to make this look nice or make it very usable, I leave that to you. So, open up the file MyAddin.h and add the following between the line #include "OutlookInterface.h" and the class CMyAddin.

class COutlookButton : public IDispatch 
{
public:
    COutlookButton(CComPtr <Office::CommandBarControl> pButton);
    virtual ~COutlookButton();

    // IUnknown Implementation
    virtual HRESULT __stdcall QueryInterface(REFIID riid, void ** ppv);
    virtual ULONG   __stdcall AddRef(void);
    virtual ULONG   __stdcall Release(void);

    // Methods:

    // IDispatch Implementation
    virtual HRESULT __stdcall GetTypeInfoCount(UINT* pctinfo);
    virtual HRESULT __stdcall GetTypeInfo(UINT iTInfo, LCID lcid, 
                                          ITypeInfo** ppTInfo);
    virtual HRESULT __stdcall GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, 
                                    UINT cNames, LCID lcid, DISPID* rgDispId);
    virtual HRESULT __stdcall Invoke(DISPID dispIdMember, REFIID riid, 
                                     LCID lcid, WORD wFlags, 
                                     DISPPARAMS* pDispParams, 
                                     VARIANT* pVarResult, 
                                     EXCEPINFO* pExcepInfo, UINT* puArgErr);


    BOOL                        SetupConnection(IDispatch *pDisp);
    void                        ShutDown();

    DWORD                                m_dwCookie;
    CComPtr <Office::CommandBarControl> m_pButton;
    IConnectionPoint                    *m_pCP;
    ULONG                                m_cRef;
};

Now, to save some space in this article, I will not put the source for that class... it's kind of long, so if you have opened up the workspace for this article, then open the file OutlookButton.cpp and I will try explain the logic.

When we create an object of this type, we pass it a button as parameter. This button will be instantiated and we will hook up to the IConnectionPoint of that button. This is done in the constructor and in the function SetupConnection. When the button is clicked, the function Invoke is invoked (just couldn't resist writing that...) and then it's up to you to decide what to do. In this example, we will just display a message box to indicate that the button has been clicked.

In order to keep the code readable, we'll add a function called InstallInterface instead of putting the code in the Install function, and the prototype looks like this :

class CMyAddin : public IExchExt
{
public:
   CMyAddin();
   ~CMyAddin();
   :
   :
   :
   HRESULT   InstallInterface(IExchExtCallback   *lpExchangeCallback);

The function InstallInterface is a bit long so that you will have to look at the source for this article. I've put in some comments, enough for you to get the idea of how it works.

And all we need to do now is to call that function, and we do that from the Install function when we are in the context EECONTEXT_VIEWER.

STDMETHODIMP CMyAddin::Install(IExchExtCallback    *lpExchangeCallback,
                 ULONG        mcontext,
                  ULONG        ulFlags)
{
   AFX_MANAGE_STATE(AfxGetStaticModuleState());
   :
   :
         case EECONTEXT_VIEWER:
            {
               :
          InstallInterface(lpExchangeCallback); // <--Add this line here
            }
            break;

Now compile the whole thing and once again, sort errors (if any) out.

So, the only thing you need to do now is to register the DLL and to do this, in the sources for this article, you have a registry-file. You will need to edit that one and change the path so that it points to this DLL.


In the previous two parts, we've created a basic addin that added a toolbar, and when you clicked it, displayed a message box. So in this article, we will explore some more things you can do with an addin for Outlook. With this article, I will show you how you can log all incoming mails to a file. Not very impressive but it's only to show you some basic MAPI-operations.

If you haven't read the previous articles, then go back to them otherwise you wont understand very much of this.

Extending Outlook - Getting called for every mail that comes in...

First, let's extend our class to derive from IExchExtSessionEvents. This interface only has one method, and that is OnDelivery. This method is called for each and every mail that is delivered. There might be a slight delay from the moment they come in to that you are called, it all depends on rules and other addin's, but most of the time, you get called the split-second before the mail is actually displayed in the listview.

There is one thing that is extremely important and I can't stress enough about, and that is the return value of this function. If you look in MSDN, you can read the following:

  • S_OK

    The extension object replaced Microsoft Exchange default behavior with its own behavior. Microsoft Exchange will consider the task handled.

  • S_FALSE

    The extension object did nothing or added additional behavior. Microsoft Exchange will continue to call extension objects or complete the work itself.

Now, read the S_OK again, because if you do decide to return S_OK, consider the mail as lost unless you store the mail yourself. I'm not joking, the mail is lost if you do return S_OK.

I think you got the message by now. So let's get going on the code.

First, extend your class so that it looks like this (add the lines in bold)

class CMyAddin : public IExchExt, 
              IExchExtSessionEvents
{
public:
   CMyAddin();
   ~CMyAddin();

   STDMETHODIMP_(ULONG)    AddRef();
   STDMETHODIMP_(ULONG)    Release(); 
   STDMETHODIMP        QueryInterface(REFIID    riid,
                      void**    ppvObj);
   STDMETHODIMP        Install(IExchExtCallback    *lpExchangeCallback,
                    ULONG        context,
                    ULONG        ulFlags);
   STDMETHODIMP            OnDelivery(IExchExtCallback  *lpExchangeCallback); 

Before we implement the OnDelivery, we need to change the method QueryInterface. This method will be called with a REFIID set to IID_IExchExtSessionEvents, so we need to add some lines to handle that case correct.

Once again, add the lines in bold

STDMETHODIMP CMyAddin::QueryInterface(REFIID   riid,
                 void**     ppvObj)
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());
    *ppvObj=NULL;
    IUnknown* punk=NULL;
    if (riid == IID_IUnknown)
    {
        punk = (IExchExt*)this;
    }
    else if (riid == IID_IExchExt)
    {
        punk = (IExchExt*)this;
    }
    else if (riid == IID_IExchExtSessionEvents)
    {
        punk = (IExchExtSessionEvents*)this;
    }
    else
    {

We have specified in the registry that we implement the IExchExt-interface, and also the IExchExtSessionEvents-interface so that when Outlook loads us, he will query us for the IExchExtSessionEvents-interface and when he does, we need to give him something back.

So, over to the OnDelivery. Add this function as follows and remember to return S_FALSE no matter if you handled the mail or not. It's not a return-code to indicate if your custom code worked or not. It indicates if you took care of the mail or not!

STDMETHODIMP CMyAddin::OnDelivery(IExchExtCallback *lpExchangeCallback)
{
   AFX_MANAGE_STATE(AfxGetStaticModuleState());
   try
   {
   }
   catch(...)
   {
   }
   return S_FALSE;
}

Now, what we want to do is to get a pointer to the mail, extract some information and log it to a file. So first we'll ask Outlook for the pointer by calling the method GetObject.

STDMETHODIMP CMyAddin::OnDelivery(IExchExtCallback   *lpExchangeCallback)
{
   AFX_MANAGE_STATE(AfxGetStaticModuleState());
   try
   {
      // The first thing we need to do is to obtain a pointer to the message 
      // that just arrived.
      // When we do that, we also obtain a pointer to the message-store (see 
      // it as a database)
      LPMESSAGE    lpMessage = NULL;
      LPMDB    lpMdb = NULL;
      if (SUCCEEDED(lpExchangeCallback->GetObject(&lpMdb,
                                                 (LPMAPIPROP*)&lpMessage)))
      {

The method GetObject returns a pointer to the message-store and the mail that just arrived. The only thing we are interested in is the mail, so just ignore the message-store-pointer.

Before we continue, let's take a look at what a message is and how the data is stored in it. A MAPI-message is nothing like a mime-message. It's a binary blob and not a text-message. The data is stored in something called tags, propertytags. You could compare this to an ini-file where you have keys and values. In this case, the keys are tags.

These tags can either be predefined by MAPI, the most common ones like the subject and the body is a predefined tag, but you can also have something called named tags. Named tags are tags that you define in which you can store data that is private. This does not mean that the data is encrypted or hidden, anyone can extract this data.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
Sweden Sweden
Been developing MAPI-modules (client-addins, gateways, agents, MTA's etc) since -96.
Works mainly in VC++, but when needed, looks at vb, asp, etc...

Always on the look for new things to do, got something for me? Smile | :)
I'm available for consulting, on a project-base or long-term development.

Comments and Discussions

 
GeneralMy vote of 5 Pin
JBress21-Nov-11 0:19
JBress21-Nov-11 0:19 
QuestionOffice 2010 sample Pin
Ștefan-Mihai MOGA20-Oct-11 2:49
professionalȘtefan-Mihai MOGA20-Oct-11 2:49 
AnswerRe: Office 2010 sample Pin
juneblue9922-Jan-13 13:46
juneblue9922-Jan-13 13:46 
QuestionHandling Read Messages in Outlook [modified] Pin
XMarshall1210-Apr-11 21:31
XMarshall1210-Apr-11 21:31 
GeneralOutlook 2010 Pin
Member 4934607-Mar-11 10:12
Member 4934607-Mar-11 10:12 
GeneralRe: Outlook 2010 Pin
Karl Josefsson8-Mar-11 0:35
Karl Josefsson8-Mar-11 0:35 
QuestionHow to remember toolbar position? Pin
tnemec30-Apr-09 2:14
tnemec30-Apr-09 2:14 
GeneralOnWritePattToSzFile Pin
Wahaj Khan3-Feb-09 2:12
Wahaj Khan3-Feb-09 2:12 
QuestionHow to debug the program of this article? Pin
AJ15-Jun-08 2:46
AJ15-Jun-08 2:46 
AnswerRe: How to debug the program of this article? Pin
Cristian Amarie3-Aug-08 1:30
Cristian Amarie3-Aug-08 1:30 
QuestionOulook Addin for send button Pin
AJ128-May-08 2:37
AJ128-May-08 2:37 
GeneralAdding a button to New -&gt; Mail Message Window Pin
A Mehta23-May-08 0:12
A Mehta23-May-08 0:12 
QuestionWhat toolbar button was clicked Pin
Anarelle17-Jan-08 2:00
Anarelle17-Jan-08 2:00 
QuestionHow to get these libraries mso11.dll, msoutl11.olb? Pin
Giri Ganji28-Sep-07 2:55
Giri Ganji28-Sep-07 2:55 
QuestionDoes anyone has Outlook Express extension code? Pin
hebeh23-Aug-07 3:21
hebeh23-Aug-07 3:21 
AnswerRe: Does anyone has Outlook Express extension code? Pin
Cristian Amarie3-Aug-08 1:32
Cristian Amarie3-Aug-08 1:32 
Generalplug in in c#: Pin
zeltera12-Aug-07 5:31
zeltera12-Aug-07 5:31 
Questionimplement IExchExtMessageEvents Pin
123rutger27-Dec-06 7:34
123rutger27-Dec-06 7:34 
AnswerRe: implement IExchExtMessageEvents Pin
123rutger28-Dec-06 0:47
123rutger28-Dec-06 0:47 
Generala bit more leaking Pin
imagiro27-Nov-06 9:52
imagiro27-Nov-06 9:52 
QuestionDLL not called, you have to rename it? Pin
martho226-Jun-06 23:56
martho226-Jun-06 23:56 
AnswerRe: DLL not called, you have to rename it? Pin
Karl Josefsson27-Jun-06 11:49
Karl Josefsson27-Jun-06 11:49 
GeneralRe: DLL not called, you have to rename it? [modified] Pin
martho22-Aug-06 5:12
martho22-Aug-06 5:12 
GeneralWARNING! Interface leaking in given code example! Pin
Klirik20-Mar-06 23:52
Klirik20-Mar-06 23:52 
GeneralRe: WARNING! Interface leaking in given code example! Pin
Karl Josefsson21-Mar-06 6:07
Karl Josefsson21-Mar-06 6:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.