Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Developing an Office 2003 COM add-in with VC++/ATL

0.00/5 (No votes)
16 Aug 2004 3  
An article describing how to develop a complete COM add-in for Office 2003, plus some handy techniques to program Outlook.

Introduction

Recently, I wrote an Outlook2003 COM add-in for one of my clients. While coding the project, I�ve had a lot of issues to which I couldn�t find any solution for C++ especially. I found it quite tricky to code an add-in in ATL for beginners, and so to help others I thought to put down some information, since most of the Office related examples I found on the Internet were VB/VBA related and almost none with ATL.

The code in this article is not really optimistic, and the sample attached with this article may have some memory leaks or some poor COM implementation, but the general approach has been kept simple for the reader to follow. Though I took quite sometime to write this, in case of any errors or bugs, kindly drop me a mail.

Overview

If you are a beginner in COM/ATL, I�ll suggest you read Amit Dey�s article for building an Outlook 2000 add-in.

My article will describe the following techniques:

  • Basic Outlook 2003 add-in
  • Sinking Explorer Events
  • Mixing CDO and Outlook object model
  • Using CDO to see if a message has security
  • Using CDO to add custom fields to Outlook items
  • Programmatically customizing the grouping and sorting of items
  • Adding items to right click menu
  • How to use MSI to install CDO programmatically

An Office COM add-in has to implement the IDTExtensibility2 interface. The IDTExtensibility2 dispinterface is defined in the MSADDIN Designer typelibrary (MSADDNDR.dll/MSADDNDR.tlb). All COM add-ins inherited from IDTExtensibility2 interface must implement five methods:

  • OnConnection
  • OnDisconnection
  • OnAddinUpdate
  • OnBeginShutDown
  • OnStartupComplete

Registering

All of Office add-ins are registerted to the following registry entry, where Outlook is replaced with the application name.

HKEY_CURRENT_USER\Software\Microsoft\Office\Outlook\Addins\<ProgID>

There are some other entries through which add-ins are identified to Outlook.

Getting Started

Let's start by writing a basic functional COM add-in. Then, we�ll move ahead to each step and will create an add-in that will group simple and encrypted emails to different groups.

This article assumes that you are a VC++ COM programmer, and have had some experience with ATL based component development and OLE/Automation, although this is not strictly necessary. The add-in is designed for Outlook 2003, so you must have Office 2003 with CDO installed on your machine. The project code has been built with VC++ 6.0 SP3+/ATL3.0, and tested on WinXP Pro SP1 with Office 2003 installed.

Fire up your VC IDE. Create a new project and chose ATL COM AppWizard as your project type, give it a name Outlook Addin, click OK. Choose Dynamic Link Library as your server type, and click Finish.

Now, Add a New ATL Object using the Insert menu. Select Simple Object and give it a name OAddin, move to Attributes and check the Support ISupportErrorInfo checkbox, and choose the rest of the options as default.

Now, it's time to actually implement the IDTExtensibility2 interface. Right click the COAddin class, and chose Implement Interface. This will bring up Browse Type library wizard. Select Microsoft Add-in Designer(1.0), and click OK. If it's not listed there, you might have to browse to the folder <drive>/Program Files/Common Files/Designer.

This wizard implements the selected interface and adds a default implementation for each of the five methods of IDTExtensibility2 interface. For now, a basic automation compatible COM object is ready. By adding registry entries to the rgs file, we can register this COM add-in with Outlook. Open your OAddin.rgs file, and embed the following at the end of the file:

HKCU
{
      Software
    {
        Microsoft
        {
            Office
            {
                Outlook
                {
                    Addins
                    {
                        'OAddin.OAddin'
                        {
                            val FriendlyName = s 'SMIME Addin'
                            val Description = s 'ATLCOM Outlook Addin'
                            val LoadBehavior = d '00000003'
                            val CommandLineSafe = d '00000000' 
                        }
                    }
                }
            }
        }
    }
}

The registry entries look pretty simple The LoadBehaviour tells Outlook when to load this COM add-in; since we want our add-in to be loaded at startup, its value is given as 3. So, you can now build your project and you will be able to see the entry in Outlook as following: (Tools -> Options -> Other -> Advanced Options -> COM Addins):

So, the next step is to sink the Explorer events.

Sinking Explorer Events

Outlook Object Model presents Explorer objects that wrap the functionality of Outlook. These Explorer objects can be used to program Outlook. Explorer objects wrap the CommandBars, Panes, Views, and Folders. For this tutorial, we'll interact with ExplorerEvents interface to sink the FolderSwitch event of Active Explorer.

In the Outlook Object Model, the Application object is at the top of the object hierarchy that represents the entire application. Through its ActiveExplorer method, we get the Explorer object that represents the current window of Outlook.

Explorer objects expose an event which is triggered whenever user switches from one folder to other. For this example, we'll only be concerned about folders for Mailbox, and will skip the Contacts, Tasks and Calendars FolderSwitch events. Before moving to the actual coding, we'll need to import Office and Outlook Type libraries. To do this, open the project's stdafx.h file, and add the following #import directive.

#import "C:\Program Files\Microsoft Office\Office\mso9.dll" \ 
  rename_namespace( "Office" ) named_guids using namespace Office; 
#import "C:\Program Files\Microsoft Office\Office\MSOUTL9.olb" 
rename_namespace( "Outlook" ), raw_interfaces_only, \
named_guids using namespace Outlook;

You may need to change these paths according to your OS and Office installation.

Compile the project to import the type libraries.

Now, we'll have to implement the event sink interface that will be called by the source event through connection points.

ATL provides IDispEventImpl<> and IDispEventSimpleImpl<> for ATL COM objects. I've used IDispEventSimpleImpl class to set up a sink map. So, we'll have to derive our COAddin class from IDispEventSimpleImpl and setup params using _ATL_SINK_INFO structure. Setup our sink interface and call DispEventAdvise and DispEventUnAdvise to initiate and terminate connection with the source interface.

This is how the code looks:

Derive your class from IDispEventSimpleImpl.

//

class ATL_NO_VTABLE COAddin : 
public CComObjectRootEx<CComSingleThreadModel>,
...
...
 // ExplorerEvents class fires the OnFolderSwitch event of Explorer

public IDispEventSimpleImpl<1,COAddin,&__uuidof(Outlook::ExplorerEvents)>
_ATL_SINK_INFO structure is used to describe the callback parameters. Open the header file of the add-in object (OAddin.h) and add the following line at the very top:

extern _ATL_FUNC_INFO OnSimpleEventInfo;

Open the cpp file of the class and add the following line to the top:

_ATL_FUNC_INFO OnSimpleEventInfo ={CC_STDCALL,VT_EMPTY,0};

Create a callback function:

void __stdcall OnFolderChange();
void __stdcall COAddin::OnFolderChange()
{
  MessageBoxW(NULL,L"Hello folder Change Event",L"Outlook Addin",MB_OK);
}

Now, to setup the sink map, we'll use ATL BEGIN_SINK_MAP() and END_SINK_MAP(). Each entry will be represented by SINK_ENTRY_INFO, the dispid is the event's DISPID. You can find these IDs in type libraries or using Outlook Spy.

//

 BEGIN_SINK_MAP(COAddin)
  // sink the event for Explorer 

  // the first parameter is the same as given above while driving the class

  // the third parameter is the event identifier to sink i.e FolderChange 

  // rest of event id's can also be located using OutlookSpy or type libraries

  SINK_ENTRY_INFO(1,__uuidof(Outlook::ExplorerEvents),/*dispinterface*/0xf002
       ,OnFolderChange,&OnSimpleEventInfo)
  END_SINK_MAP()

Now, move to your OnConnection function which was added when we implemented the interface using the wizard, and modify the code as follows:

//

STDMETHOD(OnConnection)(IDispatch * Application, ext_ConnectMode ConnectMode,
 IDispatch * AddInInst, SAFEARRAY * * custom)
{
    omQIPtr <Outlook::_Application> spApp(Application);
    TLASSERT(spApp);
    _spApp = spApp; //store the application object

    ComPtr<Outlook::_Explorer> spExplorer; 
    pApp->ActiveExplorer(&spExplorer);
    _spExplorer = spExplorer; // store the explorer object

    RESULT hr = NULL
    /Sink Explorer Events to be notified for FolderChange Event
    /DispEventAdvise((IDispatch*)spExplorer);
    r = ExpEvents::DispEventAdvise((IDispatch*)m_spExplorer,
            &__uuidof(Outlook::ExplorerEvents));
}

Till here, we've mapped the FolderSwitch event of the Explorer object. Compile the project if everything goes OK. When you change your folder from Inbox to Sent or any other folder, the message box will pop up. If you switch folder Other, then folder Mail, you'll still receive this message box. We'll control this later using our programming logic.

Mixing CDO and Outlook Object Model

CDO (Collaboration Data Objects) is a technology for building messaging or collaboration applications. CDO can be used separately or in connection with Outlook Object Model to gain more access over Outlook.

This example deals with the "Custom Grouping of emails", depending on their nature SMIME (encrypted) or Simple emails. Outlook Object Model doesn't provide any interface to interact with signed and encrypted emails. In fact, if you look into MailItem object representing an encrypted email, around 80% of its properties and methods are unavailable. We'll use CDO to find out the availability of security on messages.

In order to customize the grouping of emails, the only way is to add custom field 'X' to the MailItem and force Outlook to group emails according to 'X'. But unfortunately, the "Fields" property of a signed/encrypted email is not valid, and we can't add any field to it using Outlook Object Model.

On the other hand, CDO is not a part of Outlook Object Model and it doesn't provide any event based functionality nor can you manipulate Outlook objects using CDO. So, to access currently selected folder in CDO, you have to use Outlook Object Model to retrieve the currently selected folder, get its unique identifier, and pass it to CDO to return the folder.

Preparing your code to Use CDO

The very first statement about CDO is "CDO is not installed during the default installation of Office XP and later", and so you've to make sure that your client does have it installed. Moreover, going further down this tutorial will also require you to have CDO installed on your machines. CDO is not available in redistributable form nor can you deploy CDO.dll with your application. CDO is only available in Office CD and can only be installed using the Microsoft Office MSI setup.

There is an example at the end of this tutorial that shows how to invoke MSI programmatically to automatically install the CDO.

We'll use the following CDO objects: Session, Messages, Message, Folder, Fields and Field. To have these objects available to your application, you will need to import CDO into your application. Move to "stdafx.h" and add the following line:

#import "F:\\Program Files\\Common Files\\System\\MSMAPI\\1033\\cdo.dll" \
rename_namespace("CDO")

We can access the current folder of Outlook using Outlook Object Model using GetCurrentFolder of the Active Explorer. Here, we can stop execution if the retrieved folder's DefaultItemType property is not olMailItem. The EntryID property of the retrieved folder identifies it uniquely from other folders. We'll pass this property to CDO to retrieve the current folder.

First, move back to OnFolderChange and add the following lines of code:

//

void __stdcall COAddin::OnFolderChange()
{
   if(!m_bEnableSorting) // its a boolean variable to identify 

    //weither to sort or not

    return; 

   CDO::_SessionPtr Session("MAPI.Session");

   //logon to CDO

   // the first parameter is the Profile name you want to use. 

   // the rest of two false tell CDO not to display

   // any user interface if this profile is not found.

   Session->Logon("Outlook","",VARIANT_FALSE,VARIANT_FALSE);

   //its the OutlookObject Model MAPIFolder object. it is used to findout

   //currently selected folder of outlook as CDO doesn't

   // provide any direct interface

   //to get the currently selected folder of outlook

   CComPtr<Outlook::MAPIFolder> MFolder; 
   m_spExplorer->get_CurrentFolder(&MFolder); 

   //this example only deals with outlook Mail Items 

   OlItemType o;
   MFolder->get_DefaultItemType(&o);
   if(o != olMailItem)
    return;

   BSTR entryID;
   MFolder->get_EntryID(&entryID);
   CDO::MessagesPtr pcdoMessages;
   // get the selected folder in CDO using the EntryID of Outlook::MapiFolder

   CDO::FolderPtr  pFolder= Session->GetFolder(entryID);

   if(pFolder) //making sure

   {
     //play with the folder messages here

   }
}

Ok, till here, we've got the CDO Folder, we can now get Messages collection.

Using CDO to see if a Message has security

Now, let's look at how we can see if a message is encrypted/signed. The following KB presents the whole theory: "KB 194623", but I couldn't find it working for my client as there are a lot of email clients and not all of them set the bits described by this KB. It, in fact, states "using these properties to programmatically determine if a message has security on it is unreliable". In order to achieve the results, the only way I am able to find is that every email that has security on it has a special attachment. The attachment contains the encrypted/signed contents that Outlook decrypts/verifies and displays. This attachment has a "p7m" extension with a MIME type of "application/x-pkcs7-mime". And for our solution, this is going to be in the following way:

  • Get the Messages collection of the folder
  • Iterate the collection and get the Message
  • Iterate the Attachments
  • Get the Fields of each Attachment
  • Iterate the Fields to find a Field (H370E001E) (it is the MIME type of the Attachment)
  • Test the field value with "application/x-pkcs7-mime"

Now, add a new member function to your class named IsCDOEncrypted. The function will accept a single CDO Message object and will return a boolean indicating the state of the message. Following is the code snippet covering all of the above theory.

//

BOOL COAddin::IsCDOEncrypteD(CDO::MessagePtr pMessageRemote)
{
BOOL bEncrypted = false;
CDO::MessagePtr pMessage;
pMessage = pMessageRemote;

//get the attachments of the CDO message

CDO::AttachmentsPtr pAttachments;
pAttachments = pMessage->Attachments;
_variant_t nCount =pAttachments->Count;
long nTotal = nCount.operator long();
//enumerate the attachments


for(int i = 0; i < nTotal; i++)
{
 // get the attachment from the 

    //attachments collection

    CDO::AttachmentPtr pAttachment;
    CComVariant nItem(i+1);//1 based index

    pAttachment = pAttachments->Item[nItem];
    //get the Fields collection of the Attachment object

    CDO::FieldsPtr pFields;
    pFields = pAttachment->Fields;
    _variant_t nVFields = pFields->Count;
    for(int z = 0; z < nVFields.operator long() ; z++)
    {
        // get the field from fields collection.

        CComVariant nFieldItem(z+1);
        CDO::FieldPtr pField;
        pField = pFields->Item[nFieldItem]; //1 based index

        //check if this field is what we need

        //the mime type of the 

        //attachment is stored as Field in the CDO message

        //the field that contains the mime type of the CDO Message

        // has an ID of 923664414 (more such ID's can be 

        //found in CDO in HTTP transport Header section)

        BSTR bstrFieldID;
        bstrFieldID =  pField->GetID().operator _bstr_t();
        if(wcscmp(bstrFieldID,L"923664414")==0) 
            // get the mime type of the attachment

        {
            // check the mime type of the mail item now.

            // compare the field value.

            if(wcscmp(pField->Value.operator _bstr_t(),
                L"application/x-pkcs7-mime")==0)
            {
                bEncrypted = true;
                break;
            }
        } 
    
    }

}

pAttachments->Release();
pAttachments = NULL;
pMessage->Update(); //its not a necessary call.

return bEncrypted;
}

Using CDO to add custom fields to Outlook items

Outlook Object Model exposes Fields properties of its items, you can add Custom fields to these items. We'll use CDO to add custom fields to mail items.

OK, now let's use IsCDOEncrypted function during the enumeration of messages in our OnFolderChange function. Move back to OnFolderChange code.

The following code snippet adds custom fields to each item.

//

   .....
   // previous code of OnFolderChange

   .....
   CDO::FolderPtr pFolder= Session->GetFolder(entryID);
   if(pFolder) //making sure

   {
     //get the message of the Folder

     pcdoMessages = pFolder->Messages;
     CDO::MessagePtr pMessage = pcdoMessages->GetFirst();
     while(pMessage) // iterate them

     {
      //check if the message is encrytped

      BOOL bEncrypted = IsCDOEncrypteD(pMessage);

      if(bEncrypted)
      {
        //add a custom field to the outlook message

        //an encrypted email 

        CDO::FieldsPtr pMessageFields = pMessage->Fields;
        //Add a custom field

        //Encrypted of type String(8) 

        //and set its value to "Encrypted" 

        pMessageFields->Add(L"Encrypted",
                CComVariant(8),L"SMIME Emails"); 
        pMessage->Update();
      }
      else
      {
        CDO::FieldsPtr pMessageFields = pMessage->Fields;
        //Add a custom field

        pMessageFields->Add(L"Encrypted",CComVariant(8),L"Simple Emails"); 
          // you must call Update message to reflect the new field to 

          // mail item

        pMessage->Update();
      }

      pMessage = pcdoMessages->GetNext(); 
     } 
   }

Customizing the grouping and sorting of items

Sample screenshot

Outlook now exposes new XML based Views system. You can create your own Views using XML, or you can modify existing Views by altering the XML. The following is the standard XML of Inbox:

//
<?xml version="1.0"?>
<view type="table">
 <viewname>Messages</viewname>
 <viewstyle>table-layout:fixed;width:100%;font-family:Tahoma;
font-style:normal;font-weight:normal;font-size:8pt;
color:Black;font-charset:0</viewstyle>
 <viewtime>0</viewtime>
 <linecolor>8421504</linecolor>
 <linestyle>3</linestyle>
 <usequickflags>1</usequickflags>
 <collapsestate></collapsestate>
 <rowstyle>background-color:#FFFFFF</rowstyle>
 <headerstyle>background-color:#D3D3D3</headerstyle>
 <previewstyle>color:Blue</previewstyle>
 <arrangement>
  <autogroup>1</autogroup>
  <collapseclient></collapseclient>
 </arrangement>
 <column>
  <name>HREF</name>
  <prop>DAV:href</prop>
  <checkbox>1</checkbox>
 </column>
 <column>
  <heading>Importance</heading>
  <prop>urn:schemas:httpmail:importance</prop>
  <type>i4</type>
  <bitmap>1</bitmap>
  <width>10</width>
  <style>padding-left:3px;;text-align:center</style>
 </column>
 <column>
  <heading>Icon</heading>
  <prop>http://schemas.microsoft.com/mapi/proptag/0x0fff0102</prop>
  <bitmap>1</bitmap>
  <width>18</width>
  <style>padding-left:3px;;text-align:center</style>
 </column>
 <column>
  <heading>Flag Status</heading>
  <prop>http://schemas.microsoft.com/mapi/proptag/0x10900003</prop>
  <type>i4</type>
  <bitmap>1</bitmap>
  <width>18</width>
  <style>padding-left:3px;;text-align:center</style>
 </column>
 <column>
  <format>boolicon</format>
  <heading>Attachment</heading>
  <prop>urn:schemas:httpmail:hasattachment</prop>
  <type>boolean</type>
  <bitmap>1</bitmap>
  <width>10</width>
  <style>padding-left:3px;;text-align:center</style>
  <displayformat>3</displayformat>
 </column>
 <column>
  <heading>From</heading>
  <prop>urn:schemas:httpmail:fromname</prop>
  <type>string</type>
  <width>49</width>
  <style>padding-left:3px;;text-align:left</style>
  <displayformat>1</displayformat>
 </column>
 <column>
  <heading>Subject</heading>
  <prop>urn:schemas:httpmail:subject</prop>
  <type>string</type>
  <width>236</width>
  <style>padding-left:3px;;text-align:left</style>
 </column>
 <column>
  <heading>Received</heading>
  <prop>urn:schemas:httpmail:datereceived</prop>
  <type>datetime</type>
  <width>59</width>
  <style>padding-left:3px;;text-align:left</style>
  <format>M/d/yyyy||h:mm tt</format>
  <displayformat>2</displayformat>
 </column>
 <column>
  <heading>Size</heading>
  <prop>http://schemas.microsoft.com/mapi/id
/{00020328-0000-0000-C000-000000000046}/8ff00003</prop>
  <type>i4</type>
  <width>30</width>
  <style>padding-left:3px;;text-align:left</style>
  <displayformat>3</displayformat>
 </column>
 <groupby>
  <order>
   <heading>Received</heading>
   <prop>urn:schemas:httpmail:datereceived</prop>
   <type>datetime</type>
   <sort>desc</sort>
  </order>
 </groupby>
 <orderby>
  <order>
   <heading>Received</heading>
   <prop>urn:schemas:httpmail:datereceived</prop>
   <type>datetime</type>
   <sort>desc</sort>
  </order>
 </orderby>
 <groupbydefault>2</groupbydefault>
 <previewpane>
  <visible>1</visible>
  <markasread>0</markasread>
 </previewpane>
</view>

We are concerned of the two nodes <groupby> and <orderby> for this tutorial. I am going to customize the grouping of items only. You can reuse the same code to customize the sorting of items.

In order to customize the grouping of items, you can use "User Defined fields" in the "Customize Current View" option in Outlook, and to do it programmatically, we've already added a custom field to items. We just need to change the <groupby> element as follows:

//
 <groupby>
  <order>
   <heading>Encrytped</heading>
   <prop>http://schemas.microsoft.com/mapi/string/
    {00020329-0000-0000-C000-000000000046}/Encrypted</prop>
   <type>string</type>
   <sort>asc</sort>
  </order>
 </groupby>

So, let's get back to code and add a new member function called ChangeView(Outlook::ViewPtr pView). The function receives an Outlook View, retrieves its XML, and modifies it accordingly. CurrentView property of Outlook MAPIFolder object returns the Current View. The XML property of Outlook::View returns the XML of the current view. Here, I've used MSXML parser to modify the XML. You can use any convenient way. The following is the code snippet:

//

void COAddin::ChangeView(Outlook::ViewPtr pView)
{
       HRESULT hr;
      IXMLDOMDocument2 * pXMLDoc;
      IXMLDOMNode * pXDN;
     //...create an instance of IXMLDOMDocument2

      hr = CoInitialize(NULL); 
      hr = CoCreateInstance(CLSID_DOMDocument30, NULL, CLSCTX_INPROC_SERVER,
      IID_IXMLDOMDocument2, (void**)&pXMLDoc);
      hr = pXMLDoc->QueryInterface(IID_IXMLDOMNode, (void **)&pXDN);
      //get the view's XML

      BSTR XML;
      pView->get_XML(&XML);
     //loaod the XML 

      VARIANT_BOOL bSuccess=false;
      pXMLDoc->loadXML(XML,&bSuccess);
    
      CComPtr<IXMLDOMNodeList> pNodes; 
      //check groupby element exists

      pXMLDoc->getElementsByTagName(L"groupby",&pNodes);
      long length = 0;
      pNodes->get_length(&length);
      if(length> 0)
      { 
            // groupby element already exists. 

            // get the first occourance of groupby element

            /*<groupby>
                <order>        
                     <heading>Encrypted</heading>
                     <prop>http://schemas.microsoft.com/mapi/string
                      /{00020329-0000-0000-C000-000000000046}
                    /Encrypted</prop>
                     <type>string</type>
                     <sort>asc</sort>
                </order>
          </groupby>*/
          HRESULT hr = pNodes->get_item(0,&pXDN);
          IXMLDOMNode *pXDNTemp,*pXDNTemp2;
          pXDN->get_firstChild(&pXDNTemp);
          pXDNTemp->get_firstChild(&pXDNTemp2);

          _variant_t vtHeading("Encrypted"),vtType("string"),
          vtProp("http://schemas.microsoft.com/mapi/string/ \
            {00020329-0000-0000-C000-000000000046}/Encrypted");
        
          // get the heading element

          //the first element is the name of the field.

          pXDNTemp2->put_nodeTypedValue(vtHeading); 
          // get the prop element

          pXDNTemp2->get_nextSibling(&pXDNTemp2); 
          pXDNTemp2->put_nodeTypedValue(vtProp);
          pXDNTemp2->get_nextSibling(&pXDNTemp2); 
          // get the type elment. it tell what sort of sorting goin to be 

          pXDNTemp2->put_nodeTypedValue(vtType);

      }else
      {
          //groupby element doesn't exists

          IXMLDOMElement *pGroupByElement;
          //create the element

          pXMLDoc->createElement(L"groupby",&pGroupByElement);
          IXMLDOMElement *pOrderElement;
          IXMLDOMNode *pOrderNode;
          //create the Order element in side groupby element

          pXMLDoc->createElement(L"order",&pOrderElement);
          pGroupByElement->appendChild(pOrderElement,&pOrderNode);
          IXMLDOMElement *pHeadingElement,*pPropElement,*pTypeElement, 
                                                        *pSortElement;
  
          IXMLDOMNode *pHeadingNode,*pPropNode,*pTypeNode, *pSortNode;
           _variant_t vtHeading("Encrypted"),vtSort("asc"),vtType("string"),
          vtProp("http://schemas.microsoft.com/mapi/string//
          {00020329-0000-0000-C000-000000000046}/Encrypted");
  
          //create the heading element and populate it with value

          pXMLDoc->createElement(L"heading",&pHeadingElement);
          pOrderNode->appendChild(pHeadingElement,&pHeadingNode);
          pHeadingNode->put_nodeTypedValue(vtHeading);
        
          //create the prop element and populate it with value

          pXMLDoc->createElement(L"prop",&pPropElement);
          pOrderNode->appendChild(pPropElement,&pPropNode);
          pPropNode->put_nodeTypedValue(vtProp);
          //create the type element and populate it with value

          pXMLDoc->createElement(L"type",&pTypeElement);  
          pOrderNode->appendChild(pTypeElement,&pTypeNode);
          pTypeNode->put_nodeTypedValue(vtType);
          pXMLDoc->createElement(L"sort",&pSortElement);
          pOrderNode->appendChild(pSortElement,&pSortNode);
          pSortNode->put_nodeTypedValue(vtSort);
        
          HRESULT hr;//= pXMLDoc->insertBefore(pOrderNode,NULL,NULL);

          IXMLDOMElement *pXMLRootElement;
          if(!FAILED(pXMLDoc->get_documentElement(&pXMLRootElement)))
          {
               _variant_t _vt;
               hr= pXMLRootElement->insertBefore(pGroupByElement,_vt,NULL);
          }
     }
      // get the xml out of the MSXML document object

      pXMLDoc->get_xml(&XML); 
      // put the xml to View

      pView->put_XML(XML);
      // Save method is a must to reflect change to the view

      pView->Save();
}

Add the following code to the OnFolderChange function to enable SMIME grouping.

// OnFolderChange

// .... 

// .. Old Code goes here



  pMessageFields->Add(L"Encrypted",CComVariant(8),L"Simple Emails"); 
         // you must call Update message to reflect the new field to 

          // mail item

        pMessage->Update();
   }
   pMessage = pcdoMessages->GetNext(); 
   }  // end of while


  CComPtr<Outlook::View> pV;
  HRESULT hr = MFolder->get_CurrentView(&pV);
  //now change its view state.

  ChangeView(pV);
}

Woof, I m tired of typing :O.

Well now, it's time to compile, compile your project and if everything goes OK, you'll have your emails sorted in two groups.

Adding items to right click menu

Sample screenshot

Well, I think this is the most awaited section of this tutorial, and most of you will be concerned of this part rather then digging into cryptography logic :).

Amit Dey has already explained a lot about adding items to menu and toolbars. If you've not read that article, it's time to read it as I m not explaining the details of CommandBars and all.

In order to add an item to the right click menu of Outlook, we'll have to map the OnUpdate event of Command Bars. We can acquire Command Bars object using the CommandBars property of the Explorer and sink its OnUpdate event.

First of all, add private members to hold the CommandBars objects. Move to OAddin.h header file and add the following lines:

 CComPtr<Office::_CommandBars> m_spCommandbars; //commandbars

 CComPtr<Office::CommandBarControl> m_pSortButton; // Sort Button

Let's first make necessary changes to our COAddin class to be able to sink the OnUpdate event. Move to OAddin.h class and derive your class from CommandBarsEvents class.

//

class ATL_NO_VTABLE COAddin : 
public CComObjectRootEx<CComSingleThreadModel>,
...
...
 // ExplorerEvents class fires the OnFolderSwitch event of Explorer

public IDispEventSimpleImpl<1,COAddin,&__uuidof(Outlook::ExplorerEvents)>,
public IDispEventSimpleImpl<2,COAddin,&__uuidof(Office::_CommandBarsEvents)>
now create a call back function declartion in your OAddin.h file as follows 
void __stdcall OnContextMenuUpdate();

Move to your cpp file and add the definition for the function.

//

void __stdcall COAddin::OnContextMenuUpdate()
{
    MessageBoxW(NULL,L"Hello Menu Update Event",L"Outlook Addin",MB_OK);
}

Now setup the sink map.

//

  
  BEGIN_SINK_MAP(COAddin)
  // sink the event for Explorer 

  // the first parameter is the same as given above while driving the class

  // the third parameter is the event identifier to sink i.e FolderChange 

  // rest of event id's can also be located using OutlookSpy or type libraries

  SINK_ENTRY_INFO(1,__uuidof(Outlook::ExplorerEvents),/*dispinterface*/0xf002
       ,OnFolderChange,&OnSimpleEventInfo) 

  SINK_ENTRY_INFO(2,__uuidof(Office::_CommandBarsEvents),/*dispinterface*/0x1,
       OnContextMenuUpdate,&OnSimpleEventInfo)
  END_SINK_MAP()

Now, we've settled up everything to sink the interface from the source interface. The best place to sink events is OnConnection. Move back to your OnConnection function and add the following line of code. As described earlier, we can retrieve the CommandBars from the Explorer object.

//... OnConnection function


// .. earlier code goes here

 hr = ExpEvents::DispEventAdvise((IDispatch*)m_spExplorer,
                         &__uuidof(Outlook::ExplorerEvents));
// .....

//.....


  CComPtr < Office::_CommandBars> spCmdBars; 
  hr = spExplorer->get_CommandBars(&spCmdBars);
  if(FAILED(hr))
   return hr;
  m_spCommandbars = spCmdBars; 
    //Sink the OnUpdate event of command bars

  hr = CmdBarsEvents::DispEventAdvise((IDispatch*)m_spCommandbars,
            &__uuidof(Office::_CommandBarsEvents));

And finally, you are done. OnUpdate event is fired whenever a CommandBar has to update, and so we'll have to find the right click menu and add item to it. The right click menu has a fixed name "Context Menu". Here, we'll have to enumerate CommandBars to find Context Menu.

CommandBars object contains child CommandBar controls, and each CommandBar control can contain child CommandBarControl and CommandBarButtons. We'll add a CommandBarControl to the menu. We can use Add method of CommandBar control to insert an item to CommandBar, and so our task is:

  • Enumerate CommandBars to find CommandBar named "Context Menu".
  • Get the CommandBar controls.
  • Call Add method on controls to insert item to CommandBar.

OK, one of the most important thing is that CommandBar objects are Locked by Outlook; in order to call the Add method, we'll have to remove the protection of the CommandBarControl object, or the call to Add method will fail with an assertion. We can use the Protection property of CommandBar to remove the protection.

Let's change the code of OnContextMenuUpdate. The code snippet is as follows:

//

void __stdcall COAddin::OnContextMenuUpdate()
{
  CComPtr<Office::CommandBar> pCmdBar;
  BOOL bFound =false;
  for(long i = 0; i < m_spCommandbars->Count ; i++)
  {
   CComPtr<Office::CommandBar> pTempBar;
   CComVariant vItem(i+1);   //zero based index

   m_spCommandbars->get_Item(vItem,&pTempBar); 
   if((pTempBar) && (!wcscmp(L"Context Menu",pTempBar->Name)))
   {
    pCmdBar = pTempBar;
    bFound = true;
    break;
   }
  // pCmdBar->Release();

  }
  if(!bFound)
     return;
 
  if(pCmdBar)//make sure a valid CommandBar is found

  { 
     soBarProtection oldProtectionLevel = pCmdBar->Protection ;
    // change the commandbar protection to zero

    pCmdBar->Protection = msoBarNoProtection;
    //set the new item type to ControlButton;

    CComVariant vtType(msoControlButton);
    //add a new item to command bar

    m_pSortButton = pCmdBar->Controls->Add(vtType);
    //set a unique Tag that u can be used to find your control in commandbar

    m_pSortButton ->Tag = L"SORT_ITEM";  
    //a caption

    m_pSortButton ->Caption = L"Sort By SMIME";     
    // priority (makes sure outlook includes this item in every time)

    m_pSortButton ->Priority =1 ;
    // visible the item

    m_pSortButton ->Visible = true; 
  }
  
}

Till now, if you compile the project, you'll be able to see an item in the right click menu of Outlook. But a control is useless until you add a handler to it. So now, let's get back to our OAddin class header file to derive it from _CommandBarButtonEvents to sink the events.

//


class ATL_NO_VTABLE COAddin : 
public CComObjectRootEx<CComSingleThreadModel>,
...
...
 // ExplorerEvents class fires the OnFolderSwitch event of Explorer

public IDispEventSimpleImpl<1,COAddin,&__uuidof(Outlook::ExplorerEvents)>,
public IDispEventSimpleImpl<2,COAddin,&__uuidof(Office::_CommandBarsEvents)>
, 
 // Its possible to sink event for a single command bar button 

 // and you can recognize the control using its face text 

 // but for this example i've sinked event for each command bar button

 public IDispEventSimpleImpl<3,COAddin, 
     &__uuidof(Office::_CommandBarButtonEvents)>,

Again, use the ATL_SINK_INFO structure to describe the callback parameters. Open the header file of the add-in object (OAddin.h) and add the following line at the very top:

extern _ATL_FUNC_INFO OnClickButtonInfo;

Open the cpp file of the class and add the following line to the top:

_ATL_FUNC_INFO OnClickButtonInfo = 
  {CC_STDCALL,VT_EMPTY,2,{VT_DISPATCH,VT_BYREF | VT_BOOL}};

Create a callback function.

//

void __stdcall OnClickButtonSort(IDispatch* /*Office::_CommandBarButton* 
      */ Ctrl,VARIANT_BOOL * CancelDefault);
//


void __stdcall COAddin::OnClickButtonSort(IDispatch* /*Office::_CommandBarButton* 
         */ Ctrl,VARIANT_BOOL * CancelDefault)
{
  // m_bEnableSorting is a member boolean variable 


  if(!m_bEnableSorting)
  {
   m_bEnableSorting =true;
   OnFolderChange(); // Sort The elements of current view;

  }
  else
  {
   m_bEnableSorting = false;
  }
}

Now, let's sink the event now. We'll sink the event as soon as the new CommandBarControl is created.

 // OnUpdate 

// .... Old code goes here.

   m_pSortButton ->Visible = true; 
  hr = CommandButtonEvents::DispEventAdvise((IDispatch*)m_pSortButton);
  if(hr != S_OK)
  {
   MessageBoxW(NULL,L"Menu Event Sinking failed",L"Error",MB_OK);    
  }
  CComQIPtr < Office::_CommandBarButton> spCmdMenuButton(m_pSortButton);

OK now, compile your project, and play with your menu. If you click it once, the Mail items are sorted automatically, and if you click it again, Mail items are not sorted again.

How about changing the state of the menu to checked?

Sample screenshot

Oh well, more to write :S, and well, more to read as well.

OK, earlier while I was developing a solution for my client, a Microsoft MVP (I'm not naming YOU ;)) replied to me that it's not possible to either add an icon to the right click menu items or change their state to checked, and I had to ship the project without these features to my client. Later, I discovered it using Outlook Spy and Amit Dey's article that it's a little tricky but not impossible.

OK, in order to change the state of the Menu item to checked, you just need to change the State property of the newly inserted CommandBarControl to Office::msoButtonDown, and you are done. The State property is not available to the newly inserted CommandBarControl and we'll have to convert it to CommandButton.

Following is the code of OnUpdate:

// OnUpdate

// .... Old code goes here.



  m_pSortButton ->Visible = true; 
 // ok this example needs to modify the new menu item to be displayed as CHECKED 

  // as well we get the equivalent commandbarbutton object of this object.

  CComQIPtr < Office::_CommandBarButton> spCmdMenuButton(m_pSortButton);

  if(m_bEnableSorting)
  { 

     //if sorting is enabled check mark the new menu item

    ATLASSERT(spCmdMenuButton);
    spCmdMenuButton->State = Office::msoButtonDown;
    m_bEnableSorting = true;
   }
 
// .. rest of code goes here

  hr = CommandButtonEvents::DispEventAdvise((IDispatch*)m_pSortButton);
  if(hr != S_OK)
  {
   MessageBoxW(NULL,L"Menu Event Sinking failed",L"Error",MB_OK);    
  }

NOTE: in order to add an ICON to the Menu item, you can use the PasteFace method to put an image to the button. The sample source code does it. The following is the code to put an icon on the button.

//


HBITMAP hBmp =(HBITMAP)::LoadImage(_Module.GetResourceInstance(),
   MAKEINTRESOURCE(IDB_BITMAP1),IMAGE_BITMAP,0,0,LR_LOADMAP3DCOLORS);

 // put bitmap into Clipboard

  ::OpenClipboard(NULL);
  ::EmptyClipboard();
  ::SetClipboardData(CF_BITMAP, (HANDLE)hBmp);
  ::CloseClipboard();
  ::DeleteObject(hBmp); 

 // change the button layout and paste the face

  spCmdMenuButton->PutStyle(Office::msoButtonIconAndCaption); 
  HRESULT hr = spCmdMenuButton->PasteFace();
  if (FAILED(hr))
   return ;

If you compile your project, you'll have a complete plug-in that sorts the elements according to security availability on the message.

How to use MSI to install CDO programmatically.

MSI can be used to install CDO programmatically. In order to use MSI in your C++ project, you'll have to import the MSI.dll into your project.

 #import "msi.dll" rename_namespace("MSI")

MSI requires the feature name and the product code in order to install CDO. The product code can be retrieved from Applicaton object of Outlook that is passed to OnConnection function. Following is the code snippet of MSI.

//

BSTR bstrCode;
spApp->get_ProductCode(&bstrCode);   
MSI::InstallerPtr pInstaller(__uuidof(MSI::Installer));
MSI::MsiInstallState o = pInstaller->GetFeatureState(bstrCode,L"OutlookCDO");
if(o != MSI::msiInstallStateLocal)
{
 pInstaller->ConfigureFeature(bstrCode,L"OutlookCDO",MSI::msiInstallStateLocal);
}

OK, that's it :).

I've tried to give as much description as possible in the tutorial.

The sample code provided is a C++ sample of what I did in VB. It is not optimized to give you the best results, moreover you may find some poor COM implementations in the code, but comments are invited :). Thanks. Cheers :).

~Blowin' me up with her love~

References and Acknowledgements

MSKB Articles:

  • Q220600 - How to automate Outlook using VC++.
  • Q238228 - Build an Office2000 COM add-in in VB.
  • Q259298 - How to use Outlook Object Model through #import.
  • Q173604 - How to use CommandBars in Outlook solutions.
  • Q182394 - How to use CommandBars in Outlook solutions.

MSDN Articles:

  • Building COM add-ins for Outlook 2000 - Thomas Rizzo.
  • Developing COM add-ins for MS Office 2000.

Books:

  • WROX Beginning ATL 3.0 COM Programming - Richard Grimes.

Other:

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here