Programming Microsoft Outlook and Microsoft Exchange 2003, Third Edition [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Programming Microsoft Outlook and Microsoft Exchange 2003, Third Edition [Electronic resources] - نسخه متنی

Thomas Rizzo

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
توضیحات
افزودن یادداشت جدید

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.


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.


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 methodThe 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 methodYou 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 methodThe 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 methodThe 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 methodThe 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 methodThe 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 0 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>000000000000000
0000000000000000000002222222222220002222220000002222
222222220000000000002222222
22000000000000000000000000000000000000000000000000</
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>00000000000000
00000000000000000000002222221111110002222220000002222
2222222200000000000022222
2222000000000000000000000000000000000000000000000000</
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>00000000000000
00000000000000000000000000002222220002222220000000001
11111000000000000000111222
222000000000000000000000000000000000000000000000000</
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.


/ 227