Make Your E-mail System Love Script

Andrew Clinick
Microsoft Corporation

September 11, 2000


XML and HTTP Support
Outlook Web Access
Getting at the Data
Sending Mail

Exchange 2000 marks a major step forward for e-mail systems, both in terms of capabilities for the end user and, perhaps most importantly, scripters. The Exchange 2000 team spent a lot of time developing the product so that it can be used as a development platform—in particular, for building Web applications. Because script is such a key part of Web development, I thought I'd take a brief tour of what's offered in Exchange 2000 from a scripter's perspective. When I started, I figured this would be a pretty short article, but the more I dug into Exchange, the more I discovered just how scriptable it really is.

XML and HTTP Support

A number of advances in Exchange 2000 make it a scriptable application, but none more so than the addition of XML and HTTP. Nearly all the functionality within Exchange 2000 is provided via XML and HTTP, so you can take advantage of all the new features simply by querying the Exchange server for some XML. Once you have that XML, you can use the XML parser to extract the data.

Exchange provides a rich XML schema that describes all of its internal data structures (mail messages, calendar items, notes, etc.). Querying the server for this information couldn't be easier: Make an HTTP request to the relevant mailbox. Each mailbox on the server is exposed via HTTP under the exchange v-root, so the URL to query my inbox is http://mymailserver/exchange/andrewc/inbox.This works great for internal applications.

Accessing Exchange over the Internet becomes even more interesting. Exchange provides the ability to access your e-mail system via a secure, SSL-encrypted connection, so it's possible to have a consolidator server on the Internet that takes requests for all users of the Exchange servers running on the intranet and pass on the appropriate requests.

From a script perspective, this is great, because it allows you to develop one solution against the Exchange XML format, regardless of where the script is being run. Providing the XML via HTTP makes negotiating with any firewalls much simpler, because pretty much every firewall allows an HTTP request over port 80.

JScript and Visual Basic Scripting Edition (VBScript) don't provide any inherent mechanism for accessing XML or HTTP. Luckily, the XML team provide a great object, the XMLHTTP object, that allows just this. XMLHTTP allows you to request data via either an HTTP GET or a POST from any script host. It's remarkably easy to create an HTTP request in just a few lines of code:

    ' Create an instance of the xmlHTTP object
    Set oXMLHTTP = CreateObject("microsoft.xmlhttp")
    ' Open a request to a particular url
    oXMLHTTP.Open "PROPFIND", strURL,false
    ' Set the header type - we want XML
    oXMLHTTP.setRequestHeader "Content-type:", "text/xml"
    oXMLHTTP.setRequestHeader "Depth", "1"
    ' Send the request
    oXMLHTTP.send ("")

When the request has been made to the server, it returns an object that has the XML tree available as a standard XML DOM object. The XML DOM provides a mechanism for traversing the XML document returned. Since XMLHTTP is an object, it can be used anywhere script is available: HTML, Active Server Pages (ASP), Windows Script Host, or any application that hosts script. This means that you can start to integrate Exchange functionality into any application that hosts script.

To illustrate the use of XML and Exchange, I've written a simple HTML page that queries a URL that you provide, extracts the information from the returned XML, and transforms that data into HTML. Rather than use XSL, which would be a prime candidate for this type of task, I've used script to show how you can use the XML DOM to easily extract data from the XML and use the Internet Explorer object model to transform the data into usable HTML. The example uses the simple XMLHTTP code from above to query the Exchange server. Once the query has been returned, the example looks for the response nodes in the XML. Each response node equates to an item in the data store that has just been queried. For example, if you queried the mailbox http://mymailserver/exchange/andrewc, there would be a response node for every folder in the mailbox. Each response contains quite a bit of data; to keep it simple, the example extracts only two pieces of data, the HREF of the item (more on that later) and the displayname of the item:

    ' Get all the response nodes in the XML tree
    set myXML = oXMLHTTP.ResponseXML.documentElement.selectNodes("a:response")

    ' Iterate through each node
    for each node in myXML
        ' Update the innerhtml with href (it's the first childnode)
        ' and the displayname node
      divshow.innerhtml = divshow.innerhtml & _
            "<a href='" & node.childNodes(0).Text & "' target='_blank'>" _
            & node.selectNodes("a:propstat/a:prop/a:displayname")(0).Text _
            & "</a><br>"

To get the response nodes in the XML tree returned by the request, the code uses the selectNodes method, which takes any valid XSL pattern as its argument. XSL patterns allow considerable flexibility, including the ability to limit the return based on a search pattern.

Once all the response nodes have been returned, the code iterates through the collection of node objects. Each node object has a full XML object model to query for any data contained in the node. To get the HREF, I've used the childNodes collection passing in 0 to the default Items method. The HREF is always going to be the first node within the response, so it seemed reasonable to hard code this. To get the displayname information, the code needs to traverse a few more levels in the XML tree. Rather than write code to do this, the script uses an XSL pattern passed into the selectNodes method on each node. There will always be only one instance of displayname, so the script passes in 0 to the default Items method on the returned collection from selectNodes. This makes the code shorter and, I hope, somewhat easier to read.

Once the data has been retrieved, the script builds up HTML to display something meaningful to the user. In this case, it builds an <A> element around the displayname, with an HREF set to the HREF returned in the XML. The result is a Web page that displays a list of items for the requested URL, with each item being a hyperlink that, when clicked on, will display the Outlook Web access HTML page for the selected item.

Outlook Web Access

The Exchange group took the XML and HTTP development strategy to heart when it started to develop the Web-based interface to Exchange 2000—and the result is Outlook Web Access (OWA). OWA provides a scalable user interface that works on older browsers, but also takes advantage of the DHTML capabilities of Internet Explorer 5.5. The team achieved this by building the application on top of the XML provided by Exchange. If a user navigates to OWA with an older browser, all the transformation is done on the server via a series of Active Server Pages. The pages use the XML DOM to do the transformation, but rather than send down the XML, the server sends only the final HTML. Any interactivity is achieved via server round trips and page refreshes.

If the user is using Internet Explorer 5.5, the capabilities for dealing with XML and providing a rich user experience go up considerably, and OWA takes full advantage of this. The Internet Explorer 5.5 pages provide a user experience that is close to that of the full Outlook Windows client, including the ability to drag and drop items. The key to the interactivity is not just the great DHTML support in Internet Explorer 5.5, but also the ability to query the server for data without having to refresh the Web page. This is done via the XMLHTTP object, just as in my simple example.

OWA has been designed to be as modular as possible, and to provide as rich a scriptable interface as possible when used with Internet Explorer 5.5. To make OWA modular, the Exchange team made each UI element callable via a simple HTTP GET. This means that you can include all the great OWA capabilities in your Web application by including the correct URL in a frame or iframe in your application. For example, if you wanted to show your user's calendar on your Web page, you would create a frame and set the source to be http://mymailserver/exchange/username/calendar. The calendar would show up with all the flexible UI options that are available within OWA.

(Click thumbnail to view larger image.)

This provides considerable flexibility in your application development, because you can get all the UI flexibility you'd expect from Outlook within your application without having to go to the trouble of parsing the XML. To make the OWA Web components even more capable, you can control the view of the component by passing in a cmd argument to the HTTP request. For example, if you want to get the great Outlook bar OWA provides into your application, just add the cmd=navbar argument to your URL request. OWA does the rest for you.

Getting at the Data

In the past, it's been difficult to get hold of the data stored in an Exchange server by using script. You could use Collaborative Data Objects (CDO), and that was okay—but now you can really query the Exchange store as if it were a database. This allows some great script solutions in ASP and WSH scripts, because you can integrate your Exchange information as if it were stored in a normal SQL database.

Simply create an ADO connection, then set the connection provider to be Exoledb.DataSource to ensure that ADO uses the Exchange OLEDB provider. Once you've told ADO to use Exchange, you can query the data via the URL format described earlier—so http://mymailserver/exchange/username/inbox will retrieve the inbox. To get at the data, open the connection using the URL for the data you want. Here's a quick example that will get all posts by me into the reports folder in the Exchange public folder store.

'Finds items from a sender
'Passes search result recordset to DoResults (see Enumerating Results)

'Create connection object
Set Conn = CreateObject("ADODB.Connection")
Conn.Provider = "Exoledb.DataSource"

'URL for connection object
'is at the virtual directory root
cURL = "http://mymailserver/public"

Conn.Open cURL

'Relative URL is the folder to search
relURL = "Reports"

sender = "Andrew Clinick"

FindMsgsFrom sender, relURL, Conn

Sub FindMsgsFrom(sender, relURL, Conn)

     Const adErrNoCurrentRecord = 3021

     Set rs = CreateObject("ADODB.Recordset")

     'Construct the SQL query
     strQ = "SELECT ""urn:httpmail:subject"" "
     strQ = strQ & "FROM """ & relURL & """ "

     '* ------
     '* A shallow traversal is the default scope. To explicitly
     '* specify a shallow traversal, the code would be:
     '* 'strQ = strQ & "FROM scope('shallow traversal of """ & strURL & """ ')"

     strQ = strQ & "WHERE ""urn:schemas:httpmail:sendername"" = '" & sender & "'"

     Rs.Open strQ, Conn

     'If empty recordset, return error
     'If successful, call DoResults routine passing the recorset
     If Rs.EOF = True Then
          On Error Resume Next
          Err.Raise adErrNoCurrentRecord
          Response.Write "No items found, run another query."
          Response.Write "Success! Found " & Rs.RecordCount
          DoResults Rs
     End If

End Sub

In addition to ADO and XML access to your data, Exchange 2000 provides one other flexible mechanism: the Exchange Installable File System (ExIFS to its friends). This allows you to access your Exchange store as though it were a mapped network drive. For example, you might have all your users map their M: drives to their Exchange store using a WSH logon script. Once you've connected to the store, you can access it just as if it were a "normal" file share. This means your users can access it via Windows Explorer. More interestingly, you can use FileSystemObject, which ships as part of the Windows Script Runtime, to get at all the folders and items within the store because the ExIFS responds to the Win32 disk access APIs that FileSytemObject uses. To show all the folders in your Exchange e-mail store, try the following code:

WScript.Echo (ShowFolderList("m:"))

function ShowFolderList(folderspec)

   var fso, f, fc, s;
   fso = new ActiveXObject("Scripting.FileSystemObject");
   f = fso.GetFolder(folderspec);
   fc = new Enumerator(f.SubFolders);
   s = "";
   for (; !fc.atEnd(); fc.moveNext())
      s += fc.item();
      s += "<br>";

Sending Mail

Being able to query and display Exchange data is great—but chances are that you will want to use Exchange to send mail to your users. Luckily, CDO make this simple to do, and in Exchange 2000, the object model of CDO has been made even easier. To illustrate, here's a quick example of how to send mail using CDO. This is a simple program that sends a basic e-mail, but it's really only four lines of code to send mail.

' Declare and create an instance of CDO.Message
Dim iMsg
Set iMsg = CreateObject("CDO.Message")

' Using the CDO instace
With iMsg
     ' Set who the email is going to
     .To = ""
     ' Set the subject
     .Subject = "A short message"
     ' Set the Text of the message - I could have used the HTMLBody property to send
     ' HTML e-mail
     .TextBody = "Text of this short message."
     ' Send the mail
End With


Exchange 2000 extends the capabilities of script and e-mail significantly. The more you dig into the Exchange 2000 SDK, the more you'll find. When I started writing this article, I thought I had a pretty good handle on Exchange and script, but the depth of the SDK never ceased to amaze me; there's a huge amount of capabilities available. I've only really scratched the surface of what's possible, because I haven't even started to deal with Calendaring, contacts, and workflow, all of which are available to you as a script author when using Exchange 2000. I highly recommend the Exchange 2000 Developer Center on MSDN Online for more details on script and Exchange 2000.

Andrew Clinick is a program manager in the Microsoft Script Technology group, so chances are, if there's script involved, he's probably had something to do with it. He spends most of his spare time trying to get decent rugby coverage on U.S. television and explaining cricket to his American colleagues.