If you want to refer to this content, please link to this page instead of copying it, as I might be adding some more source code examples later.



 


 
PLEASE NOTE

The following code samples might be obsolete. They might not work with current version of the mapi33 dll, though they will still work using the out-of-date (but quite well-working and free) version of the mapi33 dll included in the samples zip file.

To get up-to-date information about the mapi33 dll and to buy the most recent version, visit the mapi33 website at
http://www.mapi33.adexsolutions.com/





Extended MAPI with C#

A Basic Introduction to Extended MAPI Using C# and the Mapi33 Library.


Author: Jan Pichler (mail AT jan DOT to)

Last changed: 2004-05-20



Content

 

1      Abstract and Confession............................................................................................................................ 3

2      MAPI Architecture................................................................................................................................... 4

2.1        MAPI? What is that?........................................................................................................................ 4

2.2        The MAPI Family - CMC, CDO, Simple MAPI and Extended MAPI.................................................. 4

2.2.1      Common Messaging Calls (CMC)................................................................................................. 4

2.2.2      Collaboration Data Objects (CDO)................................................................................................ 4

2.2.3      Simple MAPI............................................................................................................................... 4

2.2.4      Extended MAPI........................................................................................................................... 4

2.2.5      Outlook Redemption..................................................................................................................... 5

2.3        The Concept of Extended MAPI....................................................................................................... 5

2.4        Sources of Documentation................................................................................................................ 5

3      The Mapi33 Library................................................................................................................................... 6

3.1        Introduction...................................................................................................................................... 6

3.2        Code Conversion Examples............................................................................................................... 6

4      C# Functional Code Samples...................................................................................................................... 7

4.1        Starting a MAPI Session................................................................................................................... 7

4.2        Closing a MAPI Session................................................................................................................... 7

4.3        Resolving a Recipient's Name into Name and Email Address.............................................................. 8

4.4        Selecting one or multiple Recipients from the MAPI Address Book..................................................... 9

4.5        Get a String Representation of Recipients Name/Email from Recipient EntryID.................................. 10

4.6        Get Recipient's EntryIDs from Address Book Entries........................................................................ 12

4.7        Tricky: Opening an Outlook PST File and Reading its Contents.......................................................... 12

5      Conclusion.............................................................................................................................................. 17

6      References............................................................................................................................................. 18

 

1               Abstract and Confession

 Many programmers not familiar with MAPI use Simple MAPI or CDO to complete basic email- and contact management tasks. While these 'primitive' tools might carry satisfactory results in certain situations, they have many major disadvantages in comparison to Extended MAPI. While Simple MAPI and CDO are available as ready-to-use COM components, and are easily accessible with nearly every  programming language, the Extended MAPI interface uses complex structures and lower-level techniques that imply the use of C-based languages or Delphi. Even in modern and powerful languages like C#, the few available structures make it highly difficult to implement Extended MAPI. The mapi33.dll is an Extended MAPI wrapper entirely written in managed C#. It provides simple access to the Extended MAPI API and enables a MAPI programming comfort similar to appropriate developments in C++. As C/C++ is the most frequently used language for MAPI access, nearly all of the existent documentation is C++ based. Therefore, the knowledge of translating C++ to C# plays an important role in C#  based Extended MAPI programming.

This paper will try to give a brief overview of Extended MAPI architecture and explain relevant concepts, while it's main focus lies on illustrating possible implementation approaches with functional code samples.

I am not really familiar with C++. I don't really know a lot about MAPI and the Mapi33 library. I know that some deeply experienced MAPI programmers might roll on the floor when they read a newbie's ideas about how MAPI works. This paper is not intent to give a global, complete overview of the MAPI world, as this is beyond my possibilities. Much more, this paper is supposed to be a superficial introduction for all those unfamiliar with MAPI, a point to start off from and do further research into the desired direction. There might be a little smarter ways to do some things that my sample codes do, even though I think most of the code works very well.

2               MAPI Architecture

2.1                   MAPI? What is that?

Microsoft introduced the Messaging Application Programming Interface (MAPI) because they felt "committed to making a reality of this vision of electronic messaging as the "central nervous system" for organizational communication" [1].

MAPI provides a layer functionality between applications and the underlying messaging systems. It allows applications to access different (MAPI enabled) messaging systems without having to support them explicitly. Various applications can interact with each other and various messaging systems - without even having to 'know' the counter part. Practical everyday life samples for this technique are many MS Outlook integrated fax and even SMS applications.

2.2                   The MAPI Family - CMC, CDO, Simple MAPI and Extended MAPI

There is a basic difference between Extended MAPI and all other MAPI-derived interfaces: While MAPI 1.0 not only concludes but itself provides the whole range of MAPI functionality, all other MAPI derivates are just wrappers for certain aspects of just this MAPI 1.0 library. Because of that, the direct access of Extended MAPI significantly increases speed while offering the whole range of MAPI functionality, transparency and stability.

2.2.1              Common Messaging Calls (CMC)

The CMC API is an obsolete and therefore deprecated MAPI interface for C/C++ languages. It is built directly on top of the core MAPI subsystem, and limited to the following basic messaging tasks:

  • Sending messages
  • Retrieving messages
  • Looking up addressing information

2.2.2              Collaboration Data Objects (CDO)

The CDO library (aka "OLE Messaging", "Active Messaging") is a COM wrapper for the MAPI library. It is accessible with all development languages that support automation. CDO is primarily designed for client activities. It does not provide the full MAPI functionality - but still more then Simple MAPI does:

  • Log onto the messaging system with specific profiles or with anonymous authentication
  • Compose messages, address and resolve recipients, send, receive, and read messages, add attachments, automate replies
  • Manage calendars, create meetings and appointments
  • Manage folders and messages within the information store.
  • Manage Addresses, especially within the Personal Address Book (PAB)

2.2.3              Simple MAPI

Simple MAPI is a subset of 12 functions, which enable programmers to add basic messaging support to their applications. Its functionality includes

  • Log onto the messaging system
  • Compose new messages, add and resolve recipients, send messages
  • Retrieve and read messages from the inbox

2.2.4              Extended MAPI

Extended MAPI (or "MAPI 1.0") comprehends the straight-forward access of the whole MAPI library. Originally, Microsoft designed this library for C/C++ implementation only. By and by, all of the required C++ headers were translated to Pascal and used within Delphi. Since the development of the Mapi33 library has reached production quality, C# is also absolutely suited for designing completely MAPI enabled applications.

2.2.5              Outlook Redemption

The Outlook Redemption COM component [7] is no member of the MAPI Family in general. Redemption is - in contrast to all other listed MAPI interfaces - not developed by Microsoft. As the need for an easy-to-use component that handles MAPI issues circumventing today's security warnings arose, Dmitry Streblechenko developed a commercial COM component that provides simplified access to a certain range of Extended MAPI functionality for nearly all development languages, including highest-level languages like Visual Basic Script (VBS) or Visual Basic for Applications (VBA).

Redemption more or less directly offers a small part of the real Extended MAPI objects an structure, but its main focus lies on extending the capabilities of simpler MAPI wrappers like CDO and Simple MAPI (eg. managing profiles, circumventing security warnings when browsing the contact list etc.). Hence, it is not directly comparable to native Extended MAPI.

2.3                   The Concept of Extended MAPI

All MAPI interaction is session-based. Before performing any actions in the MAPI context, a session has to be established. At logon time, the system (or the user) has to specify the name of the MAPI profile to work in as well as required access credentials (username and password). Multiple applications are able to - but don't have to - share a single session. After MAPI interaction has finished, a MAPI logoff is performed which closes the active session if it is no longer used.

Most of the MAPI interaction is profile-based. All productive MAPI actions happen in the scope of a initially chosen profile. The only relevant actions that are performed outside a profile are administrative actions regarding the addition, modification and deletion of profiles and corresponding properties.

2.4                   Sources of Documentation

There are multiple sources for MAPI documentation, code examples and public discussion of MAPI-related topics. These include

  • The good old Google search [3]
  • Microsoft's Strategic White Paper for MAPI [1]
  • Microsoft's Messaging Application Programming Interface Programmer's Reference [4]
  • Numerous MAPI developer websites [5]
  • Public forums and discussion boards [6]

3               The Mapi33 Library

3.1                   Introduction

The Mapi33 native Extended MAPI wrapper for .NET consists of a single file, MAPI33.dll. It can be used and redistributed freely without any conditions or contributions [2].

Referenced in a project, the library provides the namespace MAPI33 which includes all objects, structures, types, enumerations etc. necessary for accessing Extended MAPI. As the library is entirely written in managed C#, it doesn't require the programmer to use unsafe code blocks or access external APIs or COM components.

Though still under development, the library works very well. There is no adequate documentation available by now,  but as most C++ code snippets can be intuitively converted to equivalent Mapi33/C# calls, this doesn't obstruct development at all.

3.2                   Code Conversion Examples

The following code sample represents a very easy yet working syntax-only conversion between C++ and Mapi33/C#:

C++ code

IProfAdmin *iprofadmin;

hr = pMAPIAdminProfiles(0, &iprofadmin);

if (hr != S_OK) return;

hr = iprofadmin->CreateProfile("Test", NULL, PtrToUlong(hwnd), 0);

 

IMsgServiceAdmin *imsadmin;

hr = iprofadmin->AdminServices("Test", NULL, PtrToUlong(hwnd), 0, &imsadmin);

C# code

IProfAdmin iprofadmin;

hr = MAPI.AdminProfiles((MAPI.FLAGS) 0, out iprofadmin);

if (hr != Error.Success) return;

hr = iprofadmin.CreateProfile("Test", null, _Handle, 0);

        

IMsgServiceAdmin imsadmin;

hr = adm.AdminServices(tmpProfileName, null, _Handle, 0, out imsadmin);

This code sample demonstrates a conversion which presumes some basic knowledge about C++ and MAPI data types:

C++ code

SizedSPropTagArray(2, mscols) = {2, {PR_SERVICE_UID, PR_DISPLAY_NAME}};

mstable->SetColumns((SPropTagArray*)&mscols, 0);

 

SRowSet *msrows;

hr = mstable->QueryRows(1, 0, &msrows);

mstable->Release();

C# code

Tags[] mscols = new Tags[] {Tags.PR_SERVICE_UID, Tags.PR_DISPLAY_NAME};

tblMessageService.SetColumns(mscols, 0);

 

MAPI33.MapiTypes.Value[,] msrows;

hr = tblMessageService.QueryRows(1, 0, out msrows);

mstable.Dispose();

4               C# Functional Code Samples

The following examples show some common MAPI tasks.

DOWNLOAD: You may download a fully functional (MS Visual Studio 2003) implementation of all listed sample codes as well as a small test application including the free MAPI33 DLL here.

All methods except the first one presume an established MAPI connection and an active MAPI session (stored in the instance variable session).

4.1                   Starting a MAPI Session

The following code starts a MAPI session, which is required for nearly every MAPI operation:

Error hr;

IMAPISession session;

// Initialise MAPI interface driver

hr = MAPI.Initialize(null);

 

// Logon to the MAPI session with the following flags:

// MAPI.FLAGS.Extended     Use Extended MAPI interface

// MAPI.FLAGS.LogonUI      Show a user dialog if additional credentials required

// MAPI.FLAGS.NoMail       Do not check for new mail etc.

// MAPI.FLAGS.USEDEFAULT   Try to use the default profile if possible

// There are several other flags, see the MAPI33.MAPI.FLAGS enumeration for details

//

// This call initialises a MAPI session without logon credentials. As all MAPI

// methods, this method does not return the IMAPISession object itself but a Error

// value specifying the execution result of the call. The result is either

// Error.Success or every other element of the Error enumeration. The object of

// the created session is passed as out parameter (out session).

 

hr = MAPI.LogonEx(IntPtr.Zero, null, null,   MAPI.FLAGS.Extended |

                                             MAPI.FLAGS.LogonUI |

                                             MAPI.FLAGS.NoMail |

                                             MAPI.FLAGS.USEDEFAULT,

                                                                  out session);

// Returns true if successful

return (hr == Error.Success);

4.2                   Closing a MAPI Session

This code closes a MAPI session:

session.Logoff(IntPtr.Zero, 0);

 

// You have to dispose every MAPI object explicitly, otherwise an exception

// will be thrown at runtime.

 

session.Dispose();

 

// Finally, we do uninitialise the MAPI driver.

 

MAPI.Uninitialize();


4.3                   Resolving a Recipient's Name into Name and Email Address

This method resolves the given part of a recipient's name or email address. The MAPI Library checks the underlying IAddrBook and returns all matches. The method ResolveName() returns Error.Success if there is exactly one match, Error.NotFound if there are no matches and Error.AmbiguousRecip if there are multiple. The parent window's handle is needed in all sample codes for showing dialogs modally.

public string[] ResolveName(string _Name, IntPtr _Handle)

{

   Error hr;

   IAddrBook ab = null;

   string[][] txtRecipients = new string[1][];

   txtRecipients[0] = new string[0];

 

   try

   {

      ADRENTRY[] abe;

 

      // We open the standard address book

      hr = session.OpenAddressBook(_Handle, Guid.Empty, 0, out ab);

     

      if (hr != Error.Success)

      {

         // Error opening the address book

         ab.Dispose(); // we MUST dispose stuff!

         return null;

      }

 

      // We tell the method that we have a fragment of the PR_DISPLAY_NAME

      // property of a recipient.

      abe = new ADRENTRY[1];

      abe[0] = new ADRENTRY(1);

      abe[0].PropVals[0] = new MapiString(Tags.PR_DISPLAY_NAME, _Name);

     

      // We perform our first call _without_ showing any dialogue, for that

      // if our lookup fails and we have no matches, there will be shown no

      // "Recipient unknown" dialog. If there is more than one match, we will

      // do another resolve-call that will bring up a dialog to select the match.

      hr = ab.ResolveName(_Handle, 0, null, ref abe);

 

      if (hr == Error.AmbiguousRecip)

      {

         // We have more than one match - now we will call the method again

         // and tell it to ask for the user to choose a recipient.

        

         abe = new ADRENTRY[1];

         abe[0] = new ADRENTRY(1);

         abe[0].PropVals[0] = new MapiString(Tags.PR_DISPLAY_NAME, _Name);

        

         // Next try, this time with dialog

         hr = ab.ResolveName(_Handle, IAddrBook.FLAGS.Dialog, null, ref abe);

      }

        

      if (hr == Error.Success)

      {

         // We have a single match.

         // Get a suitable string representation of the selected recipient.

         // Look at the used methods to see details about what happens.

         txtRecipients = NormalizeAddressEnties(GetEntryIdsFromABEntries(abe));         }

 

      ab.Dispose(); // We MUST dispose stuff!

 

      // If there are no matches (Error.NoMatch), array is still empty.

      // Otherwise, we will return the match.

      return txtRecipients[0];

   }

   catch (Exception e)

   {

      // An exception has been thrown - return null

      ab.Dispose(); // We MUST dispose stuff!

      return null;

   }

}

4.4                   Selecting one or multiple Recipients from the MAPI Address Book

This code shows the standard "Select Recipient" dialog and lets the user chose one or multiple recipients.

public string[][] GetMapiRecipients(IntPtr _Handle)

{

   Error hr;

   IAddrBook ab = null;

   string[][] txtRecipients = null;

 

   try

   {

      // Open address book

      hr = session.OpenAddressBook(_Handle, Guid.Empty, 0, out ab);

  

      ADRENTRY[] abe = new ADRENTRY[0];

      ADRPARM abp = new ADRPARM();

      abp.Caption = "Choose your recipient(s)..."; // Caption of the dialog

      abp.Flags = 0x21;

      abp.DestFields = 1; // How many categories (to/cc/bcc) do we have?

      abp.DestWellsTitle = "Add following recipients:"; // Some text...

      abp.DestComps = new ADRPARM.DESTCOMPS[] {ADRPARM.DESTCOMPS.Orig};

      abp.DestTitles = new String[]{"TO:"}; // Title for our "TO" category

 

      // Show dialog

      hr = ab.Address(_Handle, ref abp, ref abe);

 

      // Get a suitable string representation of the selected recipient.

      // Look at the used methods to see details about what happens.

      txtRecipients = NormalizeAddressEnties(GetEntryIdsFromABEntries(abe));

 

      // Important - don't forget!

      ab.Dispose();

  

      return txtRecipients;

   }

   catch (Exception e)

   {

      ab.Dispose();

      return null;

   }

}


4.5                   Get a String Representation of Recipients Name/Email from Recipient EntryID

This method returns the full name and email string of each passed ENTRYID in a string[2]. It could - for example - return a string[] {"John Doe", "john.doe@microsaft.at"}.

private string[][] NormalizeAddressEnties(ENTRYID[] eids)

{

   Error hr;

   IAddrBook ab = null;

   IUnknown unknown = null;

   IMailUser mu = null;

   ArrayList resolvedRcps = new ArrayList();

 

   try

   {

      // We open our standard address book..

      hr = session.OpenAddressBook(IntPtr.Zero, Guid.Empty, 0, out ab);

     

      if (hr != Error.Success)

      {

         ab.Dispose();

         return new string[0][];

      }

 

      for (int i=0; i < eids.Length; i++)

      {

         if (eids[i] != null)

         {

            ENTRYID ei = eids[i];

            string[] entry = new string[2];

            MAPI.TYPE retType;

 

            // Here we open the recipient object by it's ENTRYID

            hr = ab.OpenEntry(ei, Guid.Empty,IAddrBook.FLAGS.BestAccess, out retType,

                                                                     out unknown) ;

            if (hr == Error.Success)

            {

               if (unknown != null)

               {

                  // Is our object really an individual recipient (no group etc.)?

                  if (retType == MAPI.TYPE.IndividualRecipient)

                  {

                     mu = (IMailUser)unknown;

 

                     // Now we query the following properties:

                     Tags[] itags = new Tags[] {Tags.PR_GIVEN_NAME, Tags.PR_SURNAME,

                                    Tags.PR_SMTP_ADDRESS, Tags.PR_EMAIL_ADDRESS};

 

                     MAPI33.MapiTypes.Value[] ivals = new MAPI33.MapiTypes.Value[0];

 

                     // Now perform query

                     hr = mu.GetProps(itags, IMAPIProp.FLAGS.Default, out ivals) ;

                    

                     // Parse all attributes

                     // NOTE: We check all those PR_SMTP_ADDRESS_A, PR_SMTP_ADDRESS_W

                     // PR_EMAIL_ADDRESS_A, PR_EMAIL_ADDRESS_W etc. tags because we

                     // don't really know what our folder will return. I experienced

                     // different behaviors of Outlook in internet mail mode, Outlook                    

                     // in workgroup mode, Outlook in Exchange domain etc. ...

                     // I check for a containing "@" because I even got some Exchange

                     // addresses (in /DOM=domain/USER=john.doe -style) returned!

                     // There might be more serious ways to do this, anyway...

                     if (ivals.Length > 0)

                     {

                        for (int k=0; k < ivals.Length; k++)

                        {

                           // HACK: We use ANY tag just to know that it has not been

                           // set yet. PR_DEBUG seems to be a good choice...

                           Tags attrTag = Tags.PR_DEBUG;

                           string attrVal = "";

 

                           if (ivals[k] .GetType() == typeof(MapiString))

                           {

                              attrTag = ((MapiString)ivals[k]).PropTag;

                              attrVal = ((MapiString)ivals[k]).Value;

                           }

                           if (ivals[k].GetType() == typeof(MapiUnicode))

                           {

                              attrTag = ((MapiUnicode)ivals[k]).PropTag;

                              attrVal = ((MapiUnicode)ivals[k]).Value;

                           }

                       

                           if (attrTag != Tags.PR_DEBUG && attrVal.Length > 0)

                           {

                              switch (attrTag)

                              {

                                 case Tags.PR_SURNAME_A:

                                 case Tags.PR_SURNAME_W:

                                    entry[0] = entry[0] + attrVal;

                                    break;

                             

                                 case Tags.PR_GIVEN_NAME_A:

                                 case Tags.PR_GIVEN_NAME_W:

                                    entry[0] = attrVal +" "+ entry[0];

                                    break;

 

                                 case Tags.PR_SMTP_ADDRESS_A:

                                 case Tags.PR_SMTP_ADDRESS_W:

                                 case Tags.PR_EMAIL_ADDRESS_A:

                                 case Tags.PR_EMAIL_ADDRESS_W:

                                    if (attrVal.IndexOf("@") != -1 && entry[1] ==

                                                                            null)

                                       entry[1] = attrVal;

                                    break;

                              }

                           }

                        }

                     }

 

                     mu.Dispose();

                     mu = null;

                  }             

               }

            }

 

            // Do we have an email address for this recipient?

            if (entry[1] != null && entry[1].Length > 0)

               resolvedRcps.Add(entry);

 

            unknown.Dispose();

            unknown = null;

         }

      }

   }

   catch

   {

      // Well, no success...

   }

 

   // Always dispose...

   if (ab != null)

      ab.Dispose();

 

   if (unknown != null)

      unknown.Dispose();

 

   if (mu != null)

      mu.Dispose();

 

   return (string[][]) resolvedRcps.ToArray(typeof(string[]));

}

4.6                   Get Recipient's EntryIDs from Address Book Entries

The ResolveName() and Address() methods each return a bunch of the recipients properties. This function does nothing but isolate and return the ENTRYID of each given Address Book Entry (equals 1 recipient).

private ENTRYID[] GetEntryIdsFromABEntries(ADRENTRY[] abe)

{

   ENTRYID[] entries = new ENTRYID[abe.Length];

 

   // Iterate through Address Book Entries

   for (int i=0; i < abe.Length; i++)

   {

      ADRENTRY aktEntry = abe[i];

      byte[] eidBytes = null;

 

      // Iterate through current ADRENTRY's propVals, look for ENTRYID...

      for (int j=0; j < aktEntry.PropVals.Length; j++)

      {

         if (aktEntry.PropVals[j].PropTag == Tags.PR_MEMBER_ENTRYID)

         {

            // Yes, we got it...

            eidBytes = (byte[])((MapiBinary)aktEntry.PropVals[j]).Value;

            break;

         }

      }

 

      // Did we find an ENTRYID?

      if (eidBytes != null)

      {

         // Add it to our array - basically that's it.

         entries[i] = new ENTRYID(eidBytes);

      }

      else

      {

         entries[i] = null;

      }

   }

  

   return entries;

}

4.7                   Tricky: Opening an Outlook PST File and Reading its Contents

Did you imagine you could do that? Who did not always want to write a little program to access PST files and - for example - extract all email content/attachments/contacts etc. to a physical folder? Now you can.

We use a little trick (in fact, this trick appears to be the only way to do what we want): We create a temporary profile (which is, intrinsically seen, nothing but a normal profile we delete once we are finished), attach our PST file, and access it subsequently as it has become nothing but our profile's new message store. Thanks to the incredible speed of 'native' Extended MAPI, you won't notice any delay caused by our profile operations at all. For demonstration purposes, our method returns just a string containing the subjects and senders of all emails located directly in the inbox folder. You may have to change the inbox folder's name (eg. 'Inbox', 'Posteingang' etc.) for the program to detect it correctly.

My main guidance for this code was the 'MAPI example code for getting folders and messages' by Lucian Wischik [5]. In fact, most of the code is just a C++/C# translation. On his page you can find much more good examples of what MAPI can do.

public string OpenMessageStoreFromFile(IntPtr _Handle, string _Filename)

{

   Error hr;

   IProfAdmin adm;

 

   string tmpProfileName = "TemporaryProfileToAccessPstFile";

   string inboxFolderName = "Posteingang";

  

   // --------------------------------------------------------------------------

   // PART ONE: We create a new profile and attach our PST file as message store

 

   // First, we get the profile administration object...

   hr = MAPI.AdminProfiles((MAPI.FLAGS) 0, out adm);

   if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());

 

   // ...and create a new profile

   hr = adm.CreateProfile(tmpProfileName, null, _Handle, IProfAdmin.FLAGS.Default);

   if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());

  

   // Now we can get the administration object for our new profile

   IMsgServiceAdmin msa;

   hr = adm.AdminServices(tmpProfileName, null, _Handle, IProfAdmin.FLAGS.Default,

                                                                        out msa);

   if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());

 

   // We create a new message service of type "MS PST" (a PST file) in this profile

   hr = msa.CreateMsgService("MSPST MS", tmpProfileName, _Handle,

                                          IMsgServiceAdmin.FLAGS.ServiceUIAllowed);

   if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());

 

   // We get the message services table

   IMAPITable tblMessageService;

   hr = msa.GetMsgServiceTable(IMsgServiceAdmin.FLAGS.Default,

                                                            out tblMessageService);

   if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());

 

   // And the the corresponding UID

   Tags[] itags = new Tags[] {Tags.PR_SERVICE_UID, Tags.PR_DISPLAY_NAME};

   tblMessageService.SetColumns(itags, IMAPITable.FLAGS.Default);

  

   // In Mapi33, the C++ data type SRowSet has been implemented as rectangular array

   MAPI33.MapiTypes.Value[,] rows;

   hr = tblMessageService.QueryRows(1, IMAPITable.FLAGS.Default, out rows);

   if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());

 

   tblMessageService.Dispose(); // Don't need it any longer

     

   Guid msgSvcGuid = new Guid((byte[])((MapiBinary)rows[0,0]).Value);

 

   // here we configure our message service to use OUR pst file

   MAPI33.MapiTypes.Value[] ivals = new MAPI33.MapiTypes.Value[1];

   ivals[0] = new MapiString(Tags.PR_PST_PATH, _Filename);

   msa.ConfigureMsgService(msgSvcGuid, _Handle,

                                    IMsgServiceAdmin.FLAGS.ServiceUIAllowed, ivals);

 

   // Never forget to dispose all the no longer used MAPI stuff

   // NOTE: We won't dispose IProfAdmin for now as we will need it later on to delete

   // the temp profile

   msa.Dispose(); // Don't need it any longer

 

   // -------------------------------------------------------------------

   // PART TWO: Ok. We have attached the PST to our temp profile and got its

   // name. Let's see what we can do with it... At first we will look for our

   // inbox folder

 

   // So we get a list of all message stores in our profile

   IMAPITable tblMsgStores;

   hr = session.GetMsgStoresTable(0, out tblMsgStores);

   if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());

  

   // Define the cols we want to read

   itags = new Tags[] {Tags.PR_ENTRYID, Tags.PR_DISPLAY_NAME};

   tblMsgStores.SetColumns(itags, IMAPITable.FLAGS.Default);

 

   // Then we simply take the first row (for we have only one message store,

   // our PST)...

   hr = tblMsgStores.QueryRows(1, 0, out rows);

   if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());

  

   tblMsgStores.Dispose(); // Don't need it any longer

 

   // Read its ENTRYID and DISPLAY_NAME

   ENTRYID folderEid = null;

   string name = "";

 

   if (rows[0,0].GetType() == typeof(MapiBinary))

      folderEid = new ENTRYID((byte[])((MapiBinary)rows[0,0]).Value);

 

   if (rows[0,1].GetType() == typeof(MapiString))

         name = ((MapiString)rows[0,1]).Value;

  

   if (name.Length == 0 || folderEid == null) throw new Exception("Error PST props");

  

   // Now we can open our message store

   IMsgStore store;

   hr = session.OpenMsgStore(_Handle, folderEid, Guid.Empty,

                                             IMAPISession.FLAGS.Default, out store);

   if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());

 

   // We will query the EID of the root folder now

   itags = new Tags[] {Tags.PR_IPM_SUBTREE_ENTRYID};

   ivals = new MAPI33.MapiTypes.Value[] {new MapiBinary(Tags.PR_IPM_SUBTREE_ENTRYID,

                                                                     new byte[0])};

   hr = store.GetProps(itags, 0, out ivals);

   if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());

  

   ENTRYID rootFldEid = new ENTRYID((byte[])((MapiBinary)ivals[0]).Value);

  

   // We can open the root folder now...

   MAPI.TYPE type;

   MAPI33.IUnknown unkRootFolder = null;

   hr = store.OpenEntry(rootFldEid, Guid.Empty, 0, out type, out unkRootFolder);

  

   if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());

   if (type != MAPI.TYPE.Folder) throw new Exception("Error: Wrong type");

   if (unkRootFolder == null) throw new Exception("Error: Expected folder is NULL");

 

   IMAPIFolder fldRootFolder = (IMAPIFolder)unkRootFolder;

  

   // ... and get a list of all contained objects

   MAPI33.IUnknown unkRootFolderObjects = null;

   hr = fldRootFolder.OpenProperty(Tags.PR_CONTAINER_HIERARCHY, IMAPITable.IID, 0, 0,

                                                         out unkRootFolderObjects);

   if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());

   if (unkRootFolderObjects == null) throw new Exception("Error: Table is NULL");

  

   IMAPITable tblRootFolderObjects = (IMAPITable)unkRootFolderObjects;

 

   // Again, we define the cols we want to query

   itags = new Tags[] {Tags.PR_ENTRYID, Tags.PR_DISPLAY_NAME, Tags.PR_SUBFOLDERS};

   hr = tblRootFolderObjects.SetColumns(itags, 0);

   if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());

  

   // Perform the query

   hr = tblRootFolderObjects.QueryRows(50, 0, out rows);

   if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());

 

   // Not disposing MAPI objects will cause an exception!

   unkRootFolder.Dispose();

   fldRootFolder.Dispose();

   unkRootFolderObjects.Dispose();

   tblRootFolderObjects.Dispose();

 

   // Now we iterate through all containes objects to find the inbox folder,

   // then we save the names of all contained items into a string and return it.

   // There are several other ways to fetch default folders (like inbox, contacts

   // etc.), but for this example, identification by name is way sufficient.

   ENTRYID fldInboxEid = null;

   for (int i=0; i <= rows.GetUpperBound(0); i++)

   {

      string aktName = ((MapiString)rows[i,1]).Value;

      if (aktName.ToLower().Equals(inboxFolderName.ToLower()))

      {

         // We found our folder :)

         fldInboxEid = new ENTRYID((byte[])((MapiBinary)rows[i,0]).Value);

         break;

      }

   }

  

   if (fldInboxEid == null) throw new Exception("Error: Couldn't find inbox!");

  

   // -------------------------------------------------------------------

   // PART THREE: We open our inbox folder and access the contained objects

 

   // Open inbox folder

   IUnknown unkInboxFolder;

   hr = store.OpenEntry(fldInboxEid, Guid.Empty, 0, out type, out unkInboxFolder);

   if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());

   if (type != MAPI.TYPE.Folder) throw new Exception("Error: Wrong type");

   if (unkInboxFolder == null) throw new Exception("Error: Expected folder is NULL");

 

   IMAPIFolder fldInboxFolder = (IMAPIFolder)unkInboxFolder;

 

   // Get the content table

   IMAPITable tblInboxFolderObjects;

   hr = fldInboxFolder.GetContentsTable(0, out tblInboxFolderObjects);

   if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());

  

   // We just want to retrieve the subject and sender of each message

   itags = new Tags[] {Tags.PR_SENDER_NAME, Tags.PR_SUBJECT};

   hr = tblInboxFolderObjects.SetColumns(itags, 0);

  

   // Perform query

   hr = tblInboxFolderObjects.QueryRows(50, 0, out rows); // max. of 50 items

   if (hr != Error.Success) throw new Exception("Error: "+ hr.ToString());

 

   // Not disposing MAPI objects will cause an exception!

   unkInboxFolder.Dispose();

   fldInboxFolder.Dispose();

   tblInboxFolderObjects.Dispose();

   store.Dispose();

  

   // Now we can easily iterate through our results

   string result = "";

   for (int i=0; i <= rows.GetUpperBound(0); i++)

   {

      result += string.Format("Msg from \"{0}\": {1}\r\n",

                     ((MapiString)rows[i,0]).Value, ((MapiString)rows[i,1]).Value);

   }

 

   // Ok, we have what we wanted - let's remove the temporary profile and finish

   adm.DeleteProfile(tmpProfileName, 0);

   adm.Dispose(); // Don't need it any longer

  

   return result;

}

5               Conclusion

As you can see, Extended MAPI and C# is no contradiction at all. The advantage of native Extended MAPI in comparison to all other MAPI Interfaces are numerous. With its incredible performance and stability (compared to other interfaces) and with its extensive range of functionality, Extended MAPI in my opinion even compensates the disadvantage of significantly longer and (partly much) more complicated application development. C# implementation is nearly identical to C++, the only difference is the C# programmer's dependence on the Mapi33 library.

Though this library works absolutely flawless, there is a little drawback as - at this time - there is no source code available. Programmers who use Mapi33 will always depend on it, and on the programmer's not changing his mind and Redemption-like charging license fees for future versions of Mapi33.

6               References

[1]        Strategic White Paper for MAPI, Microsoft

 

[2]        Mapi33 developer's homepage         
            http://www.mapi33.freeservers.com/

 

[3]        Google web search                         
            http://www.google.com/

 

[4]        Messaging Application Programming Interface (MAPI) Programmer's Reference, Microsoft

 

[5]        MAPI developer websites:

            OutlookCode.com                           
            http://www.outlookcode.com/

            Lucian Wischik's brilliant code         
            http://www.wischik.com/lu/programmer/mapi_utils.html

 

[6]        MAPI forums:

            Expert's Exchange                          
            http://www.experts-exchange.com/

            WROX Press Programmer's forum 
            http://p2p.wrox.com/

 

[7]        Outlook Redemption COM component

            http://www.dimastr.com/redemption/

 

 

 

 

Supported by Kreativwarenhandlung Grafik Design