CDO for Exchange Server


Exchange Server contains a version of CDO called CDO for Exchange. This version of CDO builds on OLE DB, which means that its object model is different from that of previous versions. For example, instead of having navigational objects, such as InfoStore , the new version of CDO relies on ADO for record navigation. Therefore, if you want to query Exchange Server for a specific set of records, you must rely on ADO. However, if you want to perform collaborative functions on those records, you need to use CDO. You can retrieve items using ADO and then open and process each item using CDO.

CDO adds collaborative functionality above and beyond that provided by ADO. If you need to create recurring meetings, ADO won't be very helpful ”for example, you would have to determine which properties to set to make an appointment recur on the third Tuesday of every month. With CDO, however, you set some properties, save the appointment, and suddenly you have a recurring meeting. CDO does all the hardcoding for you behind the scenes.

CDO and ADO were designed to work together. Both object models have the same Fields collection. Furthermore, CDO objects can be bound directly to ADO objects. This allows you to use the same connection with the Exchange server.

CDO Design Goals

Microsoft had a number of design goals in mind for the CDO object library. First, as just discussed, integrating with and extending ADO was key. Second, having learned from previous versions of the CDO object model, the CDO design team knew the model needed to be dual- interfaced so that different development environments, such as Visual Basic, Microsoft Visual C++, and ASP, could take advantage of it. The third goal was to have CDO adhere to Internet standards. Internet standards are now critical to CDO because it uses vCard, iCal, Lightweight Directory Access Protocol (LDAP), Multipurpose Internet Mail Extensions (MIME), MIME Encapsulation of Aggregate HTML Documents (MHTML), Simple Mail Transfer Protocol (SMTP), and Network News Transfer Protocol (NNTP). The final and probably most important design goal for CDO was to make the developer's job easier by providing a rich set of objects on top of OLE DB for building collaborative applications.

start sidebar
CDO for Windows

You might have seen CDO for Windows. Consider this object model the little brother of CDO for Exchange Server. CDO for Windows provides SMTP and NNTP support. It also provides support for protocol events such as SMTP events. However, CDO for Windows does not provide mailbox support or public folder support. CDO for Exchange Server provides these features and extends CDO for Windows. If you get started with CDO for Windows, you'll have a working knowledge of the basics of CDO for Exchange Server.

end sidebar
 

The CDO Object Model

The CDO object model consists of five main components . I say components rather than objects because the CDO object model contains dozens of objects. The five main components are messaging, calendaring, contacts, workflow, and management. We'll cover the most common tasks you'll perform with the CDO library in each of these areas. I won't cover the specific properties and methods of these components in great detail, however; the Exchange Server SDK provides extensive documentation on this subject.

Frequently Used Objects in CDO

You'll commonly work with two objects, the Configuration object and the DataSource object, on all CDO objects you use in your applications. Before we look at the most typical CDO functionality you'll use, let's examine these two objects.

Configuration Object

The Configuration object allows you to customize the parameters CDO uses and the way CDO works. For example, using the Configuration object, you can set the e-mail address of the message sender, set which proxy server to use, set the username and password if you require authentication via SMTP or NNTP, or select other configuration options. The following code, taken from the workflow process, sets the sender e-mail address for a meeting request to the notification address you specified in the setup program of the Training application:

 'Create a throwaway appointment set oAppt = CreateObject("CDO.Appointment") set oConfig = CreateObject("CDO.Configuration") strNotificationAddress = GetWorkflowSessionField("notificationaddress") oConfig.Fields("http://schemas.microsoft.com/cdo/" & _     "configuration/sendemailaddress") = strNotificationAddress oConfig.Fields.Update oAppt.Configuration = oConfig 

As you can see, you use the Fields collection on the CDO Configuration object to set your properties. All the properties you can set are contained in the "http://schemas.microsoft.com/cdo/configuration" namespace.

Another common use for the Configuration object is to set the time zone for viewing appointments from Exchange Server. Remember that Exchange Server stores dates in UTC format. Any dates you retrieve through ADO are returned in UTC. However, you can use CDO to change UTC dates to local time zone dates for the client. Sometimes you might want to use a different time zone in your application than the one originally detected by CDO. The Configuration object allows you to do this. The following code changes the time zone to Mountain:

 Dim objAppt As New CDO.Appointment Dim objConfig As New CDO.Configuration objConfig.Fields(cdoTimeZoneID) =  cdoMountain objAppt.Configuration = objConfig 

DataSource Object

The DataSource object provides access from CDO objects to data sources, such as the Web Storage System or Active Directory. You can use the DataSource object to open items from or save items to data sources from CDO. You should become familiar with six methods on the DataSource object: Open , OpenObject , Save , SaveTo , SaveToContainer , and SaveToObject .

Open method     The DataSource object's Open method is similar to the Open method on the ADO Record object. The only difference is that ADO can create items using the Open method, and CDO cannot. Here is the syntax for the Open method:

 Open(ByVal SourceURL as String,      ByVal ActiveConnection as Object,      [ByVal Mode as ConnectModeEnum],      [ByVal CreateOptions as RecordCreateOptionsEnum],      [ByVal Options as RecordOpenOptionsEnum],      [ByVal UserName as String],      [ByVal Password as String]) 

Here is a code example that uses the Open method in conjunction with an ADO RecordSet :

 Dim rs as New RecordSet Dim msg as New Message      fldr = "file://./backofficestorage/domain/MBX/user/inbox"      rs.Open "Select * from " & _     "scope('shallow traversal of " & _     fldr & "')" & _     "where urn:schemas:mailheader:subject = 'hello'" rs.MoveFirst msg.DataSource.Open rs("DAV:href"),rs.ActiveConnection 

OpenObject method     You can use the OpenObject method to open data from another object rather than from a data source such as Exchange Server. A common use for OpenObject is to open an embedded message in another message. This is the syntax for OpenObject :

 OpenObject(ByVal Source as Object, ByVal InterfaceName as String) 

The following code opens an embedded message within another message. The code assumes that you already retrieved the object that represents the embedded message in a variable named oBodyPart .

 oDataSource.OpenObject oBodyPart, "IBodyPart" 

Save method     The Save method writes back data to the currently opened data source. This method is so simple that we won't even look at a code sample. You should, however, call this method if you change any values that need to be written back. You should also be sure to open the data source with the read/write flags; otherwise , you will receive an error.

SaveTo method     The SaveTo method allows you to save an item to a URL you specify. As you will see momentarily, this method differs from the SaveToContainer method, which doesn't let you specify a URL to the item that you want to create. Instead, the SaveToContainer method lets you specify the URL to the container where you want to save the item. When you use SaveToContainer , CDO generates a GUID to identify your item. This is actually quite useful because you do not have to worry about conflicting URLs when you save items. The syntax for the SaveTo method is shown here, along with a code sample:

 SaveTo(ByVal SourceURL as String,        ByVal ActiveConnection as Object,        [ByVal Mode as ConnectModeEnum],        [ByVal CreateOptions as RecordCreateOptionsEnum],        [ByVal Options as RecordOpenOptionsEnum],        [ByVal UserName as String],        [ByVal Password as String])      'Assume oMsg is a valid message Set oDataSource = oMsg      oDataSource.SaveTo "PATHTOFOLDER/myitem.eml", _                    MyCONN, _                    adModeReadWrite, _                    adCreateOverwrite 

SaveToContainer method     The SaveToContainer method, as just discussed, saves the item to a container you specify and assigns a GUID as the identifier for the item. Here is the syntax for the SaveToContainer method, along with a code sample taken from the Training application that saves a new course to the schedule folder:

 SaveToContainer(ByVal ContainerURL as String,                 ByVal ActiveConnection as Object,                 [ByVal Mode as ConnectModeEnum],                 [ByVal CreateOptions as RecordCreateOptionsEnum],                 [ByVal Options as RecordOpenOptionsEnum],                 [ByVal UserName as String],                 [ByVal Password as String])      With iAppt     .Fields("DAV:contentclass").Value = _             "urn:content-classes:trainingevent"     .Fields(strSchema & "instructoremail").Value = _             Cstr(Request.Form("email"))     .Fields(strSchema & "prereqs").Value = CStr(Request.Form("prereqs"))     .Fields(strSchema & "seats").Value = CStr(Request.Form("seats"))     .Fields(strSchema & "authorization").Value = _             Cstr(Request.Form("authorization"))     .Fields(strSchema & "category").Value = cStr(Request.Form("category"))     .Fields("http://schemas.microsoft.com/mapi/proptag/" & _             "x001A001E").Value = "IPM.Appointment"     .Fields.Update End With      iAppt.DataSource.SaveToContainer strScheduleFolderPath 

SaveToObject method     The SaveToObject method allows you to save data to a run-time object rather than to a data source. SaveToObject works the same way as the OpenObject method, except that you're saving information rather than opening it.

CDO Messaging Tasks

In this section, we'll look at some of the most common CDO messaging tasks you can perform. But first you need to know how MIME works because CDO leverages MIME to read and store content.

The MIME specification divides a message body into parts separated by boundary tags. This enables a mail reader to discern where the logical breaks between parts occur. The message body parts can contain child parts or data. MIME body parts can have one of two content types: singular parts or multiple parts . The nice thing about CDO is that unless you really want to, you don't have to deal with the MIME stream itself; CDO provides an easy object model to manipulate MIME. By supporting MIME, CDO allows you to send complex messages, such as embedded messages, as well as messages that contain HTML pages. The following is an example of a MIME message:

 From: "Thomas Rizzo" <thomriz@microsoft.com> To: "Stacy" <stacy@test.com > Subject: Text and HTML Message Date: Tue, 7 Mar 2003 3:32:48 0700 MIME-Version: 1.0 Content-Type: multipart/alternative; boundary="----=_123" ------=_ 123 Content-Type: text/plain; charset="iso-8859-1" Content-Transfer-Encoding: quoted-printable This is a multipart/alternative text & html message.  ------=_ 123 Content-Type: text/html; charset="iso-8859-1" Content-Transfer-Encoding: quoted-printable <HTML> <BODY>This is a multipart/alternative text &amp; html message.</FONT> </BODY></HTML> ------=_123-- 

Now we're ready to discuss the various CDO messaging tasks.

Sending a Standard Message

Sending an e-mail using CDO is straightforward. You simply create the CDO message object, address the message, set the subject and body, and then send the message. We'll look at some of the more complex tasks you can perform with CDO messages in a moment. The code for sending a simple message is shown here:

 set oMsg = createobject("CDO.message") oMsg.To = "stacy@test.com" oMsg.From = "bob@test.com" oMsg.Subject = "Hello world!" oMsg.AutoGenerateTextBody = True oMsg.MimeFormatted = True oMsg.HTMLBody = "<HTML><BODY>This is HTML!</BODY></HTML>" oMsg.Send 

Sending an MHTML Message

In addition to sending a simple HTML message, CDO allows you to send a message with an entire Web page embedded in it. The MHTML standard enables you to take HTML content, convert it into MIME, and embed it in an e-mail message. In CDO, creating an MHTML message is as easy as calling a single method, CreateMHTMLBody . This method takes as parameters the URL of the Web resource you want to embed; flags that specify any content you don't want to embed, such as sounds or images; and if the Web site that you're embedding requires authentication, a username and password. The following code embeds the Microsoft Exchange Web site into an e-mail and mails it:

 Set oMsg = CreateObject("CDO.Message") oMsg.To = "test@test.com" oMsg.Subject = "Exchange Web site" oMsg.CreateMHTMLBody "http://www.microsoft.com/exchange" oMsg.Send 

Adding an Attachment

CDO makes adding attachments easy, too. CDO supports an AddAttachment method, which takes as a parameter a URL for the resource you want to add, and if that resource requires authentication, a username and password. If successful, CDO will return to you the MIME body part that corresponds to the new attachment. Here is code that adds an attachment:

 Dim oMsg as New CDO.Message Dim oBp as CDO.IBodyPart Set oBp = oMsg.AddAttachment("http://www.microsoft.com/myfile") Set oBp = iMsg.AddAttachment("c:\docs\my.doc") Set oBp = iMsg.AddAttachment("file://mypublicshare/docs/mydoc.doc") iMsg.Send 

Adding Mail Headers

You can now access mail headers directly from CDO. Most of the properties that you'll want to access in the mail header are already exposed as top-level properties in CDO. For example, you could look in the mail header to see who a message is from and who it will be sent to. However, CDO already has two properties that perform this service for you: From and To . You might instead want to access the mail headers for a message you're having a problem with if CDO doesn't provide an object for the header you're interested in or if you want to get and set custom headers. The following code sets some built-in and custom headers in an e-mail message:

 Dim oMsg as New CDO.Message Dim oFlds as ADODB.Fields Set oFlds = oMsg.Fields      With oFlds     .Item("urn:schemas:httpmail:to") = "test@test.com"     .Item("urn:schemas:httpmail:from") = "stacy@test.com"     .Item("urn:schemas:mailheader:mycustomheader") = "test"     .Update End With 
Note  

When you work with the BCC property, you need to know about an important workaround. If you attempt to use the Configuration object with CdoSendUsingExchange , you will not be able to send the message. If you need to add users to the BCC property, you should instead set the CdoSendUsingMethod method on the CDO Configuration object to CdoSendUsingPort . Then add information about your server and the port (normally port 25) for connecting to that server to send SMTP mail.

Sending a Message with Custom Properties

If you modify the properties on a message and you want those custom properties to travel with the item, you must use the CdoSendUsingExchange option for the CDO Configuration object. If you do not use this option, all the custom properties on your messages will be lost.

Resolving Addresses

Before adding addresses to the To , CC , or BCC properties on a CDO Message object, you might want to resolve the address against a directory or the Contacts folder to make sure the address is correct. The way to resolve addresses is to use the CDO Addressee object. This object has a number of properties, such as DisplayName and EmailAddress , which you can fill out to try and resolve an address. The names of these properties provide a clue to what values should go in them. Once you create the Addressee object and fill in one of the two properties just described, you can call the CheckName method, which takes either an LDAP path to your Active Directory or the file path to a Contacts folder contained in Exchange Server.

If the address can be resolved without ambiguous names, you can use some other properties on the Addressee object to get more information about the person. For example, the DirURL property returns either the Active Directory path or the file path to a contact, depending on whether you are validating the address against Active Directory or the Contacts folder. You can also call the ResolvedStatus property, which will contain for unresolved , 1 for resolved, or 2 for ambiguous. If you get an ambiguous address, you can use the AmbiguousNames property to return the Addressees collection, which is made up of Addressee objects that are ambiguous with respect to your current Addressee object.

The following sample shows how to use the properties and methods just described:

 Dim oAddressee As New CDO.Addressee      'Use the Display Name oAddressee.DisplayName = "Thomas Rizzo"      'Use the Active Directory      oAddressee.CheckName "LDAP://thomriznt5srv" If oAddressee.ResolvedStatus = 1 Then     'Resolved     MsgBox oAddressee.DirURL & vblf & oAddressee.EmailAddress End If      'Use the E-mail Address Dim oAddressee2 As New CDO.Addressee oAddressee2.EmailAddress = "thomriz@thomriznt5dom.microsoft.com"      'Use a Contact folder oAddressee2.CheckName "file://./backofficestorage/" & _     "thomriznt5dom.microsoft.com/public folders/contacts" If oAddressee2.ResolvedStatus = 2 Then     'Ambiguous     Set oAddressees = oAddressee2.AmbiguousNames     For Each otmpAddressee In oAddressees         'Scroll through the ambiguous addressees         MsgBox otmpAddressee.EmailAddress     Next End If 

CDO Calendaring Tasks

The new version of CDO provides some invaluable calendaring features. For example, if you have the correct permissions, you can open other users' calendars and perform operations on those calendars. Also, you can use public folder calendars. Plus, CDO directly supports iCalendar, which allows you to send meeting requests in a standard format that other clients can understand. The following sections discuss the most common tasks you'll perform with the CDO calendaring component.

Creating Appointments

Creating appointments using CDO is easy. You create a new CDO Appointment object, specify some properties, and use the DataSource interface on the Appointment object to save the appointment into a folder. This process is shown in the following sample:

 Dim oAppt As New Appointment sCalendarURL = "file://./backofficestorage/thomriz.com/MBX/User/Calendar/"      'Set the appointment properties oAppt.StartTime = #2/14/2003 12:30:00 PM# oAppt.EndTime = #2/14/2003 1:30:00 PM# oAppt.Subject = "Shop for Valentine's Day present!" oAppt.Location = "Gift Store" oAppt.TextBody = "Don't forget this year!!!!"      'Save the appointment oAppt.DataSource.SaveToContainer sCalendarURL 

Creating Meeting Requests and Checking Free/Busy Information

You can create a meeting request by creating an appointment and then adding the names of the desired attendees. When generating a meeting request, you can also retrieve the free/busy information for all attendees. The following code creates a meeting request, finds the first available slot of free/busy time for an attendee, and sends the meeting request to the attendee :

 Dim oAppt As New CDO.Appointment Dim oAttendee As CDO.Attendee Dim oAddressee As New CDO.Addressee      oAddressee.EmailAddress = "bobw@thomriznt5dom.extest.microsoft.com" 'Check the address against the local directory 'You could also specify any LDAP path oAddressee.CheckName ("LDAP://thomriznt5dom.extest.microsoft.com")      If oAddressee.ResolvedStatus = cdoResolved Then     'Create an hour meeting sometime on March 7th     'Get the free/busy information.     strFB = oAddressee.GetFreeBusy(CDate("3/7/2003 9:00:00 AM"), _             CDate("3/7/2003 5:00:00 PM"), 60)     'Returns a string of 0,1,2,3     '0 - Free, 1 - Tenative, 2 - Busy, 3 - OOF, 4 - No F/B data     'Find the first free spot by looking for 0     bFoundFB = False     For i = 1 To 8         'Look for a free spot         If Mid(strFB, i, 1) = 0 Then             dStart = DateAdd("h", i, CDate("3/7/2003 8:00 AM"))             dEnd = DateAdd("h", 1, dStart)             bFoundFB = True             Exit For         End If     Next          If bFoundFB = True Then         Dim oConfig As New CDO.Configuration         oConfig.Fields("http://schemas.microsoft.com/cdo/" & _                        "configuration/sendemailaddress") = _                        "thomriz@thomriznt5dom.extest.microsoft.com"         oConfig.Fields("http://schemas.microsoft.com/cdo/" & _                        "configuration/calendarlocation") = _                        "file://./backofficestorage/" & _                        "thomriznt5dom.extest.microsoft.com/" & _                        "MBX/thomriz/calendar"         oConfig.Fields.Update              oAppt.Configuration = oConfig         oAppt.StartTime = dStart         oAppt.EndTime = dEnd         oAppt.Subject = "Meeting"         oAppt.Location = "Your office"         oAppt.TextBody = "Meeting with you!"         Set oAttendee = oAppt.Attendees.Add         oAttendee.Address = oAddressee.EmailAddress         oAttendee.Role = cdoRequiredParticipant         Set oCalMsg = oAppt.CreateRequest         oCalMsg.Message.Send              strCalendarURL = "file://./backofficestorage/" & _                          "thomriznt5dom.extest.microsoft.com/" & _                          "MBX/thomriz/calendar"         'Save to organizer calendar         oAppt.DataSource.SaveToContainer strCalendarURL     End If End If 

Notice that the code uses the Addressee object. This object allows you to create and resolve addresses using LDAP with a directory server. Once the attendee is resolved, the free/busy information is retrieved for the attendee by using the GetFreeBusy method. This method takes the start time, the end time, and the interval in minutes that you want to break the free/busy information into.

The code then checks for the first time when the attendee is free. Next it creates an appointment, fills out the appropriate fields, and then calls the CreateRequest method. This method returns a CalendarMessage object. You can then call the contained Message object's methods, such as Send . Finally, the code saves the appointment to the calendar.

Another Way to Check Free/Busy Information

In addition to using CDO to check the free/busy information for a user on the server, you can leverage HTTP to access that information. Exchange Server provides a special URL that Outlook Web Access uses to check a user's free/busy data. The nice thing about this technique is that you are not limited by where your code runs. Because CDO requires you to run your code on the Exchange server, you can use this approach with the XMLHTTP or ServerXMLHTTP objects (discussed in the next chapter). The server will return XML that contains the overall free/busy status of all attendees aggregated and the individual free/busy status for each attendee. Then you must write code to parse the XML and display, calculate, or perform whatever functionality your application requires. Here is the URL you need to pass to the server to use this technique:

 http://SERVER/public/?Cmd=freebusy&start=ISOFORMATTEDDATE&end=ISOFORMATTED DATE&interval=30&u=SMTP:user@user.com 

As you can see, you go into the public vroot and pass a Cmd of freebusy . With that Cmd , you must pass the start date and time as a specially formatted ISO string such as yyyy-mm-ddThh:mm:ssTZOffset . For example, 2003-10-12T06:00:00-08:00 is a valid start and end date. You then specify an interval such as 30 or 60. This interval can be any interval you want. Exchange will return the free/busy information for the users who are using that interval as the default. Finally, you specify the SMTP address of the users you want to retrieve using the format u=SMTP:smtpaddressofuser . To return multiple users, pass in multiple u=SMTP:smtpaddressofuser parameters in your query. The following XML code is returned with a query of two people using the following URL:

 http://server/public/?Cmd=freebusy&start=2003-06-11T00:00:00-08:00&end=2003-06- 12T00:00:00-08:00&interval=10&u=SMTP:thomriz@domain.com&u=jwierer@domain.com      <a:response xmlns:a="WM">  <a:recipients>  <a:item>   <a:displayname>All Attendees</a:displayname>   <a:type>1</a:type>   <a:fbdata>0000000000000000000000000000000000002222222222220002222220000002222 22222222000000000000222222222000000000000000000000000000000000000000000000000</ a:fbdata>   </a:item> <a:item>   <a:displayname>Thomas Rizzo</a:displayname>   <a:email type="SMTP">thomriz@domain.com</a:email>   <a:type>1</a:type>   <a:fbdata>0000000000000000000000000000000000002222221111110002222220000002222 22222222000000000000222222222000000000000000000000000000000000000000000000000</ a:fbdata>   </a:item>  <a:item>   <a:displayname>Jeff Wierer</a:displayname>   <a:email type="SMTP">jwierer@domain.com</a:email>   <a:type>1</a:type>   <a:fbdata>0000000000000000000000000000000000000000002222220002222220000000001 11111000000000000000111222222000000000000000000000000000000000000000000000000</ a:fbdata>   </a:item>   </a:recipients>   </a:response> 

As you can see from the XML returned, an all attendees row is returned, which is the aggregate of all the free/busy information. Then individual item rows are returned for each user containing the displayname, e-mail address, type, and free/busy data. From this, you need to write code that will parse the XML and return whatever user interface or functionality your application requires. The following sample code shows you how to format a request, send that request using the XMLHTTP object, get back a response, and then parse that response using the XMLDOM:

    Dim oXMLHTTP As New MSXML2.XMLHTTP30 dtCurrentDay = Date dStartDate = dtCurrentDay dEndDate = dtCurrentDay + 1 dISODateStartFB = TurnintoUTCFB(DateValue(dStartDate), _                   FormatDateTime(TimeValue(dStartDate), 4)) dISODateEndFB = TurnintoUTCFB(DateValue(dEndDate), _                 FormatDateTime(TimeValue(dEndDate), 4))      strURL = "http://server/public/?Cmd=freebusy&start=" & _          dISODateStartFB & "&end=" & dISODateEndFB & _          "&interval=30&u=SMTP:thomriz@domain.com&u=SMTP:user@domain.com"      oXMLHTTP.Open "GET", strURL, False oXMLHTTP.setRequestHeader "Content-type", "text/xml" oXMLHTTP.setRequestHeader "translate", "t" oXMLHTTP.setRequestHeader "user-agent", _                           "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT)" oXMLHTTP.Send ("")      Set objXML = CreateObject("MSXML.DOMDocument") objXML.loadXML oXMLHTTP.ResponseText      bGetFBForThisNode = False Set objnodelist = objXML.getElementsByTagName("*") For i = 0 To (objnodelist.length - 1)     Set objnode = objnodelist.nextNode     If objnode.nodeName = "a:email" Then         'Check to see if the current user         If UCase(objnode.Text) = UCase("thomriz@domain.com") Then             bGetFBForThisNode = True         End If     End If     If bGetFBForThisNode = True Then         If objnode.nodeName = "a:fbdata" Then             'Get the FB             aFB = CStr(objnode.Text)         End If     End If Next      Function TurnintoUTCFB(dDate, strTime)     'Format should be "yyyy-mm-ddThh:mm:ssTZOffset"     strUTC = Year(dDate) & "-"          If Month(dDate) < 10 Then         strUTC = strUTC & "0" & Month(dDate) & "-"     Else         strUTC = strUTC & Month(dDate) & "-"     End If          If Day(dDate) < 10 Then         strUTC = strUTC & "0" & Day(dDate) & "T"     Else         strUTC = strUTC & Day(dDate) & "T"     End If          If Mid(strTime, 3, 1) <> ":" Then         'Must be X not 0X         strUTC = strUTC & "0" & strTime & ":00"     Else         strUTC = strUTC & strTime & ":00"     End If          'Add the timezone difference     iDiff = -8     If Len(iDiff) = 2 Then         'It's -8 or +8, not -08 or +08, add a zero         iDiff = Left(iDiff, 1) & "0" & Right(iDiff, 1)     End If          strUTC = strUTC & iDiff & ":00"          TurnintoUTCFB = strUTC End Function 

Creating Recurring Meeting Requests

To create a recurring meeting request, you simply add a RecurrencePattern object to the appointment and set some properties on it. If you want to get more complex, you can add an Exception object to the RecurrencePattern object to specify that a certain date be excluded from the meeting request. The following code shows how to create a simple recurring meeting request:

 Dim oAppt As New CDO.Appointment Dim oAttendee As CDO.Attendee Dim oAddressee As New CDO.Addressee Dim oRP As CDO.IRecurrencePattern      oAddressee.EmailAddress = "bobw@thomriznt5dom.extest.microsoft.com" 'Check the address against the local directory. 'You could also specify any LDAP path. oAddressee.CheckName ("LDAP://thomriznt5dom.extest.microsoft.com")           If oAddressee.ResolvedStatus = cdoResolved Then     Dim oConfig As New CDO.Configuration     oConfig.Fields("http://schemas.microsoft.com/cdo/" & _                    "configuration/sendemailaddress") = _                    "thomriz@thomriznt5dom.extest.microsoft.com"     oConfig.Fields("http://schemas.microsoft.com/cdo/" & _                    "configuration/calendarlocation") = _                    "file://./backofficestorage/" & _                    "thomriznt5dom.extest.microsoft.com/" & _                    "MBX/thomriz/calendar"     oConfig.Fields.Update          oAppt.Configuration = oConfig     oAppt.StartTime = dStart     oAppt.EndTime = dEnd     oAppt.Subject = "Meeting"     oAppt.Location = "Your office"     oAppt.TextBody = "Meeting with you!"     Set oRP = oAppt.RecurrencePatterns.Add("ADD")     'Make it weekly     oRP.Frequency = cdoWeekly     oRP.Interval = 1     oRP.Instances = 10          Set oAttendee = oAppt.Attendees.Add     oAttendee.Address = oAddressee.EmailAddress     oAttendee.Role = cdoRequiredParticipant     Set oCalMsg = oAppt.CreateRequest     oCalMsg.Message.Send           strCalendarURL = "file://./backofficestorage/" & _          "thomriznt5dom.extest.microsoft.com/MBX/thomriz/calendar"     'Save to organizer calendar     oAppt.DataSource.SaveToContainer strCalendarURL End If 

The code creates the RecurrencePattern object by using the RecurrencePatterns collection on the Appointment object. The Add method of this collection has only two values that you can pass to its parameter: ADD and DELETE . Once the new RecurrencePattern object has been added to the collection, the code sets the properties on the object to make the recurrence weekly, specifying only 10 recurrences. If you do not specify the instances, CDO will make the meeting recur indefinitely.

Creating Exceptions

You can create exceptions to your recurring meetings or appointments by using the CDO exceptions and the Exception object. I'll show a simple example of adding a single exception to a recurring appointment, but you can do more complex operations with the CDO Exception object. The following code sample uses the CDO Exceptions collection to add a new exception to a recurring meeting. You can also delete and modify exceptions using the CDO Exceptions collection.

 Set oException = oAppt.Exceptions.Add("Add") oException.StartTime = "5/2/2003 10:00 AM" oException.EndTime = "5/2/2003 11:00 AM" 

Responding to a Meeting Request

You can use CDO calendaring to accept, decline, or mark as tentative a meeting request. This operation is quite easy, so I'll just show you the code. This code sample assumes that the meeting request you want to process is in your Inbox and is already assigned to the oAppt object:

 Set oMsg = oAppt.Accept 'You can then modify the response message by modifying oMsg oMsg.Message.Send 'Save the appointment to your calendar oAppt.DataSource.SaveTo URLTOYOURCALENDAR 

Opening Other Users' Mailbox Folders

To open another user's folder or folders, all you need to do is pass the path to that folder to a Record object or to a query that returns an ADO recordset. The following example opens up Neil Charney's Calendar folder. To open another user's folder, you must have permissions on that user's folders.

 Dim oRecord as New ADODB.Record oRecord.Open "file://./backofficestorage/domain/MBX/neilc/calendar/" 

Working with Time Zones

One of the first issues you have to deal with when working with CDO calendaring functionality, and even when working with ADO, is the storage of dates in UTC format in Exchange. CDO offsets the UTC date to the local time zone specified in the CDO Configuration object. If you do not explicitly set a time zone, CDO will use the time zone of the machine. This functionality is different from how ADO handles time zones; ADO does no offset at all and returns the date in UTC format. You can run into some strange debugging issues if you forget this fact ”if you create event registrations or calendar appointments using ADO and then look at those items through CDO, you will see different dates.

Time zones play an important role in the Training application when setup creates event registrations for the survey and creates course notification events. To use ADO to create the event registrations so the events fire daily at 10 P.M. local time on the server, the application has to figure out the offset from 10 P.M. local time to UTC time. The following code does this by leveraging CDO to create an appointment at 10 P.M. local time. The code then retrieves that appointment using ADO, which returns the start time for the appointment at UTC time, not at 10 P.M. local time. The code then figures out the offset between UTC and local time on the server by using the DateDiff function. This information is used by the application event handlers to make sure the ADO query to find all items created in the last 24 hours finds those items with the correct date and time. You must figure out the UTC offset because Exchange stores the creation dates of items in UTC. Otherwise, you will not really be querying for items created in the past 24 hours in local time.

 'Figure out start time for timer events. 'Since the start time needs to be UTC, we need to figure 'out the local server time and then figure out the UTC 'time to tell the event to fire so that it is 10 PM.      Dim oAppt As CDO.Appointment Set oAppt = CreateObject("CDO.Appointment") oAppt.Subject = "Delete me" If DateDiff("n", Now, Date & " 10:00 PM") < 60 Then     If DateDiff("n", Now, DateAdd("h", 1, Date & " 10:00 PM")) <= 60 Then         'We're passing 10 PM for the current day         dDate = DateAdd("d", 1, Date)     Else         dDate = Date     End If Else     dDate = Date End If      oAppt.StartTime = DateValue(dDate) & " 10:00 PM" oAppt.EndTime = DateAdd("n", 60, oAppt.StartTime) strTime = TimeValue(oAppt.StartTime)      'Dump the appt into the root folder oAppt.DataSource.SaveToContainer strPath strdeletehref = oAppt.Fields("DAV:href").Value Set oAppt = Nothing 'Get it back Dim oDeleteRecord As New ADODB.Record oDeleteRecord.Open strdeletehref, , adModeReadWrite strNow = oDeleteRecord.Fields("urn:schemas:calendar:dtstart").Value      'Figure out the offset from UTC to the local time zone. 'This is needed for the survey notification event handler 'so that it knows how much to offset its query for items created 'during the current day. strDate = oDeleteRecord.Fields("urn:schemas:calendar:dtstart").Value      strLocDate = dDate & " " & strTime iDiff = DateDiff("h", strDate, strLocDate)      'Delete it oDeleteRecord.DeleteRecord 

CDO Contact Tasks

CDO implements a Person object for working with contacts in both the Exchange Server store and Active Directory. Using this object, you can create and query contact objects in Exchange Server and Active Directory. CDO handles all the property mapping between Exchange Server and Active Directory. The CDO Person object is very straightforward, so instead of telling you all the properties you can set on this object, I'll just show you some code samples.

Creating a Contact in Exchange Server

Creating a contact in Exchange Server is as easy as creating a CDO Person object, setting several properties, and then calling SaveToContainer . The following code shows these steps:

 Dim oPerson As New CDO.Person strContactURL = "file://./backofficestorage/thomriznt5dom." & _                 "extest.microsoft.com/public folders/group contacts/" oPerson.FirstName = "Thomas" oPerson.LastName = "Rizzo" oPerson.Company = "Microsoft" oPerson.WorkStreet = "1 Microsoft Way" oPerson.WorkCity = "Redmond" oPerson.WorkState = "WA" oPerson.WorkPostalCode = "98052" oPerson.Email = "thomriz@microsoft.com" oPerson.WorkPhone = "425 555 1212" oPerson.Email2 = "test@test.com"      'Save the Person object to the folder oPerson.DataSource.SaveToContainer strContactURL 

Creating a Contact in Active Directory

Creating a contact in Active Directory is similar to creating a contact in an Exchange Server folder. The only difference is that you provide an LDAP URL rather than a file URL, as shown here:

 strContactURL = "LDAP://thomriznt5srv/cn=tomrizzo,cn=users," & _                 "dc=extest,dc=microsoft,dc=com" oPerson.FirstName = "Thomas" oPerson.LastName = "Rizzo" oPerson.Company = "Microsoft" oPerson.WorkStreet = "1 Microsoft Way" oPerson.WorkCity = "Redmond" oPerson.WorkState = "WA" oPerson.WorkPostalCode = "98052" oPerson.Email = "thomriz@microsoft.com" oPerson.WorkPhone = "425 555 1212" oPerson.Email2 = "test@test.com"      'Save the Person object to the folder oPerson.DataSource.SaveToContainer strContactURL 

Saving Your Contact as a vCard

CDO allows you to access or create vCard information for your contacts. vCard is a standard way of describing contact information on the Internet. CDO vCard support allows you to send or save your contacts to any compliant vCard system. You get the vCard information by using the GetVCardStream method, which returns the information about a contact in vCard format to an ADO Stream object. The following code uses the GetVCardStream method to retrieve the vCard information and print it to the screen for a contact:

 Dim oPerson as New CDO.Person oPerson.DataSource.Open "file://./backofficestorage/domain/folder/name.eml" oPerson.GetVCardStream.SaveToFile "c:\vcard.txt" 

The output from running the preceding code, which is contained in the text file vcard.txt, is shown here:

 BEGIN:VCARD VERSION:2.1 N:Rizzo;Thomas FN:Thomas Rizzo ORG:Microsoft Corporation TITLE:Product Manager NOTE;ENCODING=QUOTED-PRINTABLE:=0D=0A TEL;WORK;VOICE:(425) 555-1212 ADR;WORK:;;1 Microsoft Way;Redmond;WA;98052;United States of America LABEL;WORK;ENCODING=QUOTED- PRINTABLE:1 Microsoft Way=0D=0ARedmond, WA 98052=0D=0AUnited States of America URL: URL:http://www.microsoft.com/exchange EMAIL;PREF;INTERNET:thomriz@microsoft.com REV:20030410T033803Z END:VCARD 

Interoperating with Outlook Properties

The good thing about CDO for Exchange is that it automatically sets some of the properties that enable seamless Outlook integration, such as making contacts that you create automatically appear in Outlook address books. However, there are some contact properties that CDO cannot set, and you have to resort to the MAPI equivalents to set these properties. One of these properties is the IM address property on a contact. The MAPI property set for contact items in Outlook is 00062004-0000-0000-C000-000000000046 . If you're using CDO 1.21, you must transpose this property, as in 0420060000000000C000000000000046 . Please check Knowledge Base article 298401 for more information. The property identifier for the IM address property is 0x8062 . The following code creates a new contact and uses the Fields collection of the CDO Person object to set the IM address of the contact:

 Dim oContact As New CDO.Person strURL = "file://./backofficestorage/thomriznt5dom2." & _          "extest.microsoft.com/public folders/contacts/" oContact.FirstName = "Tom" oContact.LastName = "Rizzo" oContact.Email = "thomriz@microsoft.com" oContact.FileAs = "Rizzo, Tom" 'Set the IM Address 'Propset is 00062004-0000-0000-C000-000000000046 'For CDO 1.21, this is transposed to 0420060000000000C000000000000046 'Property ID is 0x8062 oContact.Fields.Item("http://schemas.microsoft.com/" & _                      "mapi/id/{00062004-0000-0000-C000-" & _                      "000000000046}/0x00008062").Value = _                      "thomriz@microsoft.com" oContact.Fields.Update oContact.DataSource.SaveToContainer strURL 

CDO Folder Tasks

CDO provides a Folder object that allows you to create or access folders contained in the Exchange database. Using this object, you can retrieve the number of read and unread items contained in the folder. You will interact most with the Folder object's properties, which include the following: Configuration , ContentClass , DataSource , Description , DisplayName , EmailAddress , Fields , HasSubFolders , ItemCount , UnreadItemCount , and VisibleCount .

Most of these properties are straightforward. The only one that really needs explaining is the VisibleCount property. This property is the total number of hidden items in the folder. Hidden items are created in the associated Contents table. These items can be created by setting the DAV:ishidden property to True .

Creating a Folder

To create a folder using the CDO Folder object, you first create a CDO Folder object and then use the DataSource property to save the folder. The following example shows you how to create a folder using CDO:

 Dim oFolder As New CDO.Folder      oFolder.Description = "This is my folder" oFolder.ContentClass = "urn:content-classes:contactfolder" oFolder.Fields("http://schemas.microsoft.com/exchange"  & _                "/outlookfolderclass") = "IPF.Contact" oFolder.Fields.Update oFolder.DataSource.SaveTo "file://./backofficestorage/"  & _     "thomriznt5dom.extest.microsoft.com/public folders/my contact folder" 

Mail-Enabling Folders

With the addition of new top-level hierarchies, Exchange Server does not, by default, mail-enable folders in the new hierarchies. You can mail-enable the folders either through the administrative UI or programmatically. The latter approach is quite easy. All you do is use the IMailRecipient interface from the CDO for Exchange Management library. To retrieve the interface, you set a variable to the IMailRecipient interface. Then you set the value of that variable to be your CDO folder. Call the MailEnable method on the folder, and then make the changes to the properties on the IMailRecipient interface. For example, you might set up the alias, establish the SMTP e-mail address, and decide whether the new address should be hidden from the address book. Once you finish setting the properties, you can just save the folder back to the Exchange database. Remember that you will need to open the folder in the read/write mode using the CDO Folder object, as shown in the following code:

 Dim oFolder As New CDO.Folder Dim oRecip As CDOEXM.IMailRecipient      oFolder.DataSource.Open "file://./backofficestorage/thomriznt5dom." & _                         "extest.microsoft.com/public folders/" & _                         "my contact folder", , adModeReadWrite Set oRecip = oFolder oRecip.MailEnable oRecip.SMTPEmail = "contacts@domain.com" oRecip.HideFromAddressBook = False oRecip.Alias = "My Contacts Folder" oFolder.DataSource.Save 

What About Tasks?

As you might have noticed, there is no CDO task object. However, for simple task functions such as creating, modifying, or deleting tasks, you can use task schema properties to perform these operations. You can attempt to code task recurrence and task assignment, but this is much harder and can easily break Outlook if done incorrectly. For this reason, these functions are not shown in the following code because they are complex and prone to breaking Outlook:

 'Core Task Properties Const cdoTaskStartDate = "http://schemas.microsoft.com/mapi/id/" & _     "{00062003-0000-0000-C000-000000000046}/0x00008104" 'PT_SYSTIME Const cdoTaskDueDate = "http://schemas.microsoft.com/mapi/id/" & _     "{00062003-0000-0000-C000-000000000046}/0x00008105" 'PT_SYSTIME Const cdoTaskPercentComplete = "http://schemas.microsoft.com/mapi/id/" & _     "{00062003-0000-0000-C000-000000000046}/0x00008102" 'PT_DOUBLE Const cdoTaskComplete = "http://schemas.microsoft.com/mapi/id/" & _     "{00062003-0000-0000-C000-000000000046}/0x0000811c" 'PT_BOOLEAN Const cdoTaskDateCompleted = "http://schemas.microsoft.com/mapi/id/" & _     "{00062003-0000-0000-C000-000000000046}/0x0000810f" 'PT_SYSTIME Const cdoTaskStatus = "http://schemas.microsoft.com/mapi/id/" & _     "{00062003-0000-0000-C000-000000000046}/0x00008101" 'PT_LONG Const cdoTaskState = "http://schemas.microsoft.com/mapi/id/" & _     "{00062003-0000-0000-C000-000000000046}/0x00008113" 'PT_LONG Const cdoTaskActualEffort = "http://schemas.microsoft.com/mapi/id/" & _     "{00062003-0000-0000-C000-000000000046}/0x00008110" 'PT_LONG Const cdoTaskEstimatedEffort = "http://schemas.microsoft.com/mapi/id/" & _     "{00062003-0000-0000-C000-000000000046}/0x00008111" 'PT_LONG Const cdoTaskMode = "http://schemas.microsoft.com/mapi/id/" & _     "{00062003-0000-0000-C000-000000000046}/0x00008518" 'PT_LONG      'Common Props Const cdoBillingInformation = "http://schemas.microsoft.com/mapi/id/" & _     "{00062008-0000-0000-C000-000000000046}/0x00008535" 'PT_UNICODE Const cdoCompanies = "http://schemas.microsoft.com/mapi/id/" & _     "{00062008-0000-0000-C000-000000000046}/0x00008539" 'PT_MV_UNICODE Const cdoMileage = "http://schemas.microsoft.com/mapi/id/" & _     "{00062008-0000-0000-C000-000000000046}/0x00008534" 'PT_UNICODE      'Reminder Props Const cdoReminderDelta = "http://schemas.microsoft.com/mapi/id/" & _     "{00062008-0000-0000-C000-000000000046}/0x00008501" 'PT_LONG Const cdoReminderNextTime = "http://schemas.microsoft.com/mapi/id/" & _     "{00062008-0000-0000-C000-000000000046}/0x00008560" 'PT_SYSTIME Const cdoReminderTime = "http://schemas.microsoft.com/mapi/id/" & _     "{00062008-0000-0000-C000-000000000046}/0x00008502" 'PT_SYSTIME Const cdoReminderSet = "http://schemas.microsoft.com/mapi/id/" & _     "{00062008-0000-0000-C000-000000000046}/0x00008503" 'PT_BOOLEAN      Private Sub Form_Load()     Dim TaskItem As New ADODB.Record          'Modify this URL to point to an item in your task folder     TaskItem.Open "file://./backofficestorage/domain.com/MBX/users/" & _                   "tasks/mytask.eml", , adModeReadWrite, adCreateOverwrite     TaskItem.Fields.Item("DAV:contentclass") = "urn:content-classes:task"     TaskItem.Fields.Item("http://schemas.microsoft.com/exchange/" & _                          "outlookmessageclass").Value = "IPM.Task"          'Set core Task props     TaskItem.Fields.Item(cdoTaskStartDate).Value = Now     TaskItem.Fields.Item(cdoTaskDueDate).Value = Now     TaskItem.Fields.Item(cdoTaskActualEffort).Value = 36000 'Minutes.     TaskItem.Fields.Item(cdoTaskEstimatedEffort).Value = 72000 'Minutes.          'Set additional Props     TaskItem.Fields.Item("urn:schemas-microsoft-com:office:office" & _                          "#Keywords").Value = Array("my tasks", "Exchange")     TaskItem.Fields.Item("urn:schemas:httpmail:textdescription").Value = _                          "Description goes here!!"     TaskItem.Fields.Item("urn:schemas:httpmail:subject").Value = _                          "Tasks Rock!!"     TaskItem.Fields.Item(cdoBillingInformation).Value = _                          "Microsoft Corporation"     TaskItem.Fields.Item(cdoCompanies).Value = Array("Expedia", "Microsoft")     TaskItem.Fields.Item(cdoMileage).Value = "120"     TaskItem.Fields.Append cdoTaskState, adInteger, , , 1     TaskItem.Fields.Update          'Set the PercentComplete and Task Status together for Unassigned Tasks     'TaskItem.Fields.Append cdoTaskPercentComplete, adDouble, , , "0.0"     'TaskItem.Fields.Append cdoTaskStatus, adInteger, , , 0     'TaskItem.Fields.Update     'MsgBox "The Task Status is Unassigned!"          'Set the PercentComplete and Task Status together when updating     'Task Status     'TaskItem.Fields.Append cdoTaskPercentComplete, adDouble, , , "0.5"     'TaskItem.Fields.Append cdoTaskStatus, adInteger, , , 1     'TaskItem.Fields.Update     'MsgBox "The Task Status was Updated!"          'Set the PercentComplete, Task Status, Task Complete and     'TaskDateCompleted together     'TaskItem.Fields.Append cdoTaskPercentComplete, adDouble, , , "1.0"     'TaskItem.Fields.Append cdoTaskStatus, adInteger, , , 2     'TaskItem.Fields.Item(cdoTaskComplete).Value = True     'TaskItem.Fields.Item(cdoTaskDateCompleted).Value = Now     'TaskItem.Fields.Update     'MsgBox "The Task Status was Updated!"          TaskItem.Close End Sub 

Interoperability Between CDO 1.21 and CDO for Exchange

Sometimes you might want to get the entry ID of a message using CDO for Exchange and pass that entry ID to CDO 1.21 to open the message via CDO. You can use the MAPI property identifier for the entryID property to retrieve the entry ID via CDO or ADO. The following code shows how to retrieve the entry ID using CDO for Exchange and then pass that property to CDO 1.21:

 strMsgURL = "file://./backofficestorage/thomriznt5dom2.extest." & _             "microsoft.com/public folders/folder/message.eml" Set objCDOEXMsg = CreateObject("CDO.Message") objCDOEXMsg.DataSource.Open strMsgURL      'Convert the byte array in MAPI property PR_ENTRYID to a string. strIDProp = "http://schemas.microsoft.com/mapi/proptag/0x0fff0102" byteArray = objCDOEXMsg.Fields(strIDProp).Value      For Each singleByte In byteArray     byteString = Hex(singleByte)     If Len(byteString) < 2 Then         byteString = "0" & byteString     End If     strEntryID = strEntryID & byteString Next      Set objCDOEXMsg = Nothing      'Log on via CDO 1.21. Set objSession = CreateObject("MAPI.Session") objSession.Logon 'Get the message based on the message entry ID. Set objMsg = objSession.GetMessage(strEntryID) MsgBox "Message Subject: " & objMsg.Subject      objSession.Logoff 
Note  

One question developers always ask about CDO is how to programmatically send encrypted and/or digitally signed mail. Rather than cover this in detail here, I'll just tell you that you can do this by programming to the cryptography APIs in Windows. The secure mail sample application, which you can find on the companion content for this book, shows you how to do this with CDO for Exchange.




Programming Microsoft Outlook and Microsoft Exchange 2003
Programming MicrosoftВ® OutlookВ® and Microsoft Exchange 2003, Third Edition (Pro-Developer)
ISBN: 0735614644
EAN: 2147483647
Year: 2003
Pages: 227
Authors: Thomas Rizzo

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net