Accessing the Address Book Data Using MAPI

Working with MAPI was a nightmare for me until I learned some basics of it. In this article, I will explain some different ways of accessing the address book data using Extended MAPI.

Extracting the data from a smaller address list will never give any problems. But, if the address book of the organization is very large (more than 5,000), we need some special handling of this address book. In my case, I had to handle an address list of more than 1 lakh (one hundred thousand) entries. This article explains how to extract info from a small address list and also a large address list.

Requirements

This article explains the development of Address book apps for Exchange Server 5.5 using Exchange Development Kit (Exchange SDK) and with Microsoft Visual C++ 6.0. The Exchange SDK can be downloaded from the Microsoft Exchange home page.

Basics of Working with MAPI

Any MAPI application and Exchange-related programs first need a profile to be created. The MAPI profile can be created through the Control Panel –> Mail option. To do this, one has to have a Exchange mailbox account created with one of the Exchange servers in the organization.

The next step in a MAPI program is to initialize the MAPI libraries with the MAPIInitialize function. When the program exits, it should call the MAPIUninitialize function. Very frequent calls to these two functions should be avoided because this could result in a memory overhead.

Logging into MAPI

This is the second step in a MAPI program. The MAPILogonEx function can be used (supply the profile name) with the MAPI_EXTENDED| MAPI_NEW_SESSION| MAPI_LOGON_UI| MAPI_EXPLICIT_PROFILE flags set. These flags ensure that the:

  1. Program starts a new MAPI Session without acquiring a new one.
  2. Opens a new MAPI user dialog if any authentication details are required.
  3. Ensures that it does not use the default profile.

Once the session is established, the session pointer can be used throughout the program to do a lot of the jobs Outlook can do. In fact, it can also be used to create/modify the Distribution lists (provided these are manipulated under the same Exchange site as the user is present), Viewing/Modifying/Creating public folders(if the user has access), and so forth.

Understanding IMAPITable

This IMAPITable is the standardised interface through which all the MAPI-related data are extracted. Some of the places where this can be used are the Address Book, Folders list, Mail Messages, Appointments, and so on. The samples in this article use these tables for displaying Address Book data. This IMAPITable should be used along with structures such as SRowSet and functions like IMAPITable->QueryRows or IMAPITable->HrQueryAllRowsand the like. These two functions will retrieve the data from the table and populate the data as an SRowSet array. Though it may not be clear at this point, the following code snippet may be of help.


LPSRowSet pRow;
LPMAPITABLE lpContentsTable = NULL;
LPABCONT lpGAL = NULL;
/*.. Write the code to get a pointer to the Global Address
/* List(GAL)
….
*/
//Use the GAL pointer to retreive the IMapitable
if(FAILED(hr = lpGAL->GetContentsTable(0L, &lpContentsTable)))
{
return hr;
}
//Get the data from the table into the pRow – SRowSet pointer
if ( FAILED ( hr = HrQueryAllRows (lpContentsTable,
(SPropTagArray*) &sptCols, NULL, NULL, 0,&pRow)))
{
return hr;
}

Understanding ENTRYID

Exchange Server stores each of the objects with an entry ID. Every object created inside Exchange Server, including Mailboxes, Distribution Lists, Folders, Messages, and so forth will be assigned an entry ID. An interface to the IMailBox, IDistList, IMapiFolder, IMessage and so on, can be obtained by calling the OpenEntry function with the ENTRYID of the corresponding object.

Property Tags in MAPI

Exchange keeps the properties of the objects in reference to property tags. The property tags are hexadecimal values defined with programmer-understandable tags starting with PR_. For example, PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, and PR_ENTRY_ID are used to store the display name, e-mail address, and entry ID of the objects. This can be roughly equated with the Column names of a database table.

Working with a Smaller Address List

This is a much easier operation. It is enough if the basic steps of MAPI are followed:

  1. Initialize MAPI using the MapiInitialize function.
  2. Log in to MAPI by using MapiLogonEx with a profile.
  3. Use the IMAPISession pointer to open the address book of the session with OpenAddressBook.
  4. Get the ENTRYID to the Global Address List using HrFindExchangeGlobalAddressList.
  5. Open the GAL using the ENTRYID.
  6. Now, the table can be generated by using the GetContentsTable function and the data can be displayed after using the HRQueryRows function.
  7. End all jobs, free all the used memory, and log off from the MAPI session. The attached sample project MAPIAddressList_demo.zip can be used as a reference. Although the given demo app is a console application, it can very easily be ported to a MFC GUI application.

Working with Larger Address Lists

This is when some care should be taken when writing GUIs. If the application is a small console application that is run in batch mode, the same code as Mapi_small_AddressList_demo.zip will be acceptable and the speed issues can be compromised.

But, when writing GUIs that involve lakhs of records of mailboxes and distribution lists, the following points should be considered:

  1. The data cannot be kept in memory in any data structure because it has a huge impact on the performance of the application and the computer.
  2. This data cannot also be loaded on to a grid or list box or list control because these controls also will experience some performance issues.

The best alternative will be to query the Exchange Server for data in small packets of 1 row or 10 rows. Although this will have an impact on the network traffic, the impact will not be large.

So, in cases like this, we will have to use the owner drawn controls of a list box or a Virtual List control using OWNER DATA.

Using a Owner Draw ListBox to Show Data

There is already a sample provided by Microsoft on this regard. The sample name is called “MAPI Address Book” or “ABVIEW”. There is a small bug in this code. It uses an integer variable to get and use the Row numbers. Because an integer cannot handle more than a 32K odd numeric value, I ran into trouble when using it on my code. I had an address book that is more than 1 lakh in size. But, this can be solved very easily. Just replace these integers with long data type variables. This class will serve the purpose if only the name has to be displayed in the application because one column is enough.

Using Virtual List Controls to Show Multiple Columns

A list control (CListCtrl) is used when there is a need to show Multiple data like the User Display Name, e-mail address, House Phone numbers, Office Phone Numbers, and so forth. The performance gain using this kind of list control is visible only if we work with larger address lists. I could see a huge performance gain with our production servers where we have a lakh records and the servers keep a replicated GAL at each site. But, if the servers are placed remotely, refreshing each row by getting the data from the servers can be very, very slow.

Using the source files

  1. Copy the MAPIUtils.h, MAPIUtils.cpp, MapiDataListCtrl.h, and MapiDataListCtrl.cpp files to the project folder and add them to the project.
  2. This project uses two icons, IDI_ICONDL and IDI_ICONMAILUSER, to differentiate a user and a distribution list. Copy them to the project.
  3. Place a List control on the dialog with the OWNER DATA property set.
  4. Include the MAPIUtils.h in the Source file of the dialog box and declare a variable.
  5. Add a call to MAPIInitialize once at the beginning of the application. When exiting the application, call the MAPIUninitialize() function.

Getting the profiles installed on the machine

A normal allocation to the MapiUtils class will load the profiles to the CArray variable MapiUtils::m_ProfileList. This array can be iterated to retrieve the list of Profiles installed in the system.

Loading the list control

  1. A call should be made to the StartandLogon function of the MapiUtils class, with the profile to be loaded.
  2. Declare a Pointer variable to the MapiDataListCtrl class and allocate memory to it. Pass the MapiUtils::m_pContentsTable pointer to the constructor and call the SubclassDlgItem function to assign the control to the class.
  3. Call the InsertColumn functions of the list control. The title of the columns should be one of the following:
    • Name
    • Phone
    • Alias
    • E-mail
    • Location
  4. The reason is because the OnGetdispinfo function, related to the LVN_GETDISPINFO message, gets the data according to the column names as specified above.
  5. If adding the column is being done for the first time, call the InitListBox function. Call the ChangeCount() function of the list control any time after this. This will start loading the data automatically.
  6. Do not forget to call stopandlogoff when closing the application.

Note: The project uses a PropTagArray as follows. This is used in the MAPIUtils.cpp file.


SizedSPropTagArray ( 8, sptCols ) = { 8,
PR_ENTRYID,PR_DISPLAY_NAME,PR_ACCOUNT,
PR_OBJECT_TYPE,PR_OFFICE_LOCATION,
PR_COMPANY_NAME,PR_EMAIL_ADDRESS,
PR_OFFICE_TELEPHONE_NUMBER};

These property tags are the ones that are treated similarly to column names in the Database tables. So, the Display name property is accessible from the PR_DISPLAY_NAME tag, the Alias name is from the PR_ACCOUNT tag, and so on. If more data is needed, the property tags supported are available at the MSDN Property Tags link.

After adding the property tags, add a column to the list control and add the code inside the OnGetdispinfo function as follows.


//Does the list need text information?
if (pItem->mask & LVIF_TEXT)
{
CString l_strText;

int l_iRet;
LVCOLUMN pColumn;
char strBuffer[100];

pColumn.mask =LVCF_TEXT;
pColumn.pszText =strBuffer;
pColumn.cchTextMax=sizeof(strBuffer);

l_iRet = GetColumn(pItem->iSubItem,&pColumn);
//… Retain the code as it is till this point
//Add this code snippet
else if(strcmpi(pColumn.pszText,”NewColumnName”)==0 &&
(lpRows->cRows > 0))
{
LPSPropValue lpDN = PpropFindProp(lpRows->aRow[0].lpProps,
lpRows->aRow[0].cValues,
PR_NEW_PROPERTY);
lpDN != NULL ? l_strText = lpDN->Value.lpszA : l_strText=
“”;
}
//…Add the rest of the code as in the examples
}

Include files and libraries

Ensure that the edk.h is included in the project.

Link to the following libraries:
Edkguid.lib, Edkutils.lib, Edkmapi.lib, Addrlkup.lib, Edkdebug.lib, Mblogon.lib, mapi32.lib, Msvcrt.lib, and Version.lib.

Please visit the http://www.codersource.net Web site, run by the author.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read