Enumerating Exchange Contacts in .NET

I gave myself a goal this weekend: to write a simple application that would enumerate all the contacts for a given Exchange user so that I could acquire some experience in Exchange programming. I knew absolutely nothing about the Exchange programming model, or what was actually available to me as a developer. As usual, there was a rather large object model to learn and, of course, tons of documentation with samples that sometimes work well and sometimes don't. The following article shows what I learned this weekend about the world of Exchange Programming in .NET.

Exchange Programming Basics

Exchange is more than a mail server—it is a tool for storing, securing, exchanging, and collaborating information between groups of people. Think about it: You can deal with such things as Calendar events, Notes, Reminders, and such with Outlook (which talks to Exchange)—all of which have nothing to do, directly, with e-mail. You have folders that act as repositories of information, some of which are public and some of which are private. And, of course, you have mailboxes that are responsible for storing and sorting mail content. Once you take away the view you usually have of Exchange through the eyes of Outlook or some other mail client, it really begins to look like a large directory service or database—full of information to create, update, delete, query, and so forth; this is the inherent reality of the Exchange Server.

Building from the collaborative nature of Exchange, there is constructed a set of COM objects and associated interfaces collectively known as CDO, or the Collaborative Data Objects. This is the technology I chose to write my application in because it was the first one I encountered that allowed me to accomplish my task at hand. However, it is by no means the only available tool Exchange developers have at their disposal. CDO is exclusively for applications that reside on the Exchange Server itself, and is not designed to write large-scale distributed applications. If you want to talk to your Exchange Server from the outside world, you have WebDAV: a protocol that extends the HTTP 1.1 specification to allow for the retrieval of objects and their properties through additional methods. WebDAV can be programmed against using Microsoft technologies by way of the various HTTP WebRequest/Response and XML frameworks (both managed and unmanaged exists) you have at your disposal. A current fallback to CDO is that it does not have a direct port to the .NET runtime, meaning that are no fully managed CDO frameworks and COM interop is necessary. For the most part, this effort went smoothly for me, however, and I didn't have to concern myself too much with COM; as you'll see later, though, I purposefully took the long route in a couple of instances for educational purposes.

Necessary Objects and Interfaces

Three technologies were used in my application: .NET, CDO, and ADO. If you're unfamiliar with CDO, you might find it odd that I'm using ADO in addition to it. The reason is that there exists an ADO provider for the Exchange store (which is, as of Exchange Server 2000, backed by Active Directory), so one can use SQL statements to query, update, and retrieve data. The ADO provider for Exchange is known as ExOLEDB. It may seem a bit foreign, at first, to use SQL statements to bind to and retrieve information from the Exchange Store, but remember your earlier realization that the store is nothing but a large database anyway.

The following table displays the objects and interfaces used in my application:

Interface or Object Technology Description
IPerson CDO Interfaces with an Exchange user
IDataSource CDO Interfaces with the data source for a CDO object
IMailBox CDO Interfaces with the mailbox for an Exchange user
Record ADO An ADO Record object
RecordSet ADO An ADO RecordSet object
Field ADO The field of an ADO RecordSet

The object and interface descriptions above are relatively self-explanatory, and really need no further investigation (for more detailed information, refer to MSDN). Again, remember that all of these interfaces and objects require interop assemblies or runtime marshalling if they are to be subsumed by the .NET runtime.

The CDO COM servers needed to generate the interop assemblies only exist on the Exchange server itself—which happens to not be my development machine (and is probably not your development machine either). Two methods of getting the assemblies, then, were apparent to me (although there are probably better options out there). First, when you download and install the Exchange SDK from Microsoft, you are given a couple type libraries, one of which is cdoex.tlb. This type library describes all the types housed by the CDO COM server, CDOEX.dll. This is not the route I took, however, because it was a little later (after I had already generated my interop assemblies) that I noted the type libraries existed in the SDK install folders \Exchange SDK\SDK\Support\CDO\). What I did was manually locate CDOEx.dll on the Exchange server, drag it locally, and reference it with Visual Studio (which generated an interop assembly as well). Both methods are okay, but referencing the type library seems less "hackish" to me.

I encountered one problem, however, that I have been so far unable to neither understand nor resolve: the Person object and IPerson interfaces are totally missing from my interop assembly. Thankfully, the Person object supports IDispatch and so I could talk to the interfaces through automation, but this unfortunately made the code a bit less appealing to look at.

The Collaborate Data Objects Code

Let me describe the code now, pausing every so often to show actual code to keep your attention as I move about. The first task was unfortunately the most frustrating and is due to the problem I mentioned earlier: that my interop assembly did not have the Person object within it. I had to manually load the type information (via the type libraries) using the Person's CLSID and the runtime's System.Type.GetTypeFromCLSID:

// Load the type library for Person.
System.Type PersonType =
   System.Type.GetTypeFromCLSID(new System.Guid("CD000107-8B95-
                                11D1-82DB-00C04FB1625D"));
// CoCreateInstance on the type.
System.Object PersonObject =
   System.Activator.CreateInstance(PersonType);\

What this code does is first load the type library for the Person object via the object's CLSID (the process first looks in the Registry for the type registered with this CLSID and then loads its type library). With this information in hand, the .NET runtime is ready to actually work on the type, which includes generating a valid COM instance through CoCreateInstance. The .NET wrapper around this functionality is the System.Activator.CreateInstance() method, which takes as its sole parameter the type object retrieved earlier (which the runtime dutifully cracks and uses to create a real COM object for you).

After calling CreateInstance, one would assume I have a valid Person object, right? Well, not exactly. I only have a Person object, but the object isn't bound to any real data—so the person is totally faceless; this presents an obvious problem if you are to work with it. The Person CoClass exposes three interfaces to make it functional: IPerson, IDataSource, and IMailBox. To bind the Person object to actual data, you use its IDataSource interface. This means you have to adjust yourselves to look at IDataSource in lieu of IPerson, which, in terms of COM, means a call to QueryInterface and in .NET an explicit cast to your desired interface.

CDO.IDataSource DataSourceInterface =
   PersonObject as CDO.IDataSource;
if (DataSourceInterface != null)
{
   // load myself.
   DataSourceInterface.Open(
      "MAILTO:ben@littlebigendian.com",
      null,
      ADODB.ConnectModeEnum.adModeRead,
      ADODB.RecordCreateOptionsEnum.adFailIfNotExists,
      ADODB.RecordOpenOptionsEnum.adOpenSource,
      System.String.Empty, System.String.Empty);

In C#, the "as" keyword means "attempt to cast to the desired type, and if the cast failed, set the LHS to null." I check to verify that the casting succeeded and then proceed to attach the Person object to data by specifying an e-mail address; this is just the process that is done in CDO.

Enumerating Exchange Contacts in .NET

Now, I perform a couple of steps for posterity's sake; that is, I attempt to retrieve the Person's e-mail address and verify that I have a handle on real, valid data. I do so with the following code:

// can we talk to Person yet? is it hydrated?
System.String Email = (System.String)PersonType.InvokeMember(
   "Email",
   System.Reflection.BindingFlags.GetProperty,
   null,
   PersonObject,
   null);
if (Email != null)

This odd syntax is the result of me not having any type information for Person in my interop assembly—so all calls to methods or properties of this type must be done at runtime in a late-bound fashion. This technique, known as automation, is very well formulated and is used by many technologies, especially scripting languages (like VBScript, for example). Under the hood, the .NET runtime follows the same steps as these scripting languages, and queries the IDispatch interface on the Person object, and calls its Invoke method with nearly the same parameters as I pass to InvokeMember above. The result is that between the .NET runtime and the COM runtime, the proper method or property gets called by name as opposed to memory address. The result is the same regardless of how the method is called, albeit more slowly achieved via automation.

Once I have successfully bound the Person object to a real data source and verified that I get a valid (well, non-null) e-mail address back from its Email property, I then am left to accomplishing my task at hand; that is, enumerating my user's contacts from the Exchange store. I mentioned earlier that there are three interfaces with which to get things done on the Person object, and you have used two of them thus far: IPerson and IDataSource. The final interface, IMailBox, is going to be wielded now to retrieve your Contact list (and could be used to retrieve much more). The IMailBox interface has, as a property, Contacts—which returns the LDAP URL for my user's contact folder (I'll explain this more later). Alas, my interop assembly is bogus with respect to this interface too and I am therefore left to automating a call to the IMailBox interface to retrieve the value of my Contacts property. At this point, I realized I could have fought with the tlbimp utility to discover why my type libraries were weird, but I realized that, this being my free time, I might as well plow ahead because things were getting relatively interesting. The results of the next puzzle were quite fascinating and took me a good couple minutes to wrap my head around.

Without strongly typed information regarding the IMailBox interface, how do I call methods on it: through automation, right? How does automation work: thanks to IDispatch, right? Both IPerson and IMailBox implement IDispatch, right? Frustratingly, the answer to all of these questions is Yes, meaning a bit of a conundrum on my part. Given type information for Person, I call InvokeMember to talk to its methods and properties. InvokeMember requires an IDispatch interface pointer to accomplish any means of automation, and it must call QueryInterface to retrieve that pointer. It is a steadfast rule of COM that all interfaces publicly supported by a COM object can be browsed through IUnknown's QueryInterface. So, how do I get InvokeMember to talk to the correct IDispatch interface pointer so that I can automate my calls to IMailBox instead of IPerson? Interesting, huh? Therein lays the torture that is multiple dual interfaces—something not encountered by .NET programmers a whole lot these days but that plagued COM programmers of yesteryear. The solution is a workaround supported by the IPerson interface: the method GetInterface that returns the IDispatch interface for the requested interface. Given a System.Object instance wrapped around the returned IDispatch pointer, I then can successfully retrieve my IMailBox interface pointer:

MethodArgs = new System.Object[1];
MethodArgs[0] = "IMailBox";
System.Object MailBoxObject = PersonType.InvokeMember(
   "GetInterface",
   System.Reflection.BindingFlags.InvokeMethod,
   null,
   PersonObject,
   MethodArgs);

I can finally automate a call to the IMailBox interface's Contacts property to retrieve the LDAP URL for the Contacts folder in the Exchange Store:

Contacts = (System.String)MailBoxType.InvokeMember(
   "Contacts",
   System.Reflection.BindingFlags.GetProperty,
   null,
   MailBoxObject,
   null);

The next section will explain what I mean by the LDAP URL and show how I used the ADO libraries to return my Contacts using the LDAP URL.

The ActiveX Data Objects Code

LDAP stands for the Lightweight Directory Access Protocol and is used to query and work with data stored in a LDAP-compliant database. The view LDAP has on a database isn't relational—it's built atop namespaces and object hierarchies instead. A URL (which can be easily used to refer to objects and namespaces) seems a smart technique, then, to use when querying such a database. As a case in point concerning LDAP URLs, you can examine the string I get back when I invoke the Contacts property using the code snippet above:

file://./backofficestorage/kiene.org/MBX/ben/Contacts

The goal here is not to crack the URL format in its entirety, although it is important to take note of a couple points. The server's name, Kiene.org, is immediately followed by a list of folder names that eventually ends in Contacts. What this means, effectively, is that the list of contacts can be found under the Contacts directory of user ben, which exists under the MBX directory on the server Kiene.org (MBX is the root folder for all private folders in the Exchange store). These folders are not actual filesystem folders, but instead are logical groups within the Exchange Store for harboring like information. The LDAP URL simply acts as a reference or pointer into the correct folder in the store.

Retrieving the LDAP URL is important because the ExOLEDB ADO provider for Exchange uses it as a data source as can be seen in the following code snippet (for you C#-heads out there, take note of the System.Type.Missing parameter; this is how VARIANT optional parameters are passed as default via the .NET runtime):

// I'm going to use the ExOLEDB type here to communicate with the
// Exchange store through the ADO interfaces.
ADODB.Record record = new ADODB.Record();
ADODB.Recordset recordSet = new ADODB.Recordset();

// the file:// protocol is registered with the ADO root binder
// so that it knows to bind this to ExOLEDB
record.Open(
   ContactsFolder,
   System.Type.Missing,
   ADODB.ConnectModeEnum.adModeUnknown,
   ADODB.RecordCreateOptionsEnum.adFailIfNotExists,
   ADODB.RecordOpenOptionsEnum.adOpenRecordUnspecified,
   System.String.Empty,
   System.String.Empty);

Now that your Contacts folder has been successfully bound to by way of your Person's Contacts folder LDAP URL, you can perform an SQL query against the data to retrieve that which interests you. In this example, I was purely interested in the e-mail addresses of the various contacts, and so ran the following SQL query:

// ExOLEDB now is opened with the record and recordset objects
// holding the same connction interface. You now can do queries
// (which are translated into calls to the exchange store/active
// directory by the provider).
System.String SourceQuery = "SELECT \"DAV:href\", " +
   " \"urn:schemas:contacts:email1\" " +
   "FROM scope('shallow traversal of \"" + ContactsFolder + "\"') ";

recordSet.Open(
   SourceQuery,
   record.get_ActiveConnection(),
   ADODB.CursorTypeEnum.adOpenForwardOnly,
   ADODB.LockTypeEnum.adLockReadOnly,
   (System.Int32)ADODB.CommandTypeEnum.adCmdUnspecified);

The SQL statement takes a bit of time to get used to, but note that you're selecting properties (such as email1) of the objects you find within the contacts folder by referring to their URNs—or Universal Resource Names. Like URLs, URNs are a way of uniquely referring to an object or property, and so are employed in a like manner here within your SQL statement. Suffice it to say that this statement queries for the email1 property on each contact (which is the default e-mail address value) and returns it in my recordSet variable (which is an ADO RecordSet object). The query can be taken as IJW (It Just Works), but if you're interested in the actual structure of these Exchange Store queries, refer to MSDN. All that is left to do now is simply iterate over the recordSet variable and do what you want with the values.

Summary

In this article, you learned about the CDO architecture a bit, and how ADO plays into it through the ExOLEDB provider for Exchange. You saw, firsthand, some .NET interop with COM's late binding facilities and how it can be used to talk to COM even if type libraries don't necessarily exist. You also learned a bit about the Exchange programming model and how to write applications for it and its Active Directory-backed data store.

One last point to make, however, concerns the installation and execution of any application you write using this technique on Exchange. You must have the proper permissions to read a user's mailbox (which, by default, the Administrator account does not have). For more information, refer to http://support.microsoft.com/default.aspx?scid=kb;en-us;821897 and carefully and thoughtfully proceed through the steps outlined within this document.



About the Author

Ben Rush

Ben Rush is a software engineer for Little Big Endian Contracting (http://www.littlebigendian.com), a software contracting company specializing in .Net and Windows development. He can be reached at ben@littlebigendian.com, and you can visit his blog at http://www.ben-rush.net/blog.

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: May 7, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT This eSeminar will explore three popular games engines and how they empower developers to create exciting, graphically rich, and high-performance games for Android® on Intel® Architecture. Join us for a deep dive as experts describe the features, tools, and common challenges using Marmalade, App Game Kit, and Havok game engines, as well as a discussion of the pros and cons of each engine and how they fit into your development …

  • With JRebel, developers get to see their code changes immediately, fine-tune their code with incremental changes, debug, explore and deploy their code with ease (both locally and remotely), and ultimately spend more time coding instead of waiting for the dreaded application redeploy to finish. Every time a developer tests a code change it takes minutes to build and deploy the application. JRebel keeps the app server running at all times, so testing is instantaneous and interactive.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds