Platform SDK: Exchange 2000 Server

Frequently Asked Questions

[This is preliminary documentation and subject to change.]

This topic covers the most frequently asked questions about programming with Microsoft® Exchange 2000 Server.

CDO for Exchange

What's the difference between the IBodyPart.BodyParts collection and the IMessage.Attachments collection on a Message object?

Why can't I write text to the Stream object of a newly added BodyPart object?

How do I set message headers on Message or BodyPart objects when there aren't properties for them?

How can CDO objects expose multiple dual interfaces?

Web Store

How do I get all properties for all items using SELECT *?

What type of URL should I use: file or http?

Does MAPI still work?

Do MAPI forms still work?

What's changed in the security model for the Web Store?

What's the difference between the IBodyPart.BodyParts collection and the IMessage.Attachments collection on a Message object?

Although both the IBodyPart.BodyParts and IMessage.Attachments properties return IBodyParts interfaces on collection objects, the objects contained within each collection normally differ. For MIME encoded messages, a good rule of thumb is that all body parts with content-disposition set to "attachment" are returned in the IMessage.Attachments collection. Depending upon the complexity of the MIME structure, these body parts can exist deep in the body part hierarchy. The IBodyPart.BodyParts collection, on the other hand, contains only that object's immediate child BodyPart objects.

For example, a common MIME structure is expressed in the diagram below as a BodyPart hierarchy below the Message object, labeled A. Each object is designated with a letter and appropriate mail header fields are listed in each box representing an object:

A <--- content-type="multipart/mixed"
  B <--- content-type="multipart/related"
    C <--- content-type="text/plain"
    D <--- content-type="text/html"
  E <--- content-disposition="attachment"
  F <--- content-disposition="attachment"

Object A is the Message object, the root of the hierarchy. All others are BodyPart objects. The IMessage.Attachments collection for the Message object would contain BodyPart objects E and F. For each object, its IBodyPart.BodyParts collections would contain only the direct descendants of the object in the hierarchy (remember that instances of the Message COM class expose the IBodyPart interface as well as instances of the BodyPart COM class). For object A (the Message object), this would include objects B, E and F. For object B (a BodyPart object), the collection would contain objects C and D. Objects E and F have empty BodyParts collections as they do not have descendants.

For UUENCODE formatted messages, the hierarchy is only one level deep, as all body parts are necessarily deemed attachments. In this case, only the Message object has descendents, and the IMessage.Attachments and IBodyPart.BodyParts collections are the same. For example,

A
  B <-- UUENCODE attachment
  C <-- ditto
  D <-- ditto

Why can't I write text to the Stream object of a newly added BodyPart object using _Stream::WriteText?

ADO Stream objects are always one of two types: text or binary. When you first add a BodyPart object to a Message object's body part hierarchy, the content media type defaults to application/octet-stream, rendering a binary decoded content stream (this only applies to the decoded content stream, as the encoded content stream is by definition text). The _Stream.WriteText method fails because this method is only defined to function on streams that are of the text type.

To use the WriteText method, you have to set stream type to text. You can do this in one of two of the following ways:

[Visual Basic]
' EXAMPLE for Step 1
'   Assume that we have a BodyPart object reference in the variable Bp1, 
'   and that we wish to add a body part that will hold a text-based 
'   content stream.

 Dim Bp2 as IBodyPart
 Dim Strm as ADODB.Stream

 Set Bp2 = Bp1.AddBodyPart
 Bp2.ContentMediaType = "text/plain"

 Set Strm = Bp2.GetDecodedContentStream
 Strm.WriteText "this is the text content here"

 ' Don't forget to flush after you've made your deposit
 Strm.Flush
 
 ' EXAMPLE for 2

 Dim Bp3 as IBodyPart
 Set Bp3        = Bp1.AddBodyPart
 Set Strm       = Bp3.GetDecodedContentStream
 Strm.Type      = adTypeText
 Strm.WriteText "<P>this is some <I>more</I> content</P>"
 Strm.Flush
 Bp3.ContentMediaType = "text/html"
 ' ...

How do I set message headers that are not properties on the IMessage or IBodyPart interfaces?

All message and body part headers can be accessed through an appropriate Fields collection, for example, IMessage.Fields, or IBodyPart.Fields. Each mail header resides in the urn:schemas:mailheader namespace. For example, the Message-ID mail header is identified with the full name urn:schemas:mailheader:message-id.

The provided fields within the urn:schemas:mailheader namespace are the most common headers defined by the internet community. However, you can add other headers not a part of this default set by adding it to the Fields collection within the urn:schemas:mailheader namespace. For example, 'urn:schemas:mailheader:Some-Header.'

Dim Msg as New CDO.Message
Dim Flds as ADODB.Fields

Set Flds = Msg.Fields
With Flds
  .Append("urn:schemas:mailheader:message-id") = id
  .Append("urn:schemas:mailheader:custom")   = "some value"
  .Update
End With

How can CDO objects expose multiple dual interfaces?

The short answer is: they can't. By definition, a dual interface is one that both extends the IDispatch interface, and provides access to defined methods both through the vtble and through the seven methods defined by the IDispatch interface (hence the name dual). Because COM uses proxy/stubs to marshal arguments between separate apartments, objects can only really expose one IDispatch interface and you can't trick COM proxy/stubs by returning different addresses depending upon which interface you request IDispatch from (readers interested in knowing the details of this rule are directed to the section marked Advanced below). Therefore, there's one and only one IDispatch interface per object.

This restriction is no problem for languages such as Microsoft® Visual Basic® and Microsoft® Visual J++® that support direct binding to OLE Automation compatible interfaces. All CDO interfaces use these types, so you can navigate to and use each in the standard way for that language. For example, the CDO Message object exposes the IMessage, IDataSource, and IBodyPart dual interfaces. In Visual Basic, you first add references to the ADODB and CDO type libraries, then type each interface variable using the types in these type libraries, and finally use the Set keyword to navigate to each interface. With Microsoft® Visual J++®, use the jactivex tool to create necessary Microsoft virtual machine-compatible Java packages, and then import these packages into their .java source file(s). Examples of each procedure are given in the following examples:

[Visual Basic]
Dim iMsg  as CDO.Message
Dim iDsrc as CDO.IDataSource
Dim iBp   as CDO.IBodyPart
Dim iDisp as Object

Set iBp   = New CDO.Message   ' Have IBodyPart
Set iDsrc = iBp               ' Have IDataSource
Set iMsg  = iBp               ' Have IMessage
Set iDisp = iBp               ' Have IDispatch
[Microsoft J++]
import cdo.*;

class Main {
  public static void main( String args[] ) {
    IMessage    iMsg  = null;
    IDataSource iDsrc = null;
    IBodyPart   iBp   = null;
    Object      iDisp = null;

    iBp   = (IBodyPart)   new Message(); // IBodyPart
    iDsrc = (IDataSource) iBp;           // IDataSource
    iMsg  = (IMessage)    iBp;           // IMessage
    iDisp = (Object)      iBp;           // IDispatch
    // ...
  }
}

In both cases, we finish with references to all three CDO interfaces and a reference to the IDispatch interface each on the same object.

The situation is different for scripting languages such as Microsoft® Visual Basic® Scripting Edition (VBScript), and Microsoft® JScript™. With these languages, all interactions with COM objects occur through the object's IDispatch interface. Since each object can only have one IDispatch interface, we need separate objects with different implementations of IDispatch, not separate interfaces. Furthermore, we need a way for these languages to access these other objects. To facilitate this, many CDO interfaces define properties and the GetInterface method that return other objects (rather than interfaces) exposing the appropriate IDispatch implementation. Yet the illusion of "interface navigation" is produced because essentially, it appears to the scriptwriter that the interface itself is being returned. The following code demonstrates this illusion using the CDO Message object:

[VBScript]
Option Explicit
Dim iMsg
Dim iDsrc
Dim iBp
Set iMsg  = CreateObject("CDO.Message")      ' IMessage    (IDispatch)

Set iDsrc = iMsg.DataSource                  ' IDataSource (IDispatch)
Set iDsrc = iMsg.GetInterface("IDataSource") ' Again IDataSource (IDispatch)

Set iBp   = iMsg.BodyPart                    ' IBodyPart (IDispatch)
Set iBp   = iMsg.GetInterface("IBodyPart")   ' Again IBodyPart (IDispatch)
[Microsoft JScript]
var iMsg  = null;
var iBp   = null;
var iDsrc = null;

iMsg  = New ActiveXObject("CDO.Message");  // IMessage (IDispatch)

iBp   = iMsg.BodyPart;                     // IBodyPart (IDispatch)
iBp   = iMsg.GetInterface("IBodyPart");    // Again IBodyPart (IDispatch)

iDsrc = iMsg.DataSource;                   // IDataSource (IDispatch)
iDsrc = iMsg.GetInterface("IDataSource");  // Again IDataSource (IDispatch)

In each case, the script actually interacts with separate objects (object identities really), each exposing an appropriate implementation of IDispatch; no interface navigation per se is occurring. Instead, object navigation is occurring. The scriptwriter need not be concerned with such details however; the CDO objects handle all of the details internally. Most importantly, the CDO object behaves the same irrespective of which object is used.

[C++,IDL]
Note to C/C++ Programmers (Advanced)

If you write code that depends upon object identity (the physical address of the IUnknown interface), you should be aware of the behavior outlined above. If you use the interface properties such as IMessage::get_DataSource or IMessage::get_BodyPart, or you use the GetInterface method to retrieve interfaces, you're actually getting a different object identity. Consider the following code:

#include <iostream.h>
#include "cdoex.h"
#include "cdoex_i.c"

void main() {
  CoInitialize(NULL);

  IMessage*    pMsg  = NULL;
  IDataSource* pDsrc = NULL;
  IUnknown*    pUnk  = NULL;
  IUnknown*    pUnk2 = NULL;

  /*
  ** Create an instance of the Message CoClass
  */
  CoCreateInstance(CLSID_Message,
                   NULL,
                   CLSCTX_INPROC_SERVER,
                   IID_IUnknown, 
                   (void**)&pUnk);

  /* 
  **  have IUnknown (controlling) for Message object 
  ** Get the IMessage interface on the object 
  */
  pUnk->QueryInterface(IID_IMessage,(void**)&pMsg);

  /* 
  ** Get IDataSource interface using property.
  */

  pMsg->get_DataSource(&pDsrc);

  /* 
  ** Navigate back to IUnknown interface using new IDataSource prop.
  */
 
  pDsrc->QueryInterface(IID_IUnknown,(void**)&pUnk2);


  /*
  ** Check if these are the same address
** We know the answer...they're different, but let's 
** be scientific about it...
  */

  if(pUnk == pUnk2)
    cout << "pUnk == pUnk2" << endl;
  else
    cout << "pUnk != pUnk2!" << endl;

  pUnk->Release();
  pUnk2->Release();
  pDsrc->Release();
  pMsg->Release();

  CoUninitialize();
}

If you compiled and executed this code, you would find that the program would emit

pUnk != pUnk2

In fact, this is the correct behavior: a separate object identity (and hence a different IUnknown address) is returned when an interface is retrieved through a property on the interface or with GetInterface. This time, the IDispatch interface covers the implementation of the requested interface (IDataSource), essentially "shifting" from the previous.

You can think of it this way: C++, Microsoft® Visual Basic®, and Visual J++ can use multiple oleautomation interfaces on the same object. Scripting languages, however, require an object model with a one-to-one correspondence between object and IDispatch interface. The answer is to provide both: CDO objects conform to both models, exposing multiple oleautomation compatible interfaces on the object for use by C++, Microsoft® Visual Basic®, and Visual J++, and spawning separate objects (one for each type of interface) to accommodate scripting languages. For each spawned object, the IDispatch implementation covers the functionality of the target dual interface in "round-robin" fashion.

You may be wondering why all of this is needed. It may appear at first glance that with a clever enough implementation of the IUnknown methods for each interface, you could trick the client into using separate IDispatch implementations by returning the appropriate interface address through an interface property or GetInterface. However, this only works when the CDO object resides in the same apartment as the caller. If the client calls from a separate COM apartment (or process, or even machine process), COM silently provides a proxy and stub manager to remote the calls. The client is then invoking methods through interfaces aggregated on the proxy object, not directly on the object itself. The proxy rigorously enforces the COM object identity laws and additionally assumes that there is only one address for each interface exposed by the object. Being ignorant of our devious attempt to return "appropriate" IDispatch physical addresses in round robin fashion to the client, it instead preempts us, always returning the first IDispatch address retrieved by the client, and irrespective of how this interface is subsequently requested. To circumvent this behavior, we are forced to return interfaces exposed on different object identities (and therefore though different proxy objects) to get the new IDispatch address (through the new proxy object) to the client in the other apartment. The price we pay for this approach is one interface (vtbl) duplicate for each exposed interface per IDispatch "shift."

This behavior is essential to understand if you intend to aggregate CDO interfaces on your objects.

What type of URL should I use? file or http?

Use the file scheme. For example:

file://./backofficestorage/domain/store/folder/path/to/item

When using the file: OLE DB URL namespace, you do not need to explicitly specify the provider binder used to bind the item. The Web Store OLE DB provider is registered for this namespace. For example:

Dim Rec as New Record
Rec.Open "file://./backofficestorage/domain/store2/folder1/item3.doc", , adModeReadWrite
' The connection is implicitly created by the Open command.
Dim Conn as Connection
Set Conn = Rec.ActiveConnection
....

HTTP URLs are supported. However, the OLE DB root binder will use the Provider binder for the Internet Publishing (MSDAIPP) provider if you do not explicitly specify the Web Store OLE DB Provider (ExOledb.datasource) binder. The MSDAIPP OLE DB provider is currently not supported for use with the Web Store and Microsoft® Exchange 2000 Server.

To explicitly specify the Web Store OLE DB Provider binder, set the Datasource property on the ADO Connection object used to bind to the item:

Dim Conn as New ADODB.Connection
Conn.Provider="exoledb.datasource"
Conn.Open "http://servername/public"

Dim Rec as New Record
Rec.Open "http://servername/public/folder1/item.eml", Conn, adModeReadWrite
...

Note that you must specify at least the path to the root folder of the information store from which you plan to open items in the call to Conn.Open. You do not need to specify the full path to each item to open the connection to the store. To access items in another store, you need a separate Connection object. For example:

Dim Conn1 as New ADODB.Connection
Dim Conn2 as New ADODB.Connection

Conn1.Provider="exoledb.datasource"
Conn1.Open "http://servername/public_store_1"

Conn2.Provider="exoledb.datasource"
Conn2.Open "http://servername/public_store_2"

Dim Rec as New Record
Rec.Open "http://servername/public_store_1/folder1/item.eml", Conn1, adModeReadWrite
...
Rec.Open "http://servername/public_store_2/folder2/item2.eml", Conn2, adModeReadWrite

Does MAPI still work?

Yes. Exchange 2000 Server information stores still support the Messaging API (MAPI) and other technologies based upon MAPI such as CDO 1.2.1.

Do MAPI forms still work?

Yes. All forms written using C++ and MAPI will continue to function as before.

What's changed in the security model for the Web Store?

The Microsoft Web Store supports folder, item, and property level access control and stores this information using the standard Microsoft® Windows NT®, Microsoft® Windows® 2000 security descriptor format. This means that applications can secure any item in the Web Store and properties of these items using both grant and deny access control entries. For backward compatibility, the per-folder MAPI PR_ACL_TABLE format is still supported. For more information, see.

The Web Store also supports an XML format for security descriptors. See . This format enables application developers that must run code on hosts that do not have access to the stock Win32 security APIs to read and write Web Store item descriptors. Such hosts include Microsoft® Windows® 95, and Microsoft® Windows® 98. Note that this format can only be used to manage descriptors for items in the Web Store.

The Web Store defines a special security principle (trustee) called a Role. Roles are defined using item and folder properties and contain a list of other Web Store roles and Microsoft® Windows® 2000 trustees. A role is only valid within descriptors for Web Store items (folders, etc.) and is meaningless for other objects, such as Active Directory and file system objects. These roles are not the same as "roles" in Microsoft® Outlook®, or roles in COM+. For more information, see .

How do I get all properties for all items using SELECT *?

You cannot get all properties for all items since each item in the folder could conceivably have different properties defined for each. When running SELECT * commands on a folder, you receive only the list of properties that are defined by the schema for the folder. To get all the properties for a particular item, bind to it directly using ADO, CDO, OLE DB, or HTTP/WebDAV. See the Web Store Schema section in the Concepts and Architecture chapter of this documentation.