|
|
|
|
CDO and MAPI Programming with Visual BasicDeveloping Mail and Messaging ApplicationsBy Dave GrundgeigerOctober 2000 1-56592-665-X, Order Number: 665X 380 pages, $29.95 |
Chapter 7
Enhancing the Email ClientThe 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 IfThe 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
Ifstatement 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
Trueif 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) ThenDebug.Print "The variables reference the same MAPI message."ElseDebug.Print "The variables reference different MAPI messages."End IfThe 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).SubjectThis line appears to do the following:
- Use the CDO Session object to find the CDO Inbox Folder object.
- Use the CDO Inbox Folder object to find the CDO Messages collection within that object.
- Use the CDO Messages collection to find the first CDO Message object in the collection.
- Use the CDO Message object to read the subject of the message.
However, what the line really does is this:
- Use the CDO Session object to instantiate a new CDO Folder object that wraps the MAPI Inbox folder.
- Use the CDO Folder object to instantiate a new CDO Messages collection object that represents the set of messages stored in the Inbox.
- Use the CDO Messages collection to instantiate a new CDO Message object that wraps the first message in the Inbox folder.
- Use the CDO Message object to read the subject of the message.
- 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:
' WrongDebug.Print CdoSession.Inbox.Messages.Item(1).SubjectDebug.Print CdoSession.Inbox.Messages.Item(1).TimeReceivedDebug.Print CdoSession.Inbox.Messages.Item(1).TextThe corrected code performs each object instantiation only once:
With CdoSession.Inbox.Messages.Item(1)Debug.Print .SubjectDebug.Print .TimeReceivedDebug.Print .TextEnd WithNote 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:
' WrongCdoSession.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 WithOne 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).SubjectCode this longer but more robust alternative:
Dim CdoFolder As MAPI.FolderDim CdoMessages As MAPI.MessagesDim CdoMessage As MAPI.MessageSet CdoFolder = CdoSession.InboxSet CdoMessages = CdoFolder.MessagesSet CdoMessage = CdoMessages.Item(1)Debug.Print CdoMessage.SubjectBecause 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:
- The return value from the MoveTo method is assigned right back to CdoMessage . This allows you to continue using CdoMessage to access the message.
- The target folder knows what message store contains it. The message store ID is given in the Folder object's StoreID property. This value is passed to the Message object's MoveTo method just in case the message store of the target folder is different from the one containing the original message.
- The move occurs immediately--it is not necessary to call the Message object's Update method.
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. IfFalse, this parameter indicates that the message should be permanently deleted. The default isFalse.For example, this code permanently deletes a message:
' CdoMessage previously Dim'ed and Set.CdoMessage.DeleteThis 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:=TrueBelieve 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.UpdateChanges 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 toFalseindicates 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
Truewould 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.SessionSet CdoSession = CdoMessage.SessionSet CdoMessage = CdoSession.GetMessage(CdoMessage.ID, _CdoMessage.StoreID)End SubWorking 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.FolderDim CdoInfoStore As MAPI.InfoStoreDim CdoFolderRoot As MAPI.FolderDim CdoFolders As MAPI.FoldersDim CdoFolder As MAPI.FolderDim 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.GetInfoStoreSet CdoFolderRoot = CdoInfoStore.RootFolderSet CdoFolders = CdoFolderRoot.FoldersElse' Get the Folders collection from the parent folder.Set CdoFolders = CdoFolderParent.FoldersEnd If' Loop through the folders in the collection until the' desired folder is found.bFound = FalseSet CdoFolder = CdoFolders.GetFirstDo While (Not bFound) And Not (CdoFolder Is Nothing)If CdoFolder.Name = strFolderName ThenbFound = TrueElseSet CdoFolder = CdoFolders.GetNextEnd IfLoop' If not found, then create it (if caller said to).If (CdoFolder Is Nothing) And bCreate ThenSet CdoFolder = CdoFolders.Add(strFolderName)End IfSet GetFolderByName = CdoFolder' Release our local objects.Set CdoFolder = NothingSet CdoFolders = NothingSet CdoFolderRoot = NothingSet CdoInfoStore = NothingEnd Function ' GetFolderByNameThe 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 ofNothing.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.FolderSet CdoFolder = GetFolderByName(CdoSession, "Drafts")CdoFolder.DeleteLet 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...Casestatement. 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.SessionDim CdoInfoStore As MAPI.InfoStoreDim CdoRootFolder As MAPI.Folder' Log on to MAPI.Set CdoSession = New MAPI.SessionCdoSession.Logon' Get the root folder of the user's default message store.Set CdoInfoStore = CdoSession.GetInfoStoreSet 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 = NothingSet CdoInfoStore = NothingCdoSession.LogoffSet CdoSession = NothingEnd Sub ' ShowHiddenPrivate 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.FoldersDim CdoSubFolder As MAPI.FolderDim CdoMessages As MAPI.MessagesDim CdoMessage As MAPI.MessageDim CdoFields As MAPI.FieldsDim CdoField As MAPI.FieldDim 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.HiddenMessagesSet CdoFolders = CdoFolder.FoldersPrint #1, strIndent; "Number of Hidden Messages: "; CdoMessages.CountPrint #1, strIndent; "Number of Subfolders: "; CdoFolders.Count' Loop through all of the hidden messages.For Each CdoMessage In CdoMessagesSet CdoFields = CdoMessage.FieldsSet 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.CountPrint #1, strIndent; "Fields Collection: ";If CdoFields.Count > 0 ThenPrint #1,ElsePrint #1, "(empty)"End If' Now print information about all of the fields (properties) of the' message.For Each CdoField In CdoFieldsPrint #1, strIndent; " ----------"Print #1, strIndent; " Index: "; CdoField.IndexPrint #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 NextPrint #1, CdoField.TypeIf Err.Number <> 0 ThenPrint #1, "(can't print: error "; Err.Number; ", "; _Err.Description; ")"End IfOn Error GoTo 0Print #1, strIndent; " Value: """; CdoField.Value; """"Next CdoFieldNext CdoMessage' If we have subfolders, then print a header for those.If CdoFolders.Count > 0 ThenPrint #1, ""Print #1, strIndent; "Folders Collection:"End If' Now start over for each subfolder.For Each CdoSubFolder In CdoFoldersRecurseHidden CdoSubFolder, strIndent & " "Next CdoSubFolderSet CdoAttachments = NothingSet CdoField = NothingSet CdoFields = NothingSet CdoMessage = NothingSet CdoMessages = NothingSet CdoSubFolder = NothingSet CdoFolders = NothingEnd 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 CdoPropTagCase 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 ElseCdoPrTextFromCode = ""End SelectEnd Function ' CdoPrTextFromCodeWorking 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.AddressListsSet CdoAddressList = CdoAddressLists.Item("Personal Address Book")If an invalid name is specified when attempting to retrieve an AddressList object, a
CdoE_NOT_FOUNDerror 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
CdoPrivateDistListresults in aCdoE_NOT_FOUNDerror 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
CdoDistListorCdoPrivateDistList), 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 toNothing.
- 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.AddressEntryOn Error GoTo ErrorHandlerSet CdoAddressEntry = CdoMessage.SenderCdoAddressEntry.DetailsSet CdoAddressEntry = NothingExit FunctionErrorHandler:' If user pressed cancel, don't treat that as an error.If Err.Number <> CdoE_USER_CANCEL ThenErr.Raise Err.NumberEnd IfEnd 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) ThenMsgBox "The AddressEntry objects are the same."ElseMsgBox "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.AddressEntrySet CdoAddressEntry = CdoMessage.SenderCdoAddressEntry.Name = "Jane Newname"CdoAddressEntry.UpdateTo 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:
- Establishes a MAPI session.
- Obtains the top-level AddressLists collection from the Session object.
- Opens a text file to hold the text that will be printed.
- 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.SessionDim CdoAddressLists As MAPI.AddressListsDim CdoAddressList As MAPI.AddressListDim CdoAddressEntries As MAPI.AddressEntriesDim nFileNum As Long' Create a Session object and log on to MAPI.Set CdoSession = New MAPI.SessionCdoSession.Logon' Get the top list of address books.Set CdoAddressLists = CdoSession.AddressLists' Here's where we're going to print our output.nFileNum = FreeFileOpen "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.NamePrint #nFileNum, "AddressEntries collection: ";' Print the objects in the AddressEntries collection.Set CdoAddressEntries = CdoAddressList.AddressEntriesIf CdoAddressEntries.Count = 0 ThenPrint #nFileNum, "(empty)"ElsePrint #nFileNum, ""RecurseAddressEntries nFileNum, CdoAddressEntries, " "End IfNext CdoAddressList' All done.Close #nFileNum' Use Notepad to view the results.Shell "notepad.exe ""C:\AddressLists.txt""", vbNormalFocusSet CdoAddressEntries = NothingSet CdoAddressList = NothingSet CdoAddressLists = NothingCdoSession.LogoffSet CdoSession = NothingEnd Sub ' IterateAddressListsPrivate 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.AddressEntryDim CdoAddressEntriesMembers As MAPI.AddressEntriesFor Each CdoAddressEntry In CdoAddressEntriesPrint #nFileNum, strIndent; "----------"Print #nFileNum, strIndent; "Name: "; CdoAddressEntry.NamePrint #nFileNum, strIndent; "Type: "; CdoAddressEntry.TypePrint #nFileNum, strIndent; "DisplayType: "; CdoAddressEntry.DisplayTypePrint #nFileNum, strIndent; "Address: ";On Error Resume NextPrint #nFileNum, CdoAddressEntry.AddressIf Err.Number = CdoE_NOT_FOUND ThenPrint #nFileNum, "(no address)"End IfOn 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.MembersIf CdoAddressEntriesMembers Is Nothing ThenPrint #nFileNum, "(none)"ElseIf CdoAddressEntriesMembers.Count = 0 ThenPrint #nFileNum, "(none)"ElsePrint #nFileNum, ""RecurseAddressEntries nFileNum, CdoAddressEntriesMembers, _strIndent & " "End IfNext CdoAddressEntrySet CdoAddressEntriesMembers = NothingSet CdoAddressEntry = NothingEnd Sub ' RecurseAddressEntries
Figure 7-6. Sample output from Example 7-5
![]()
Creating and Retrieving Address Book Entries
To add a new entry to an address book:
- Get the AddressLists collection from the Session object. Recall that the AddressLists collection represents all address books configured for the currently logged-in profile.
- Get a particular AddressList object from the AddressLists collection.
- Get the AddressEntries collection from the AddressList object.
- Call the Add method of the AddressEntries collection to add a new address. Its syntax is:
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.
- 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.SessionDim CdoAddressLists As MAPI.AddressListsDim CdoAddressList As MAPI.AddressListDim CdoAddressEntries As MAPI.AddressEntriesDim CdoAddressEntry As MAPI.AddressEntry' Create a new session object and logon.Set CdoSession = New MAPI.SessionCdoSession.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 = NothingSet CdoAddressEntries = NothingSet CdoAddressList = NothingSet CdoAddressLists = NothingCdoSession.LogoffSet CdoSession = NothingNote 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_ACCESSerror is raised. Similarly, contacts folders being used as address books can't be updated in this manner. Attempting to do so will also raise aCdoE_NO_ACCESSerror. (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_COLLISIONerror 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.AddressEntryDim 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 = NothingSet CdoAddressEntrySender = NothingThe 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.SessionDim CdoAddressLists As MAPI.AddressListsDim CdoAddressList As MAPI.AddressListDim CdoAddressEntries As MAPI.AddressEntriesDim CdoAddressEntry As MAPI.AddressEntry' Create a new Session object and log on.Set CdoSession = New MAPI.SessionCdoSession.Logon' Get the Personal Address Book's AddressEntries collectionSet CdoAddressLists = CdoSession.AddressListsSet 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 = NothingSet CdoAddressEntries = NothingSet CdoAddressList = NothingSet CdoAddressLists = NothingCdoSession.LogoffSet CdoSession = NothingRecall 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.SessionDim CdoAddressLists As MAPI.AddressListsDim CdoAddressList As MAPI.AddressListDim CdoAddressEntries As MAPI.AddressEntriesDim CdoAddressEntry As MAPI.AddressEntryDim CdoAddressEntriesMembers As MAPI.AddressEntriesDim CdoAddressEntryNewMember As MAPI.AddressEntry' Create a new Session object and log on.Set CdoSession = New MAPI.SessionCdoSession.Logon' Get the Personal Address Book's AddressEntries collection.Set CdoAddressLists = CdoSession.AddressListsSet 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 = NothingSet CdoAddressEntriesMembers = NothingSet CdoAddressEntry = NothingSet CdoAddressEntries = NothingSet CdoAddressList = NothingSet CdoAddressLists = NothingCdoSession.LogoffSet CdoSession = NothingUnfortunately, 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.DeleteFilters
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.FolderDim CdoMessages As MAPI.MessagesDim CdoMessageFilter As MAPI.MessageFilterDim CdoMessage As MAPI.MessageSet CdoFolder = CdoSession.InboxSet CdoMessages = CdoFolder.MessagesSet CdoMessageFilter = CdoMessages.Filter' Constrain the Messages collection.CdoMessageFilter.Type = "IPM.MyMessageClass"CdoMessageFilter.Unread = TrueFor Each CdoMessage In CdoMessages' Do something with the message.Next CdoMessage' Clean up.Set CdoMessage = NothingSet CdoMessageFilter = NothingSet CdoMessages = NothingSet CdoFolder = NothingThe 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.AddressListsDim CdoAddressList As MAPI.AddressListDim CdoAddressEntries As MAPI.AddressEntriesDim CdoAddressEntryFilter As MAPI.AddressEntryFilterDim CdoAddressEntry As MAPI.AddressEntrySet CdoAddressLists = CdoSession.AddressListsFor Each CdoAddressList In CdoAddressListsSet CdoAddressEntries = CdoAddressList.AddressEntries' Constrain the address entries collection.Set CdoAddressEntryFilter = CdoAddressEntries.FilterCdoAddressEntryFilter.Name = "Dave"' Process the "Dave" entries.For Each CdoAddressEntry In CdoAddressEntries' Process the entry.Next CdoAddressEntryNext CdoAddressList' Clean upSet CdoAddressEntry = NothingSet CdoAddressEntryFilter = NothingSet CdoAddressEntries = NothingSet CdoAddressList = NothingSet CdoAddressLists = NothingSummary
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
© 2001, O'Reilly & Associates, Inc.
webmaster@oreilly.com