This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.
Collaboration Data Objects: Send Microsoft Exchange Appointment Reminders to Your Pager Using CDO
|This article assumes you're familiar with Exchange, Visual Basic|
|Level of Difficulty 1 2 3|
Code for this article: McCormick0600.exe(205KB)
Besides your typical e-mail services, Microsoft Exchange also provides the service infrastructure you need to build extensible knowledge and workflow management systems. Understanding Collaboration Data Objects (CDO) is the key to accessing these Exchange services for your own programs. With CDO you can automate tedious tasks and integrate information such as e-mail, calendars, and contacts with almost any application.
This article explains how CDO enables you to access Exchange services, then walks you through a sample application that reads calendar events from an Exchange server and sends pager reminders to your users. s a consultant, I see many of the same problems recur at many of the companies I work with. One of the problems I get the most questions about is how companies can get a better return on their investment in MicrosoftÂ® Exchange. Companies will migrate from a text-only e-mail system to an integrated collaboration system like Exchange, but users still just send e-mail (except maybe now they attach a file or use boldface).
Workflow automation, process automation, and collaboration are still a rarity in most corporate environments. Microsoft has focused on providing tools that companies can use to build a knowledge management infrastructureâ€"and Exchange is at the heart of the effort. For developers, the key to using the full potential of Exchange is learning about Collaboration Data Objects (CDO), the interface into every possible data nugget and service within Exchange. With CDO you can automate tasks and integrate e-mail, calendaring, and contact information in any client/server, n-tier, or Web application. I will demonstrate how to access CDO, and will introduce you to a sample application that shows the potential for improving business processes by using Exchange.
My sample application will automate the normally mundane process of logging on to Exchange to view or print out your schedule every day. Users can receive a daily agenda via their pager and get meeting reminders along with descriptions and locations of those meetings automatically. This application is simply one example of how easily you can create more efficient workforce and business processes using Exchange. Plus, when your boss remembers his anniversary because of a pager reminderï¿½ well, maybe you'll get a raise!
Collaborative Data Objects
CDO is the COM-based interface for accessing Exchange or any other Messaging API (MAPI)-compliant service such as fax services, POP3 services, and so on. CDO provides an object-oriented interface for accessing the data stored in a MAPI-compliant service. You may find it easier to use CDO if you think of it as a staircase. You are at the top of the staircase, and the data you want is at the bottom. To get what you want, you will have to walk each stair, one at a time.
The first stair in the CDO staircase is the Session object (see Figure 1). This object identifies who you are to the underlying MAPI services so that security and authorization can take place. Once you log onto your session, you can access a Folder object. A folder represents a container for any type of data: a set of messages, the addresses in the global address list, your calendar appointments, and so on. Once you have the folder you want, you can access its Messages collection, through which you can access the data you need. The Messages collection is similar to a database recordset; it contains methods for moving to a particular record of data, much like navigating a database recordset. Once you have the Messages collection, you can navigate to any particular data nugget: a Message object, a Contact object, an Address Entry object, an Appointment object, and so on.
Figure 1CDO Objects
Now let's go back up to the top and look at the code for each step. First, you'll need to install Visual BasicÂ® 6.0 and the latest service pack (https://msdn.microsoft.com/vstudio/sp/vs6sp3/vbfixes.asp), as well as the latest version of the Microsoft OutlookÂ® client with Exchange support. During this installation, make sure that CDO is installed with Outlook. CDO is installed by default for Outlook 98, but not for Outlook 2000. Once Outlook is installed, make sure you can connect to your Exchange servers.
All CDO access begins with creating a session. After creating a CDO session, you need to log onto it to initialize the session and set up your access privileges with Exchange.
Set CDOSession = CreateObject("MAPI.Session") CDOSession.Logon
By default this will pop up a dialog box that asks you which profile to use for this logon. A profile is a collection of settings that define mail services, logon settings, message stores, and so on. This profile will tell CDO what services are available (such as Internet Mail, Exchange Server, Personal Folders, and Personal Address Book) and what security settings are used to access those services. To bypass this dialog box, you can send a profile name or Exchange server settings when logging onto the CDO session.
Once you are logged in, you can access data and services offered by Exchange. For example, if you want to log into a user's mailbox within Exchange and send e-mail, the code would look like this:
In this code, you first log onto a CDO session. To bypass the profile dialog box, you send the logon method, the name of the profile to use, then access the user's Outbox by setting a Folder object to the Outbox property of the CDO session. Exchange provides individual folders for your Inbox, Outbox, Sent Items, Calendar, Contacts, Journal entries, and so on. Once you set the Folder object to the Outbox property, this particular folder represents the Outbox of the Exchange mailbox.
Set CDOSession = CreateObject("MAPI.Session") CDOSession.Logon "Exchange Profile" Set oFolder = CDOSession.Outbox Set oMessages = oFolder.Messages Set oMsg = oMessages.Add Set oRcpt = oMsg.Recipients oRcpt.Add , "SMTP:email@example.com", CDOTo oRcpt.Resolve oMsg.Subject = "Test Message" oMsg.Text = "This is a test message..." oMsg.Send CDOSession.Logoff
The next step to sending an e-mail is accessing the Messages collection within this folder. Do this by setting the Messages object equal to the Messages property of the Folder object. This particular Messages object now represents the Messages collection of the Exchange mailbox's Outbox.
Next you create a new message within the Messages collection by calling the Add method. This will create a new Message object to fill out and send. There are a few properties to set on this Message object before it can be sent. First, you need to set a recipient for the message by accessing the Recipients collection of the Message object. The Recipients collection is the collection of addresses for this e-mail. You then call the Add method of the Recipients collection to create a new Recipient object with the necessary address properties. Set the address of the recipient, as well as the type of recipient (TO, CC, or BCC), by sending the e-mail address and a global constant, CDOTo, to the Add method.
Next you need to resolve the address of the new recipient. Resolving the address makes sure that Exchange has the proper tools to send the message to this person. For instance, if you set up the recipient to be an Internet e-mail address and this Exchange environment has no method of sending SMTP mail, resolving the address would fail.
You set the subject and text of the message by setting the appropriate properties of the Message object. You send the message by calling the Send method of the Message object. This saves your message within the Outbox folder of this mailbox and marks the message as ready to be sent by the Exchange server. Exchange takes over and sends the message automatically.
The final step is to log off the CDO session. This in turn logs you out of Exchange server. The method for sending an e-mail is quite easy. You simply need to create and save a message into the Outbox folder and your Exchange services will take care of sending it.
Now let's look at how to access an Exchange mailbox calendar. Accessing a calendar is much like accessing an Outbox for sending e-mail. The calendar is simply another folder within Exchange that contains messages. The following code sample will log onto an Exchange mailbox and access its Calendar folder. It will then loop through that calendar, display all of the user's appointments, and display their contents in a message box.
Notice that you are logging onto the CDO session differently. This isn't a requirement; I just wanted to show you another method for logging onto a CDO session. When you send the Exchange server name and mailbox alias as parameters to the logon method, you don't need a defined Exchange profile. By sending these parameters, CDO will create a profile on the fly when you log on and delete it once you log off. This means you don't have to worry about creating an identical profile on every desktop that is running your program; you simply need to know the Exchange server name and the mailbox alias you want to access and your program will work from any computer on your network.
Set CDOSession = CreateObject("MAPI.Session") CDOSession.Logon , , , , , , "TESTEXSVR" & vbLf & "Sean" Set oFolder = CDOSession.GetDefaultFolder (CdoDefaultFolderCalendar) Set oMessages = oFolder.Messages
Set oAppointment = oMessages.GetFirst Do While Not oAppointment Is Nothing MsgBox "Appointment: " & oAppointment.Subject & vbCRLF & _ "Start Time: " oAppointment.StartTime & vbCRLF & _ "End Time: " oAppointment.EndTime & vbCRLF & _ "Location: " oAppointment.Location & vbCRLF & _ "Description: " oAppointment.Text & vbCRLF Set oAppointment = oMessages.GetNext Loop CDOSession.Logoff
Also notice that you're accessing the calendar through the Folder object again. This time you have to call a special function to access this folder because the CDO session does not have an easy-access property for the Calendar folder. Once you have the Folder object, you can access the individual appointments by getting the Messages collection and accessing its contents.
This sample code will loop through the contents of the Messages collection by starting at the first appointment and using the GetNext method, which sets the Appointment object to the next object within the Messages collection. The code then prints out the details of each appointment by accessing the properties of the Appointment object that hold the start time, end time, location, subject, and a detailed description of the appointment. As soon as you get to the last appointment, you jump out of the loop and close the Session object by calling the Logoff method.
But how do you access only the appointments for a certain date or time without having to loop through all of them? You can do this by modifying the Filter object of the Messages collection. Before accessing each individual appointment within the collection, you can narrow the Messages collection contents down to those appointments that occur within a particular time (see Figure 2).
You can see that the code is almost the same, but three lines have been added between setting the Messages collection and accessing the contents of that collection. To add a filter to the Messages collection, you set a Filter object to the Filter property object of the Messages collection and modify the properties of the newly created Filter object. You then set the values of the dates you want to narrow the contents on by adding the Start Time and End Time fields to this Filter object. This causes the Messages collection to contain only appointments that fall on 1/1/2000. Once you do that, you can access the Messages collection as before, but it will only contain appointments that fall on this particular date.
The Contacts folder and its contents are treated like the Calendar folder. First you need to retrieve the Contacts folder and access its Messages collection. To add a new entry in the Contacts folder, you run the Add method of the Messages collection. My sample code sends a special parameter to the Add method (see Figure 3). This tells the folder what type of object you are adding. You can add any type of object (e-mail message, contact, calendar appointment, or whatever) to any folder, but it is best to keep contacts in the Contacts folder, appointments in the Calendar folder, and so on.
Once you have created a new Contact object, you can set its values by setting the fields of the contact. You do this by accessing the Fields collection of the new Contact object and calling the Add method with the field name (using CDO-defined constants) and the value you want to set. Finally, you save the new contact entry by calling the Update method.
Once you have created this new contact entry, you can access its data in pretty much the same manner. First you need to get the Messages collection of the Contacts folder. Then you can access each entry within the Messages collection using the move method of that collection (GetFirst, GetNext, and so on). Each entry's properties can be retrieved by accessing the Fields collection and then sending it the parameter (using a CDO-defined constant) of the property you want. The following example will loop through all of the contacts in your Contacts folder and print out the name and the e-mail address of each contact.
Up to this point, I have only shown you how to access the Messages collection of a Folder object. A folder can also contain other folders. The public folder system used by Exchange is the perfect example of folders containing folders. To access a public folder, you simply start at the root Public Folders folder. Then you can access subfolders by setting a new Folders collection object to the Folders property of the root Folder object. This Folders collection can then be looped through to access its contents much like accessing the contents of a Messages collection. Once you have the Folder object you want, you can access its contents via its Messages collection. In addition, you could access this folder's subfolders by setting a new Folders collection to its Folders property.
Set oAddr = oAddressEntries.GetFirst Do While Not oAddr Is Nothing Msgbox "Name: " & oAddr.Fields(CdoPR_GIVEN_NAME) & _ oAddr.Fields(CdoPR_SURNAME) & vbCRLF & _ "Email: " & oAddr.Fields(CdoPR_EMAIL_ADDRESS) Set oAddr = Nothing Set oAddr = oAddressEntries.GetNext Loop
The following code shows how to access the root Public Folders folder and print out the names of any subfolders that exist under it. This is much like looping through each Message object within a Messages collection:
Set CDOSession = CreateObject("MAPI.Session") CDOSession.Logon , , , , , , "TESTEXSERVER & vbLf & "SEAN" Set oInfoStores=CDOSession.InfoStores Set oInfoStore = oInfoStores("Public Folders") Set oRootFolder = oInfoStore.RootFolder Set oFolders = oRootFolder.Folders
Set oFolder = oFolders.GetFirst Do While Not oFolder Is Nothing MsgBox oFolder.Name oFolder=Nothing oFolder=oFolders.GetNext Loop
You can see that I have used a new object called an InfoStore. An InfoStore object represents a service provided through MAPI. (InfoStores is a collection of InfoStore objects.) For instance, when hitting an Exchange server through your CDO Session object, there are typically two InfoStore objects available: one for your mailbox, and one for the public folders. The methods I described earlier for accessing a mailbox are simply shortcuts around accessing the mailbox via the InfoStore.
The InfoStore object allows for folders to be organized by services: Public Folders, Mailboxes, Personal Folders, and so on. A service provides a set of folders, and those folders in turn provide collections of messages or collections of subfolders (which in turn can contain messages or more folders). Think of InfoStores as a set of organizing bins. Here is a sample that shows all of the InfoStores available to your CDO Session object:
Set CDOSession = CreateObject("MAPI.Session") CDOSession.Logon , , , , , , "TESTEXSERVER & vbLf & "SEAN" Set oInfoStores=CDOSession.InfoStores For i=1 to oInfoStores.Count Set oInfoStore = oInfoStores(i) MsgBox oInfoStore.Name Next
Sending Reminders to a Pager
At this point you've seen all of the basic techniques for accessing the common features of Exchange. Now I'll put all of these together into a sample application that sends a daily agenda and meeting reminders to a pager. The full source code for this sample can be found at the link at the top of this article.
The sample application runs like this: every weekday at 7:00 AM, the program checks each configured person's calendar, loops through the appointments for the current day, and creates an outgoing message to that user's pager telling them of the appointments they have for the day. Then it runs each hour to check for any appointments that are going to start within the next hour. If it finds one, the program will set up an outgoing message addressed to the user's pager, reminding them the appointment with a time-delay delivery.
The time delay enables the user to receive the page right before the appointment. Sending the reminder with a time delay allows you to run each hour (instead of each minute) to check for appointment reminders. Also, running each hour, and not once a day, for meeting reminders allows a schedule to change during the day so you get the most current reminders (in case meetings get cancelled or changed). The reminders will have the appointment start and end times, the location, the subject, and a description of the meeting.
Since I've already created code for accessing calendars and sending outbound messages as reminders, I need to set up some configuration information for the program. The configuration system specifies which users' mailboxes I need to access, their Exchange server settings, and their pager e-mail addresses. I used a Microsoft Access database to hold these settingsâ€"a simple MDB file with a single table in it will do. This table holds the users' pager e-mail addresses, their Exchange mailbox servers, their Exchange mailbox aliases (to log onto CDO), and how many minutes before each meeting they want to be reminded. Some users like to be reminded 30 minutes before, while others may want to be reminded only two minutes before the meeting. Once I have these configuration settings, I can start to periodically check users' calendars for meeting reminders and daily agendas, and send them reminders by creating messages in the Outbox while I am logged on checking their calendars.
To make the application more robust, I added the Windows NTÂ® Service control, NTSRV.ocx, provided on the MSDNâ„¢ Library CD. This control allows a simple form application built with Visual Basic to run as a Windows NT service. Once the application is running the way you want, you can set it up to run as a service on your Exchange server. That way it is always running and you don't have to worry about having a client computer up, running, and logged in at all times.
The sample app must run under a service identity that has access to read all of the calendars it is checking. I suggest creating a special service account and giving it necessary Exchange rights to view the calendars. That way, you don't have to compromise anything and this application can only access the data it needs.
This service will log onto users' mailboxes on a regular basis. This causes an event in your Exchange server's application log stating that somebody other than the default user for a mailbox has accessed it. This security feature is turned on by default when you install Exchange so an administrator is warned if such an event occurs. Because of this, you can either clear the event log on a regular basis or choose to turn down security logging for Exchange to be a little more forgiving.
If you are going to use this service in a site with multiple domains and multiple Exchange servers, you may want to think about installing an instance of the service within each domain so you don't have to worry about trusts and authentication for your service across domains.
Included with the sample application is a Web interface for adding and modifying your configured pager users within the Access database. By doing this, you can install the service at a remote location Exchange site and administer the users of the service from your office.
For related articles see:
Sean McCormick is a senior systems consultant for EnterpriseWise in Nashville, TN, designing and developing* n*-tier apps using the Microsoft DNA architecture. Sean has created solutions in the areas of manufacturing, financial reporting, workflow, sales automation, and knowledge management. You can reach Sean at https://www.seanmccormick.com.
From the June 2000 issue of MSDN Magazine.