Search the Catalog
CDO and MAPI Programming with Visual Basic

CDO and MAPI Programming with Visual Basic

Developing Mail and Messaging Applications

By Dave Grundgeiger
October 2000
1-56592-665-X, Order Number: 665X
380 pages, $29.95

Chapter 7
Enhancing the Email Client

The previous chapter discussed the basics of sending and receiving email messages. The techniques given there are enough to complete a wide variety of email tasks--perhaps most that you're likely to run into. However, if you're writing a complete email client application or if your messaging application requires exceptional control over email, you'll need more. This chapter rounds out the discussion of CDO capabilities as they apply to email. With the information in this chapter, you'll have complete control over the email experience.

How CDO Wraps MAPI Objects

Your understanding of CDO behavior will be heightened greatly if you remember the following: CDO objects and MAPI objects are not the same thing.

MAPI objects are COM objects exposed by the MAPI Subsystem and third-party software designed to be MAPI-compliant. Although they are COM objects, MAPI objects are not easily accessible through Visual Basic, and they are not accessible at all through scripting languages. Therefore, CDO was invented to be the bridge from these languages to MAPI. CDO exposes COM objects, but these COM objects are of the right nature to be accessible through both Visual Basic and scripting languages. CDO is built on top of MAPI, as illustrated in Figure 7-1, which means that when you instantiate a CDO object, that CDO object is accessing a MAPI object behind the scenes to do the real work.

Figure 7-1. CDO's place in MAPI architecture

 

This can occasionally result in surprising behavior. For example, an application may wish to determine whether two object references--say, CdoMessage1 and CdoMessage2--refer to the same message. The following naïve implementation of such a check won't work reliably:

' This won't work.
If CdoMessage1 Is CdoMessage2 Then
   ' Do something.
End If

The reason is that the two object references (CdoMessage1 and CdoMessage2) may refer to different CDO Message objects, yet those CDO Message objects may both wrap the same MAPI message object. In this case, the If statement shown will fail (because the two references do indeed point to different objects), but we would prefer for the statement to succeed (because the two objects wrap the same MAPI object). To handle this situation, most CDO objects implement the IsSameAs method. The syntax of the IsSameAs method is:

bResult = CdoObject1.IsSameAs(CdoObject2)

The IsSameAs method returns True if CdoObject1 and CdoObject2 refer to the same CDO object or if they refer to different CDO objects that in turn wrap the same MAPI object. Here is an example of using the IsSameAs method of the CDO Message object:

If CdoMessage1.IsSameAs(CdoMessage2) Then
   Debug.Print "The variables reference the same MAPI message."
Else
   Debug.Print "The variables reference different MAPI messages."
End If

The CDO objects that implement the IsSameAs method are AddressEntry, AddressEntryFilter, AddressList, AppointmentItem, Attachment, Folder, InfoStore, MeetingItem, Message, MessageFilter, and Recipient.

Another aspect to this business of CDO objects wrapping MAPI objects is that CDO objects are ephemeral--they are created only when needed to give access to underlying MAPI objects, and they are destroyed immediately after their use. Although the syntax of the CDO hierarchy implies that there is a tree filled with bunches of CDO object instances that exist regardless of whether we traverse them, this is not the case. Consider the meaning of this Visual Basic statement:

Debug.Print CdoSession.Inbox.Messages.Item(1).Subject

This line appears to do the following:

  1. Use the CDO Session object to find the CDO Inbox Folder object.
  2. Use the CDO Inbox Folder object to find the CDO Messages collection within that object.
  3. Use the CDO Messages collection to find the first CDO Message object in the collection.
  4. Use the CDO Message object to read the subject of the message.

However, what the line really does is this:

  1. Use the CDO Session object to instantiate a new CDO Folder object that wraps the MAPI Inbox folder.
  2. Use the CDO Folder object to instantiate a new CDO Messages collection object that represents the set of messages stored in the Inbox.
  3. Use the CDO Messages collection to instantiate a new CDO Message object that wraps the first message in the Inbox folder.
  4. Use the CDO Message object to read the subject of the message.
  5. Destroy the CDO objects created by this statement.

Given this information, code such as the following is clearly very inefficient because it performs all of the above instantiating and destroying for each statement:

' Wrong
Debug.Print CdoSession.Inbox.Messages.Item(1).Subject
Debug.Print CdoSession.Inbox.Messages.Item(1).TimeReceived
Debug.Print CdoSession.Inbox.Messages.Item(1).Text

The corrected code performs each object instantiation only once:

With CdoSession.Inbox.Messages.Item(1)
   Debug.Print .Subject
   Debug.Print .TimeReceived
   Debug.Print .Text
End With

Note further that not only are you in danger of writing inefficient code, but also your code may actually not work. For example, this code does not achieve the desired result:

' Wrong
CdoSession.Inbox.Messages.Item(1).Subject = "My New Subject"
CdoSession.Inbox.Messages.Item(1).Update ' Save it! (Not)

Because each line instantiates a new (i.e., different) Message object, the call to Update is made on a Message object other than the one on which the Subject property was changed. In fact, the Message object on which the Subject property was changed is destroyed before the Update method is even called. The change, therefore, is lost. The correct way to code this is as follows:

With CdoSession.Inbox.Messages.Item(1)
   .Subject = "My New Subject"
   .Update ' Save it!
End With

One final note on this subject concerns both efficiency and ease of coding. You may have noticed that CDO object properties that hold references to other CDO objects are declared in the CDO type library as Variant. (The CDO type library and how to view it were explained in Chapter 5, Collaboration Data Objects.) This has two unfortunate side effects. First, it forces so-called late binding when properties and methods are accessed through these Variant members. Binding and its ramifications are discussed at length in the Visual Basic online help and other sources. Briefly, late binding means that the Visual Basic compiler doesn't know at compile time what an object's type is. The compiled code therefore must jump through a lot of hoops at runtime to access properties and methods of the object. The second side effect of the Variant members is that the Visual Basic IntelliSense feature doesn't work for these objects. Again, this is because the properties are declared as Variant, so Visual Basic doesn't know what type they're really supposed to be.

You can code around both of these problems by assigning the value of a property to an appropriately declared object variable before referencing any properties or methods of the object. For example, instead of this one-liner:

Debug.Print CdoSession.Inbox.Messages.Item(1).Subject

Code this longer but more robust alternative:

Dim CdoFolder As MAPI.Folder
Dim CdoMessages As MAPI.Messages
Dim CdoMessage As MAPI.Message
 
Set CdoFolder = CdoSession.Inbox
Set CdoMessages = CdoFolder.Messages
Set CdoMessage = CdoMessages.Item(1)
Debug.Print CdoMessage.Subject

Because each object in this code fragment is explicitly typed, the compiler catches any typographical errors.

Handling Message Items

The Message object provides methods for copying, moving, and deleting messages. An automated email client may need to move incoming helpdesk inquiries, for example, to an appropriate folder for later retrieval by the next available technician.

To move an email message from one folder to another, use the Message object's MoveTo method. Its syntax is:

Set CdoMessage2 = CdoMessage.MoveTo(FolderID[, StoreID])

The parameters are:

FolderID
A string that identifies the folder to which the message is to be moved. A folder ID string is obtained from the ID property of a CDO Folder object. Obtaining CDO Folder objects representing specific folders is the subject of the next section.

StoreID
This optional parameter is a string that identifies the message store containing the target folder. If omitted, CDO assumes that the target folder resides in the same message store as the folder from which the message is being moved. A message store ID is obtained from the ID property of a CDO InfoStore object.

The CDO Message object returned by the MoveTo method (shown as CdoMessage2 in the syntax definition) references the message in its new location. The original CDO Message object (shown as CdoMessage) no longer references any message. Attempting to access its properties results in an error.

Here's an example of using the MoveTo method:

' CdoMessage and CdoFolder previously Dim'ed and Set.
Set CdoMessage = CdoMessage.MoveTo(CdoFolder.ID, CdoFolder.StoreID)

Note the following in this example:

A neat use of the MoveTo method is to provide an "undoable" delete, such as Microsoft Outlook does. When the user requests to delete a message item, the software could actually just move the item to another folder, usually the Deleted Items folder in the InfoStore object's RootFolder. (Note that the Delete method, discussed next, provides an easier way to move a message directly to the Deleted Items folder, but this technique of moving folders lets you pick any folder, not just the Deleted Items folder.) How to get a reference to a specific folder of interest is discussed later, in the section "Working with Folders."

To truly delete a message from the message store, use the Message object's Delete method. The syntax of the Delete method is:

CdoMessage.Delete([DeletedItems])

The method's single parameter, DeletedItems, is optional. It is a Boolean that, if True, indicates that the message should be moved to the user's Deleted Items folder. If False, this parameter indicates that the message should be permanently deleted. The default is False.

For example, this code permanently deletes a message:

' CdoMessage previously Dim'ed and Set.
CdoMessage.Delete

This is an irreversible operation, so be sure to ask the user whether the deletion is truly desired. To move the message to the user's "Deleted Items" folder, supply the DeletedItems parameter to the Delete method call:

' CdoMessage and CdoFolder previously Dim'ed and Set.
CdoMessage.Delete DeletedItems:=True

Believe it or not, a few different versions of CDO 1.21 are running around. At least one older version of this component doesn't accept the DeletedItems parameter of the Message object's Delete method (or at least its type library claims that it doesn't). To ensure that you're working with the latest release of CDO 1.21, download CDO directly from Microsoft, as described in Chapter 5.

To copy a message, use the Message object's CopyTo method. The syntax of the CopyTo method is:

Set CdoMessageCopy = CdoMessage.CopyTo(FolderID[, StoreID])

Its parameters are:

FolderID
A string that identifies the folder to which the message is to be copied. A folder ID string is obtained from the ID property of a CDO Folder object. Obtaining CDO Folder objects representing specific folders is the subject of the next section.

StoreID
This optional parameter is a string that identifies the message store containing the target folder. If omitted, CDO assumes that the target folder resides in the same message store as the folder from which the message is being copied. A message store ID is obtained from the ID property of a CDO InfoStore object.

The CDO Message object returned by the CopyTo method (shown as CdoMessageCopy in the syntax definition) references the new message. The original CDO Message object (shown as CdoMessage ) continues to reference the original message.

In order for the new message to appear in the target folder, the Update method must be called on the CDO Message object returned by the CopyTo method. Here's an example:

' CdoMessage and CdoFolder previously Dim'ed and Set.
' CdoMessageCopy previously Dim'ed.
Set CdoMessageCopy = CdoMessage.CopyTo(CdoFolder.ID, CdoFolder.StoreID)
CdoMessageCopy.Update

Changes made to a CDO Message object's properties aren't written to the underlying message store until either the Send or the Update method is called. The Send method was described in the previous chapter, so I'll focus on the Update method here. The syntax of the Update method is:

CdoMessage.Update([MakePermanent][, RefreshObject])

Its parameters are:

MakePermanent
This optional Boolean parameter controls whether changes are saved to the message store. Setting this parameter to True, the default, indicates that changes should be saved. Setting it to False indicates that changes should not be saved.

RefreshObject
This optional Boolean parameter controls whether the Message object's property cache is reloaded from the message store. The default is False.

The RefreshObject parameter doesn't behave as one would expect. Its name and documentation imply that passing RefreshObject as True would cause the Message object's properties to be reset to the values contained in the underlying message store. This isn't the case. It causes the Message object's property cache to be reloaded, which isn't the same thing. Unfortunately, Microsoft doesn't precisely document the behavior of the property cache, so this parameter isn't as useful as it could have been. Instead, to refresh a Message object's properties from the message store, discarding the object's current properties, re-instantiate it from the message store. Here is a generic subroutine to refresh a Message object:

Sub RefreshMessageObject(ByRef CdoMessage As MAPI.Message)
   Dim CdoSession As MAPI.Session
 
   Set CdoSession = CdoMessage.Session
   Set CdoMessage = CdoSession.GetMessage(CdoMessage.ID, _
      CdoMessage.StoreID)
End Sub

Working with Folders

An email client application can perform a number of tasks with Folder objects. An obvious one is to locate a folder of interest within the folder hierarchy. The Session object maintains references to the Inbox and Outbox folders of the default InfoStore, making it easy to obtain either. However, the Session object doesn't maintain references to other common folders, so it's useful to implement a generic function for finding a folder by name. Example 7-1 shows such a function.

Example 7-1: Finding a Folder Object by Name

Public Function GetFolderByName( _
   ByVal CdoSession As MAPI.Session, _
   ByVal strFolderName As String, _
   Optional ByVal CdoFolderParent As MAPI.Folder = Nothing, _
   Optional ByVal bCreate As Boolean = True _
   ) As MAPI.Folder
   
   Dim CdoInfoStore As MAPI.InfoStore
   Dim CdoFolderRoot As MAPI.Folder
   Dim CdoFolders As MAPI.Folders
   Dim CdoFolder As MAPI.Folder
   Dim bFound As Boolean
   
   ' If the parent folder wasn't passed in, then use the root
   ' folder of the default InfoStore.
   
   If CdoFolderParent Is Nothing Then
      ' Get the Folders collection from the default InfoStore.
      Set CdoInfoStore = CdoSession.GetInfoStore
      Set CdoFolderRoot = CdoInfoStore.RootFolder
      Set CdoFolders = CdoFolderRoot.Folders
   Else
      ' Get the Folders collection from the parent folder.
      Set CdoFolders = CdoFolderParent.Folders
   End If
   
   ' Loop through the folders in the collection until the
   ' desired folder is found.
   bFound = False
   Set CdoFolder = CdoFolders.GetFirst
   Do While (Not bFound) And Not (CdoFolder Is Nothing)
      If CdoFolder.Name = strFolderName Then
         bFound = True
      Else
         Set CdoFolder = CdoFolders.GetNext
      End If
   Loop
   
   ' If not found, then create it (if caller said to).
   If (CdoFolder Is Nothing) And bCreate Then
      Set CdoFolder = CdoFolders.Add(strFolderName)
   End If
   
   Set GetFolderByName = CdoFolder
   
   ' Release our local objects.
   Set CdoFolder = Nothing
   Set CdoFolders = Nothing
   Set CdoFolderRoot = Nothing
   Set CdoInfoStore = Nothing
 
End Function ' GetFolderByName

The GetFolderByName function in Example 7-1 takes at a minimum a Session object and the name of a folder to find as parameters. If no other parameters are supplied, GetFolderByName searches the Folders collection in the root folder of the user's default InfoStore for a Folder object possessing the given name. If found, a reference to the Folder is returned to the caller. If not found, GetFolderByName creates a new folder with the given name and returns a reference to the newly-created Folder object to the caller.

The GetFolderByName function provides a couple of optional parameters that modify its behavior. The CdoFolderParent parameter, if supplied, instructs the function to search the Folders collection of the given parent folder, rather than the root folder of the default InfoStore. The bCreate parameter, if passed as False, instructs GetFolderByName not to create a folder if one isn't found. In that case, the GetFolderByName method returns a value of Nothing.

Example 7-1 also demonstrates how easy it is to create a new folder: merely call the Folders collection's Add method. The syntax of the Add method is:

Set CdoFolder = CdoFolders.Add(Name)

The Add method's single parameter is the name of the new folder. The method returns a reference to the newly created Folder object. The addition is reflected in the message store immediately, meaning that it is not necessary to call the Folder object's Update method to reflect the change.

Deleting a folder is just as easy, requiring only a call to the Folder object's Delete method. The syntax of the Delete method is:

CdoFolders.Delete(  )

Drawing from Example 7-1, the Drafts folder (and everything in it) can be deleted with this code:

' CdoSession previously Dim'ed and Set.
Dim CdoFolder As MAPI.Folder
Set CdoFolder = GetFolderByName(CdoSession, "Drafts")
CdoFolder.Delete

Let me repeat that this also deletes everything in the folder, including nested folders and all messages, so be careful. Give the user a chance to back out if your application is interactive.

Folders can be moved and copied to other folders, just as messages can. As with Message objects, Folder objects have MoveTo and CopyTo methods.

The syntax of the Folder object's MoveTo method is:

Set CdoFolder2 = CdoFolder.MoveTo(FolderID[, StoreID])

The parameters are:

FolderID
A string that identifies the folder to which the folder is to be moved. A folder ID string is obtained from the ID property of a CDO Folder object.

StoreID
This optional parameter is a string that identifies the message store containing the target folder. If omitted, CDO assumes that the target folder resides in the same message store as the folder from which the folder is being moved. A message store ID is obtained from the ID property of a CDO InfoStore object.

The CDO Folder object returned by the MoveTo method (shown as CdoFolder2 in the syntax definition) references the message in its new location. The original CDO Folder object (shown as CdoFolder) no longer references any folder. Attempting to access its properties results in an error.

The syntax of the Folder object's CopyTo method is:

Set CdoFolderCopy = CdoFolder.CopyTo(FolderID[, StoreID][, Name]
[, CopySubfolders])

The parameters are:

FolderID
A string that identifies the folder to which the folder is to be copied. A folder ID string is obtained from the ID property of a CDO Folder object.

StoreID
This optional parameter is a string that identifies the message store containing the target folder. If omitted, CDO assumes that the target folder resides in the same message store as the folder from which the folder is being moved. A message store ID is obtained from the ID property of a CDO InfoStore object.

Name
This optional parameter is a string that specifies the name of the new folder. If omitted, the new folder has the same name as the original folder.

CopySubfolders
This optional parameter specifies whether to copy any subfolders contained in the original folder. The default is True.

To rename a folder, simply assign a new value to the Folder object's Name property. The change occurs immediately, not requiring a call to the Folder object's Update method.

Folders with Special Status

Clearly, some folders in the MAPI message store hierarchy have unique status. The Inbox and Outbox folders are obvious examples. Indeed, these two folders are so important that the Session object maintains explicit references to them for easy access. Furthermore, if their names are changed, they nevertheless retain their unique functionality. I changed the name of my Inbox folder to "foo." When new messages come in, they dutifully appear in the foo folder. Why?

After a message store provider is added to a profile as the profile's default message store, MAPI does some additional magic on our behalf. The first time a process logs on to MAPI using that profile, MAPI creates several special folders that clients are guaranteed will always exist. They are:

Deleted Items
The folder where deleted items are stored.

Inbox
The folder where new messages are stored after having been received by a transport provider.

Outbox
The folder where outgoing messages are stored while waiting to be serviced by a transport provider.

Sent Items
The folder where outgoing messages are stored after they've been transmitted by a transport provider.

Once MAPI creates these folders, they retain special significance regardless of how their names are changed. There is no facility for transferring the special status of one of these folders to a different folder. Deleting any of these folders programmatically is likely to make your message store provider unusable.

In addition to the special folders created by MAPI, CDO recognizes the special folders created by Microsoft Outlook (except for Outlook's Drafts folder). The special folders created by Outlook are:

Calendar
The folder where the user's appointment items are stored. Calendar folders are discussed in detail in Chapter 8, Calendar Folders.

Contacts
The folder where the user's contacts are stored. Contacts folders are discussed in detail in Chapter 10, Contacts Folders.

Drafts
The folder where unfinished emails are stored. CDO doesn't treat this as a special folder. That is, CDO doesn't provide any special functionality for manipulating this folder or its contents, as it does with the other special folders.

Journal
The folder where the user's journal entries are stored.

Notes
The folder where the user's note items are stored.

Tasks
The folder where the user's task items are stored. Task folders are discussed in detail in Chapter 9, Task Folders.

These folders don't have any special significance to MAPI--only to Outlook. Outlook may or may not tolerate renaming or deleting these folders, depending on the folder and on the version of Outlook.

Hidden Messages

MAPI folders are considered to have two areas for storing message objects: the standard part and the associated part. The standard part includes messages and folders that are manipulated by the average user. These are the messages and folders that we have been working with so far in this book. The associated part is for storing additional information that is not directly manipulated by the user, such as form definitions, views, rules, reply templates, and more. It is up to the individual messaging client to decide what it will store in hidden messages, and how it will be formatted.

In CDO, associated messages are accessed through a Folder object's HiddenMessages property. The HiddenMessages property returns a Messages collection object that in turn contains Message objects representing associated messages. Example 7-2 shows two utility subroutines that together show any associated messages currently in the user's default message store. The code writes its output to a text file, and then launches the Notepad program to display the output. A sample of the output is shown in Figure 7-2. The figure shows that in this user's Calendar folder (which was created by Microsoft Outlook) there are two hidden messages. The first hidden message has a subject of "LocalFreebusy". Further, this message has a message class of "IPM.Microsoft.ScheduleData.FreeBusy". This is intriguing, isn't it? It's tempting to reverse engineer this further in an attempt to write code that modifies the behavior of Outlook. However, because this information is undocumented, it is subject to change without notice, thereby threatening that your application won't work with future versions of Outlook. It's better to use this information to expand your understanding of messaging, without relying on it remaining unchanged for the correct functioning of your applications.

TIP:  

Example 7-2 uses a function called CdoPrTextFromCode, which returns the name of a MAPI property given its property tag value. CdoPrTextFromCode is Visual Basic code that I've provided in the book's sample code. You can find it in \VBMSG\Samples\Chapter 7\, in the VB source code module basCdoPrTextFromCode.bas. It is quite large but not complicated--it's just one big Select...Case statement. A condensed version is shown in Example 7-3.

Example 7-2: Finding the Hidden Messages in the Default Message Store

Private Sub ShowHidden(  )
 
   ' Traverse the folders in the user's default message store, reporting on
   ' any hidden messages found.
 
   Dim CdoSession As MAPI.Session
   Dim CdoInfoStore As MAPI.InfoStore
   Dim CdoRootFolder As MAPI.Folder
   
   ' Log on to MAPI.
   Set CdoSession = New MAPI.Session
   CdoSession.Logon
   
   ' Get the root folder of the user's default message store.
   Set CdoInfoStore = CdoSession.GetInfoStore
   Set CdoRootFolder = CdoInfoStore.RootFolder
   
   ' Here's where we're going to print our output.
   Open "C:\Hidden.txt" For Output As #1
   
   ' Start the search, beginning with the root folder.
   RecurseHidden CdoRootFolder, ""
   
   ' All done.
   Close #1
   
   ' Use Notepad to view the results.
   Shell "notepad.exe ""C:\Hidden.txt""", vbNormalFocus
   
   ' Cleanup, cleanup, everybody do your share!
   Set CdoRootFolder = Nothing
   Set CdoInfoStore = Nothing
   
   CdoSession.Logoff
   Set CdoSession = Nothing
 
End Sub ' ShowHidden
 
Private Sub RecurseHidden(ByVal CdoFolder As MAPI.Folder, _
   ByVal strIndent As String)
   
   ' Recursive helper subroutine used by ShowHidden. Look for hidden
   ' messages in the given folder, then do the same for subfolders.
   ' Assumes that file #1 has been opened for writing.
   
   Dim CdoFolders As MAPI.Folders
   Dim CdoSubFolder As MAPI.Folder
   Dim CdoMessages As MAPI.Messages
   Dim CdoMessage As MAPI.Message
   Dim CdoFields As MAPI.Fields
   Dim CdoField As MAPI.Field
   Dim CdoAttachments As MAPI.Attachments
   
   ' Start by printing the folder name.
   Print #1, strIndent; "----------------------------------------"
   Print #1, strIndent; "Folder Name: """; CdoFolder.Name; """"
   
   ' Get the hidden messages and get the subfolders.
   Set CdoMessages = CdoFolder.HiddenMessages
   Set CdoFolders = CdoFolder.Folders
   
   Print #1, strIndent; "Number of Hidden Messages: "; CdoMessages.Count
   Print #1, strIndent; "Number of Subfolders: "; CdoFolders.Count
   
   ' Loop through all of the hidden messages.
   For Each CdoMessage In CdoMessages
      Set CdoFields = CdoMessage.Fields
      Set CdoAttachments = CdoMessage.Attachments
      
      ' Print information about the message object.
      Print #1, strIndent; "----------------------------------------"
      Print #1, strIndent; "Message Subject: """; CdoMessage.Subject; """"
      Print #1, strIndent; "Text: """; CdoMessage.Text; """"
      Print #1, strIndent; "Number of attachments: "; CdoAttachments.Count
      Print #1, strIndent; "Fields Collection: ";
      If CdoFields.Count > 0 Then
         Print #1,
      Else
         Print #1, "(empty)"
      End If
      
      ' Now print information about all of the fields (properties) of the
      ' message.
      For Each CdoField In CdoFields
      
         Print #1, strIndent; "    ----------"
         Print #1, strIndent; "    Index: "; CdoField.Index
         Print #1, strIndent; "    ID:"; CdoField.ID; " ("; _
            CdoPrTextFromCode(CdoField.ID); ")"
         Print #1, strIndent; "    Name: """; CdoField.Name; """"
         Print #1, strIndent; "    Class: """; CdoField.Class; """"
         Print #1, strIndent; "    Type: ";
         On Error Resume Next
         Print #1, CdoField.Type
         If Err.Number <> 0 Then
            Print #1, "(can't print: error "; Err.Number; ", "; _
               Err.Description; ")"
         End If
         On Error GoTo 0
         Print #1, strIndent; "    Value: """; CdoField.Value; """"
         
      Next CdoField
      
   Next CdoMessage
   
   ' If we have subfolders, then print a header for those.
   If CdoFolders.Count > 0 Then
      Print #1, ""
      Print #1, strIndent; "Folders Collection:"
   End If
   
   ' Now start over for each subfolder.
   For Each CdoSubFolder In CdoFolders
      RecurseHidden CdoSubFolder, strIndent & "    "
   Next CdoSubFolder
   
   Set CdoAttachments = Nothing
   Set CdoField = Nothing
   Set CdoFields = Nothing
   Set CdoMessage = Nothing
   Set CdoMessages = Nothing
   Set CdoSubFolder = Nothing
   Set CdoFolders = Nothing
 
End Sub ' RecurseHidden

Figure 7-2. Sample output from the code in Example 7-2

 

Example 7-3: A Portion of CdoPrTextFromCode, a Function to Convert CDO Constants to Descriptive Text

Public Function CdoPrTextFromCode(ByVal CdoPropTag As MAPI.CdoPropTags) _
   As String
   
   ' This function returns the name of a property tag, given its numeric ID.
   ' Property tags are read from the ID property of Field objects.
    
   Select Case CdoPropTag
   Case CdoPR_ACKNOWLEDGEMENT_MODE:
      CdoPrTextFromCode = "CdoPR_ACKNOWLEDGEMENT_MODE"
   Case CdoPR_ALTERNATE_RECIPIENT_ALLOWED:
      CdoPrTextFromCode = "CdoPR_ALTERNATE_RECIPIENT_ALLOWED"
   Case CdoPR_AUTHORIZING_USERS:
      CdoPrTextFromCode = "CdoPR_AUTHORIZING_USERS"
   Case CdoPR_AUTO_FORWARD_COMMENT:
      CdoPrTextFromCode = "CdoPR_AUTO_FORWARD_COMMENT"
   Case CdoPR_AUTO_FORWARD_COMMENT_W:
      CdoPrTextFromCode = "CdoPR_AUTO_FORWARD_COMMENT_W"
   Case CdoPR_AUTO_FORWARD_COMMENT_A:
      CdoPrTextFromCode = "CdoPR_AUTO_FORWARD_COMMENT_A"
   Case CdoPR_AUTO_FORWARDED:
      CdoPrTextFromCode = "CdoPR_AUTO_FORWARDED"
   '
   ' Many, many more cases snipped for brevity in the printed listing.
   '
   Case CdoPR_INTERNET_PRECEDENCE_W:
      CdoPrTextFromCode = "CdoPR_INTERNET_PRECEDENCE_W"
   Case CdoPR_INTERNET_PRECEDENCE_A:
      CdoPrTextFromCode = "CdoPR_INTERNET_PRECEDENCE_A"
   Case CdoPR_REFERRED_BY_NAME:
      CdoPrTextFromCode = "CdoPR_REFERRED_BY_NAME"
   Case CdoPR_REFERRED_BY_NAME_W:
      CdoPrTextFromCode = "CdoPR_REFERRED_BY_NAME_W"
   Case CdoPR_REFERRED_BY_NAME_A:
      CdoPrTextFromCode = "CdoPR_REFERRED_BY_NAME_A"
   Case CdoPR_SEND_INTERNET_ENCODING:
      CdoPrTextFromCode = "CdoPR_SEND_INTERNET_ENCODING"
   Case Else
      CdoPrTextFromCode = ""
   End Select
   
End Function ' CdoPrTextFromCode

Working with Address Books

In MAPI, recipient information is stored in address books. A MAPI address book, much like a physical address book in which you keep your friends' names and addresses, is a place to save information about potential message recipients. Address books are implemented by address book providers, which are software components that plug into MAPI in order to provide this feature. MAPI-compliant email systems typically come with one or more address book providers. To see the list of address book providers on your system, run the Mail applet from the Windows Control Panel, then click the Add button on the dialog box that appears. This brings up the "Add Service to Profile" dialog box, as shown in Figure 7-3. (If you don't see this dialog box on your computer, your email system is not using MAPI. See "Obtaining MAPI" in Chapter 2, MAPI, for more information.) The "Add Service to Profile" dialog box shows the information services that can be added to a profile. An information service is an address book provider, a message store provider, a transport provider, or some combination of all three. Figure 7-3 shows two address book providers: the Outlook Address Book, and the Personal Address Book. The Outlook Address Book provider is on this list because I happen to have Microsoft Outlook installed on my system. The Personal Address Book provider is supplied by MAPI itself. Although these appear to be the only address book providers available on this system, there are actually others. In Figure 7-3, note the "Microsoft Exchange Server" information service. This information service is actually a combination of message store, transport, and address book providers that know how to communicate with Microsoft Exchange Server. Choosing this information service gives me access to the global address book that has been set up by my company's network administrator on our Exchange server.

Figure 7-3. The "Add Service to Profile" dialog box

 

Iterating Through the Address Books

You can see from the preceding discussion that several different address books can be set up in a single profile. MAPI arranges these address books in a tree structure, with the address books all being children of a single root object. In CDO, this root object is accessed through the Session object's AddressLists collection. Figure 7-4 shows this relationship.

Figure 7-4. The portion of the CDO object model pertaining to the objects discussed in this section

 

Each AddressList object in the AddressLists collection represents a single address book. The AddressLists collection can be iterated to examine all of the user's address books, or a particular address book can be retrieved by name, as shown here:

' Get the AddressList object representing the user's personal address
' book (PAB).
 
' CdoSession previously Dim'ed and Set.
' CdoAddressLists and CdoAddressList previously Dim'ed.
 
Set CdoAddressLists = CdoSession.AddressLists
Set CdoAddressList = CdoAddressLists.Item("Personal Address Book")

If an invalid name is specified when attempting to retrieve an AddressList object, a CdoE_NOT_FOUND error is raised.

Individual email addresses are accessed through an AddressList object's AddressEntries collection. Each AddressEntry object in the collection represents one entry in the address book.

The properties of the AddressEntry object are:

Address
A string that specifies the address of a recipient, in whatever format is appropriate for the particular messaging system specified by the AddressEntry object's Type property. The Type property and the Address property together give the full address of the recipient, which also can be found in the Recipient object's Address property. The format of the full address is "address type:address".

DisplayType
This is a useful property that helps us deal with the fact that an AddressEntry object may actually represent something other than a single recipient's address. For example, an AddressEntry object may represent an entire distribution list. The datatype is Long. The values and their meanings are:

CdoAgent
The AddressEntry represents an automated agent. In this context, an agent is either client or server software that handles mail delivery tasks.

CdoDistList
The AddressEntry represents a public distribution list--one that has been set up for use by multiple users.

CdoForum
The AddressEntry represents a bulletin board or public folder.

CdoOrganization
The AddressEntry represents a special alias defined for a large group or organization.

CdoPrivateDistList
The AddressEntry represents a private distribution list--one that has been set up by the user for his or her own private use. AddressEntry objects that represent private distribution lists have neither an Address property nor a Type property. Attempting to access either property when the DisplayType property is CdoPrivateDistList results in a CdoE_NOT_FOUND error being raised.

CdoRemoteUser
The AddressEntry object represents a messaging user who is known to be serviced by an email server different from that servicing the logged-in user.

CdoUser
The AddressEntry object represents a messaging user who is serviced by the same email server as is the logged-in user.

Fields
The Fields property holds a Fields collection, which gives access to the underlying MAPI properties applicable to the address entry.

ID
The ID property is a string that MAPI assigns to identify an object uniquely. This ID can be passed to the Session object's GetAddressEntry method to retrieve an AddressEntry object. Note that CDO does not guarantee that this ID is meaningful across MAPI sessions.

Manager
The Manager property holds a reference to an AddressEntry object that represents the user's manager in the organization. If the organization doesn't keep track of this information in the mail system, the Manager property is set to Nothing.

Members
If the AddressEntry object is a distribution list (that is, the value stored in the DisplayType property is CdoDistList or CdoPrivateDistList), the Members property holds an AddressEntries collection object, which represents the members of the distribution list. If the AddressEntry object is not a distribution list, the Member property is set to Nothing.

Name
The Name property is a string that holds the display name of the AddressEntry object.

Type
The Type property is a string (always uppercase) that specifies the type of messaging system in which the address resides. The value in this property allows MAPI to select an appropriate transport provider for message delivery. For example, a value of "SMTP" in this field indicates that the AddressEntry object refers to an Internet mail address. Table 7-1 shows the documented values that this property may take.

Table 7-1: Address Types

Address Type

Messaging System

3COM

3Com 3+Mail

ATT

AT&T Easylink Services

CCMAIL

Lotus cc:Mail (proposed)

COMPUSERVE

CompuServe (proposed)

EX

Microsoft Exchange Server

FAX

Fax

MAPIPDL

Personal Distribution List

MCI

MCI Mail

MHS

Novell Message Handling System

MS

Microsoft Mail Server for PC Networks

MSA

Microsoft Mail Server for AppleTalk Networks

MSFAX

Fax

MSN

The Microsoft Network

PROFS

Professional Office System

SMTP

Internet

SNADS

SNA Distributed System

TELEX

Telex (proposed)

X400

X.400 Message Handling System

X500

X.500 Directory Services

The methods of the AddressEntry object are:

Delete
Permanently removes the messaging user from the address book. If you only want to delete the recipient from a recipient list, use the Recipient object's Delete method instead.

Details
The Details method displays a dialog box with detailed information about the user represented by the AddressEntry object. Optionally, it can take as a parameter the window handle of the window that is to be the parent of the dialog box. If this parameter is not supplied, the dialog box is application-modal. Example 7-4 is a subroutine for showing the details of the sender of a given message. The resulting display is shown in Figure 7-5.

Example 7-4: Showing AddressEntry Details

Private Function ShowSenderDetails(ByVal CdoMessage As MAPI.Message)
 
   Dim CdoAddressEntry As MAPI.AddressEntry
   
   On Error GoTo ErrorHandler
   
   Set CdoAddressEntry = CdoMessage.Sender
   CdoAddressEntry.Details
   Set CdoAddressEntry = Nothing
   
   Exit Function
   
ErrorHandler:
 
   ' If user pressed cancel, don't treat that as an error.
   If Err.Number <> CdoE_USER_CANCEL Then
      Err.Raise Err.Number
   End If
 
End Function ' ShowSenderDetails

Figure 7-5. The AddressEntry details dialog box

 

GetFreeBusy
The GetFreeBusy method returns a string that represents the user's schedule availability for a given period of time. This method will be examined in Chapter 8, Calendar Folders.

IsSameAs
The IsSameAs method compares the AddressEntry object to a second AddressEntry object for equality. It's used like this:

' CdoAddressEntry1 and CdoAddressEntry2 previously Dim'ed and Set.   If CdoAddressEntry1.IsSameAs(CdoAddressEntry2) Then MsgBox "The AddressEntry objects are the same." Else MsgBox "The AddressEntry objects are not the same." End If

AddressEntry objects are considered to be the same if they represent the same underlying MAPI object.

Update
The Update method must be called to commit any changes made to the AddressEntry object. For example, this code fragment changes the display name of an AddressEntry object, then commits the change to permanent storage:

' CdoAddressEntry previously Dim'ed and Set.   CdoAddressEntry.Name = "Jane Newname" CdoAddressEntry.Update

Note that such changes are relevant only if the MAPI object behind the AddressEntry object is actually an entry in an address book. For example, the following code has no effect; the Sender property of the message is unchanged.

' *** This code doesn't do anything. ***   ' CdoMessage previously Dim'ed and Set.   Dim CdoAddressEntry As MAPI.AddressEntry   Set CdoAddressEntry = CdoMessage.Sender CdoAddressEntry.Name = "Jane Newname" CdoAddressEntry.Update

To help you visualize the address list hierarchy, Example 7-5 shows two subroutines that together print information on all users in all address books defined in a particular profile. A single call to the first subroutine, IterateAddressLists, accomplishes the task. The IterateAddressLists subroutine performs the following steps:

  1. Establishes a MAPI session.
  2. Obtains the top-level AddressLists collection from the Session object.
  3. Opens a text file to hold the text that will be printed.
  4. Iterates through all of the objects in the AddressLists collection, obtaining the AddressEntries collection from each object and passing that collection to the RecurseAddressEntries subroutine.

The RecurseAddressEntries subroutine iterates through the collection of AddressEntry objects, printing information about each object and examining it to see if it has any nested AddressEntries collections (which it would if it represented a distribution list). If an AddressEntry object has a nested AddressEntries collection, the RecurseAddressEntries subroutine calls itself recursively to print the nested collection's information. Sample output from this code is shown in Figure 7-6.

Example 7-5: Traversing the Address List Hierarchy

Public Sub IterateAddressLists(  )
 
   ' Log into MAPI and traverse the address book structure for the profile
   ' selected by the user.
   
   Dim CdoSession As MAPI.Session
   Dim CdoAddressLists As MAPI.AddressLists
   Dim CdoAddressList As MAPI.AddressList
   Dim CdoAddressEntries As MAPI.AddressEntries
   Dim nFileNum As Long
   
   ' Create a Session object and log on to MAPI.
   Set CdoSession = New MAPI.Session
   CdoSession.Logon
   
   ' Get the top list of address books.
   Set CdoAddressLists = CdoSession.AddressLists
   
   ' Here's where we're going to print our output.
   nFileNum = FreeFile
   Open "C:\AddressLists.txt" For Output As #nFileNum
   
   ' Each CdoAddressList represents an address book.
   For Each CdoAddressList In CdoAddressLists
   
      ' Print some useful information about the address book.
      Print #nFileNum, "----------------------------------------"
      Print #nFileNum, "Name: "; CdoAddressList.Name
      Print #nFileNum, "AddressEntries collection: ";
      
      ' Print the objects in the AddressEntries collection.
      Set CdoAddressEntries = CdoAddressList.AddressEntries
      If CdoAddressEntries.Count = 0 Then
         Print #nFileNum, "(empty)"
      Else
         Print #nFileNum, ""
         RecurseAddressEntries nFileNum, CdoAddressEntries, "   "
      End If
      
   Next CdoAddressList
   
   ' All done.
   Close #nFileNum
   
   ' Use Notepad to view the results.
   Shell "notepad.exe ""C:\AddressLists.txt""", vbNormalFocus
   
   Set CdoAddressEntries = Nothing
   Set CdoAddressList = Nothing
   Set CdoAddressLists = Nothing
   
   CdoSession.Logoff
   Set CdoSession = Nothing
 
End Sub ' IterateAddressLists
 
Private Sub RecurseAddressEntries( _
   ByVal nFileNum As Long, _
   ByVal CdoAddressEntries As MAPI.AddressEntries, _
   ByVal strIndent As String)
   
   ' Recursive helper subroutine used by IterateAddressLists. Output
   ' descriptive information for all of the AddressEntry objects in
   ' the given AddressEntries collection, then do the same for any
   ' nested AddressEntries collections referred to by the individual
   ' AddressEntry objects.
   
   ' Assumes that file #nFileNum has been opened for writing.
   
   Dim CdoAddressEntry As MAPI.AddressEntry
   Dim CdoAddressEntriesMembers As MAPI.AddressEntries
   
   For Each CdoAddressEntry In CdoAddressEntries
   
      Print #nFileNum, strIndent; "----------"
      Print #nFileNum, strIndent; "Name: "; CdoAddressEntry.Name
      Print #nFileNum, strIndent; "Type: "; CdoAddressEntry.Type
      Print #nFileNum, strIndent; "DisplayType: "; CdoAddressEntry.DisplayType
      Print #nFileNum, strIndent; "Address: ";
      On Error Resume Next
      Print #nFileNum, CdoAddressEntry.Address
      If Err.Number = CdoE_NOT_FOUND Then
         Print #nFileNum, "(no address)"
      End If
      On Error GoTo 0
      
      ' If this AddressEntry object is a distribution list, then the
      ' members of the list will be stored in the Members property.
      ' Output those members here.
      
      Print #nFileNum, strIndent; "Members: ";
      Set CdoAddressEntriesMembers = CdoAddressEntry.Members
      If CdoAddressEntriesMembers Is Nothing Then
         Print #nFileNum, "(none)"
      ElseIf CdoAddressEntriesMembers.Count = 0 Then
         Print #nFileNum, "(none)"
      Else
         Print #nFileNum, ""
         RecurseAddressEntries nFileNum, CdoAddressEntriesMembers, _
            strIndent & "   "
      End If
      
   Next CdoAddressEntry
   
   Set CdoAddressEntriesMembers = Nothing
   Set CdoAddressEntry = Nothing
 
End Sub ' RecurseAddressEntries

Figure 7-6. Sample output from Example 7-5

 

How to Designate an Outlook Contacts Folder as a MAPI Address Book

Microsoft Outlook comes with an address book provider that can make any Outlook contacts folder (i.e., a folder that contains Contact items) appear as an address book. To use this feature, two requirements must be satisfied:

  • The "Outlook Address Book" service provider must be added to the user's profile.
  • The properties of the contacts folder in Outlook must be set to allow the folder to appear as an email address book.

To satisfy the first requirement, run the Mail applet from the Control Panel. This displays the properties dialog box for the user's default profile. Assuming this is the profile that is to be changed, click the Add button. The "Add Service to Profile" dialog box is displayed. Select "Outlook Address Book" and click OK. Click OK again to dismiss the profile properties dialog box. For more detailed information on working with profiles, refer to Chapter 2.

To satisfy the second requirement, in Microsoft Outlook right-click a folder that holds Contact items. This displays a context menu. From the context menu, choose Properties. This displays the Properties dialog box for the selected folder. Click the "Outlook Address Book" tab. This reveals the "Show this folder as an e-mail Address Book" check box. Check this box to cause the folder to appear as an address book. Click OK to accept the change.

Creating and Retrieving Address Book Entries

To add a new entry to an address book:

  1. Get the AddressLists collection from the Session object. Recall that the AddressLists collection represents all address books configured for the currently logged-in profile.
  2. Get a particular AddressList object from the AddressLists collection.
  3. Get the AddressEntries collection from the AddressList object.
  4. Call the Add method of the AddressEntries collection to add a new address. Its syntax is:
  5. Set CdoAddressEntry = CdoAddrEntries.Add(Emailtype[, Name][, Address])

    The parameters are:

    Emailtype
    A Variant/String that specifies the type of messaging system in which the address resides. It corresponds to the Type property of the AddressEntry object, and can take the same values (already shown in Table 7-1).

    Name
    A Variant/String that specifies the display name for the new AddressEntry object. It corresponds to the Name property of the AddressEntry object.

    Address
    A Variant/String that specifies the address of the new AddressEntry object. It corresponds to the Address property of the new AddressEntry object.

  6. Call the Update method of the AddressEntry object returned by the Add method.

Example 7-6 illustrates these steps. This example adds a new user ("Happy Gilmore") to the Personal Address Book.

Example 7-6: Adding a New Address to an Address Book

Dim CdoSession As MAPI.Session
Dim CdoAddressLists As MAPI.AddressLists
Dim CdoAddressList As MAPI.AddressList
Dim CdoAddressEntries As MAPI.AddressEntries
Dim CdoAddressEntry As MAPI.AddressEntry
 
' Create a new session object and logon.
Set CdoSession = New MAPI.Session
CdoSession.Logon
 
' Get the master list of address books for this profile.
Set CdoAddressLists = CdoSession.AddressLists
 
' Get the user's personal address book (PAB).
Set CdoAddressList = CdoAddressLists.Item("Personal Address Book")
 
' Get the members of the PAB.
Set CdoAddressEntries = CdoAddressList.AddressEntries
 
' Add a new member.
Set CdoAddressEntry = CdoAddressEntries.Add(Emailtype:="SMTP", _
   Name:="Happy Gilmore", Address:="hgilmore@company.com")
   
' Commit the change.
CdoAddressEntry.Update
 
' Clean up.
Set CdoAddressEntry = Nothing
Set CdoAddressEntries = Nothing
Set CdoAddressList = Nothing
Set CdoAddressLists = Nothing
 
CdoSession.Logoff
Set CdoSession = Nothing

Note in Example 7-6 that the desired address book (in this case, the "Personal Address Book") is obtained simply by naming it as the index to the AddressLists collection.

Not all address books are modifiable. For example, an organization may have a global address book for use by all users. If an attempt is made to call the Add method on this address book's AddressEntries collection from a process not having sufficient privileges, a CdoE_NO_ACCESS error is raised. Similarly, contacts folders being used as address books can't be updated in this manner. Attempting to do so will also raise a CdoE_NO_ACCESS error. (There are ways to modify items in contacts folders. See Chapter 10, Contacts Folders for details.)

If an entry already exists where the name, address, and address type all equal the values supplied in the Add method, a CdoE_COLLISION error is raised.

Adding a Message Sender to an Address Book

Adding a message sender to an address book is just a special case of adding a new user to an address book. The following fragment shows the relevant code:

' CdoMessage previously Dim'ed and Set.
' CdoAddressEntries previously Dim'ed and Set to the AddressEntries
' collection of the address book to be updated.
 
Dim CdoAddressEntrySender As MAPI.AddressEntry
Dim CdoAddressEntryNew As MAPI.AddressEntry
 
' Get the (temporary) AddressEntry object that represents the message
' sender.
Set CdoAddressEntrySender = CdoMessage.Sender
 
' Add an entry to the address book, based on the sender properties.
Set CdoAddressEntryNew = CdoAddressEntries.Add( _
   Emailtype:=CdoAddressEntrySender.Type, _
   Name:=CdoAddressEntrySender.Name, _
   Address:=CdoAddressEntrySender.Address)
 
' Save the changes.
CdoAddressEntryNew.Update
 
' Clean up.
Set CdoAddressEntryNew = Nothing
Set CdoAddressEntrySender = Nothing

The Message Object's Sender property returns a reference to an AddressEntry object that represents the sender of the message. Although this AddressEntry object can't be added directly to an AddressEntries collection, a new AddressEntry object can be created based on the values in the sender's AddressEntry object.

Creating a Distribution List

Creating a distribution list is just like adding a new user to an address book. After all, users and distribution lists are both represented by AddressEntry objects, the distinction being made by the value of the AddressEntry object's Type property. The code in Example 7-7 logs into MAPI and creates a distribution list called "My Distribution List" in the Personal Address Book.

Example 7-7: Creating a Distribution List

Dim CdoSession As MAPI.Session
Dim CdoAddressLists As MAPI.AddressLists
Dim CdoAddressList As MAPI.AddressList
Dim CdoAddressEntries As MAPI.AddressEntries
Dim CdoAddressEntry As MAPI.AddressEntry
 
' Create a new Session object and log on.
Set CdoSession = New MAPI.Session
CdoSession.Logon
 
' Get the Personal Address Book's AddressEntries collection
Set CdoAddressLists = CdoSession.AddressLists
Set CdoAddressList = CdoAddressLists.Item("Personal Address Book")
Set CdoAddressEntries = CdoAddressList.AddressEntries
 
' Add a new entry for the distribution list.
Set CdoAddressEntry = CdoAddressEntries.Add( _
   Emailtype:="MAPIPDL", Name:="My Distribution List")
   
' Save the changes.
CdoAddressEntry.Update
 
' Clean up.
Set CdoAddressEntry = Nothing
Set CdoAddressEntries = Nothing
Set CdoAddressList = Nothing
Set CdoAddressLists = Nothing
 
CdoSession.Logoff
Set CdoSession = Nothing

Recall that when an AddressEntry object represents a distribution list, its Members property holds an AddressEntries collection which represents the members of the list. To add a new member, call the AddressEntries collection's Add method, as shown in Example 7-8.

Example 7-8: Adding a Member to a Distribution List

Dim CdoSession As MAPI.Session
Dim CdoAddressLists As MAPI.AddressLists
Dim CdoAddressList As MAPI.AddressList
Dim CdoAddressEntries As MAPI.AddressEntries
Dim CdoAddressEntry As MAPI.AddressEntry
Dim CdoAddressEntriesMembers As MAPI.AddressEntries
Dim CdoAddressEntryNewMember As MAPI.AddressEntry
 
' Create a new Session object and log on.
Set CdoSession = New MAPI.Session
CdoSession.Logon
 
' Get the Personal Address Book's AddressEntries collection.
Set CdoAddressLists = CdoSession.AddressLists
Set CdoAddressList = CdoAddressLists.Item("Personal Address Book")
Set CdoAddressEntries = CdoAddressList.AddressEntries
 
' Get the AddressEntry for the desired distribution list.
Set CdoAddressEntry = CdoAddressEntries.Item("My Distribution List")
 
' Get the distribution list's members collection.
Set CdoAddressEntriesMembers = CdoAddressEntry.Members
 
' Add a new member to the list.
Set CdoAddressEntryNewMember = CdoAddressEntriesMembers.Add( _
   Emailtype:="SMTP", Name:="Jeanne d'Arc", Address:="jarc@orleans.fr")
   
' Save the changes.
CdoAddressEntryNewMember.Update
 
' Clean up.
Set CdoAddressEntryNewMember = Nothing
Set CdoAddressEntriesMembers = Nothing
Set CdoAddressEntry = Nothing
Set CdoAddressEntries = Nothing
Set CdoAddressList = Nothing
Set CdoAddressLists = Nothing
 
CdoSession.Logoff
Set CdoSession = Nothing

Unfortunately, CDO doesn't give us a way to associate an existing AddressEntry object with an address list. When the AddressEntries Add method is called, a brand-new AddressEntry object is created from the values supplied.

To delete a member from a distribution list, call the AddressEntry object's Delete method, as shown here:

CdoAddressEntryMember.Delete

Filters

To assist with locating specific items in Messages and AddressEntries collections, CDO provides the MessageFilter and AddressEntryFilter objects. Every Messages collection has a MessageFilter object associated with it, which can be accessed through the Messages collection's Filter property. Similarly, every AddressEntries collection has an AddressEntryFilter object associated with it, accessible through the AddressEntries collection's Filter property. By setting properties on a filter object, the associated collection retrieves only those items that fulfill the criteria thus specified. This is far more efficient than using code to read through the entire collection, examine each item to determine if it meets the criteria, and act on only the items of interest. The code in Example 7-9 processes only unread messages having a message class of "IPM.MyMessageClass".

Example 7-9: Constraining a Messages Collection

' CdoSession previously Dim'ed and Set.
 
Dim CdoFolder As MAPI.Folder
Dim CdoMessages As MAPI.Messages
Dim CdoMessageFilter As MAPI.MessageFilter
Dim CdoMessage As MAPI.Message
 
Set CdoFolder = CdoSession.Inbox
Set CdoMessages = CdoFolder.Messages
Set CdoMessageFilter = CdoMessages.Filter
 
' Constrain the Messages collection.
CdoMessageFilter.Type = "IPM.MyMessageClass"
CdoMessageFilter.Unread = True
 
For Each CdoMessage In CdoMessages
   ' Do something with the message.
Next CdoMessage
 
' Clean up.
Set CdoMessage = Nothing
Set CdoMessageFilter = Nothing
Set CdoMessages = Nothing
Set CdoFolder = Nothing

The AddressEntryFilter is used in precisely the same way. The code in Example 7-10 processes all address entries that have a display name starting with the string "Dave".

Example 7-10: Constraining an AddressEntries Collection

' CdoSession previously Dim'ed and Set.
 
Dim CdoAddressLists As MAPI.AddressLists
Dim CdoAddressList As MAPI.AddressList
Dim CdoAddressEntries As MAPI.AddressEntries
Dim CdoAddressEntryFilter As MAPI.AddressEntryFilter
Dim CdoAddressEntry As MAPI.AddressEntry
 
Set CdoAddressLists = CdoSession.AddressLists
 
For Each CdoAddressList In CdoAddressLists
 
   Set CdoAddressEntries = CdoAddressList.AddressEntries
   
   ' Constrain the address entries collection.
   Set CdoAddressEntryFilter = CdoAddressEntries.Filter
   CdoAddressEntryFilter.Name = "Dave"
   
   ' Process the "Dave" entries.
   For Each CdoAddressEntry In CdoAddressEntries
      ' Process the entry.
   Next CdoAddressEntry
   
Next CdoAddressList
 
' Clean up
Set CdoAddressEntry = Nothing
Set CdoAddressEntryFilter = Nothing
Set CdoAddressEntries = Nothing
Set CdoAddressList = Nothing
Set CdoAddressLists = Nothing

Summary

In this chapter, you gained a lot more detailed knowledge, not only about how CDO works with MAPI, but also about working with various CDO elements, including messages, folders, address books, and filters.

In the next chapter, you'll learn all about how calendars and schedules work, including automating scheduled tasks and getting free/busy information from other users.

Back to: CDO and MAPI Programming with Visual Basic


O'Reilly Home | O'Reilly Bookstores | How to Order | O'Reilly Contacts
International | About O'Reilly | Affiliated Companies

© 2001, O'Reilly & Associates, Inc.
webmaster@oreilly.com