Quick and Dirty UNIX Shell Scripting with EWS

One of the great advantages of Exchange Web Services (EWS) is that you can write applications on just about any platform that can make HTTP requests. This characteristic of web services means that you can integrate EWS in to just about every application or environment you can imagine. This aspect of web services was brought home to me the other day while using my Mac OS X laptop, which I have to test Microsoft Entourage Exchange Web Services Edition Beta against Exchange. I wanted to quickly find the phone number for a colleague. I knew I could find the information in Entourage, but I wanted to be able to get the information without leaving my terminal window, so I dug deep in my memory to my first programming project as a 10 year old on a Unix workstation writing shell scripts (yes, that is how my parents kept me occupied).

First, I needed a suitable utility to make an HTTP request, and preferably one that could perform Windows Integrated authentication, which is the default for EWS. Curl is a great little tool for quickly issuing requests; it is supported cross-platform and has an NTLM authentication stack built in. This cool post shows how to use it for building a Mac OSX Widget that works against EWS. I extracted the necessary line and modified them for our use:

curl -s -u username@contoso.com:password -L https://servername/ews/exchange.asmx -d "$DATA" -H "Content-Type:text/xml" --ntlm

 

Here is a breakdown of the parameters and their function:

–s parameter tells curl to run silently

 -u allows me to pass UPN and password on the command line

-L allows us to specify the URL

-d allows me to specify the body of the request should come from a variable called $DATA

 -H allows me to set the content header which is required

 –ntlm allows me to specify that Curl should try ntlm auth (--anyauth doesn’t work)

The request is then customized for the alias I want by piping the XML for the resolve names request through sed so that I can replace the alias to resolve with the alias I want to resolve as an argument to the utility that I’m going to call resolvename.

DATA=$(sed 's/alias/'$1'/' resolvenames.txt)

 

resolvenames.txt

<?xml version="1.0" encoding="utf-8"?>

<soap:Envelope xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"

               xmlns:xsd="https://www.w3.org/2001/XMLSchema"

               xmlns:soap="https://schemas.xmlsoap.org/soap/envelope/"

      xmlns:t="https://schemas.microsoft.com/exchange/services/2006/types">

  <soap:Body>

    <ResolveNames xmlns="https://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="https://schemas.microsoft.com/exchange/services/2006/types"

                  ReturnFullContactData="true">

      <UnresolvedEntry>alias</UnresolvedEntry>

    </ResolveNames>

  </soap:Body>

</soap:Envelope>

Getting this far is pretty easy and you are left with a simple utility which takes in an alias as a parameter, formats the request with the alias, and makes the request. You can now take this output and pipe it to some utility which can consume it, ideally one that can natively handle the output xml.

<?xml version="1.0" encoding="utf-8"?>

<s:Envelope xmlns:s="https://schemas.xmlsoap.org/soap/envelope/">

  <s:Header>

    <h:ServerVersionInfo MajorVersion="8" MinorVersion="1" MajorBuildNumber="200" MinorBuildNumber="1" xmlns:h="https://schemas.microsoft.com/exchange/services/2006/types" xmlns="https://schemas.microsoft.com/exchange/services/2006/types" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="https://www.w3.org/2001/XMLSchema"/></s:Header><s:Body xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="https://www.w3.org/2001/XMLSchema">

      <m:ResolveNamesResponse xmlns:m="https://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="https://schemas.microsoft.com/exchange/services/2006/types">

        <m:ResponseMessages>

          <m:ResolveNamesResponseMessage ResponseClass="Success">

            <m:ResponseCode>NoError</m:ResponseCode>

            <m:ResolutionSet TotalItemsInView="1" IncludesLastItemInRange="true"><t:Resolution>

          <t:Mailbox>

                <t:Name>Jason Henderson</t:Name>

                <t:EmailAddress>jason@contoso.com</t:EmailAddress>

                <t:RoutingType>SMTP</t:RoutingType>

                <t:MailboxType>Mailbox</t:MailboxType>

              </t:Mailbox>

              <t:Contact>

                <t:Culture>en-US</t:Culture>

                <t:DisplayName>Jason Henderson</t:DisplayName>

                <t:GivenName>Jason</t:GivenName

                ><t:CompanyName>Contoso</t:CompanyName>

                <t:EmailAddresses>

                  <t:Entry Key="EmailAddress1">smtp:jason@contoso.com</t:Entry>

                    </t:EmailAddresses>

                <t:PhoneNumbers>

                  <t:Entry Key="BusinessPhone">555-5555</t:Entry></t:PhoneNumbers>

                <t:ContactSource>ActiveDirectory</t:ContactSource>

                <t:Department>Web Services</t:Department>

                <t:JobTitle>SENIOR PROGRAM MANAGER LEAD</t:JobTitle>

                <t:Manager>Bob Manager</t:Manager>

                <t:OfficeLocation>55/5555</t:OfficeLocation>

                <t:Surname>Henderson</t:Surname>

              </t:Contact></t:Resolution>

            </m:ResolutionSet>

          </m:ResolveNamesResponseMessage>

        </m:ResponseMessages>

      </m:ResolveNamesResponse>

    </s:Body>

</s:Envelope>

Since I did shell scripting before XML parsing was in high demand I don’t have any good XML parsing tools up my sleeve, just good old awk and sed. After a couple of false starts I dropped a mail to my good friend John Muster, author of Unix Made Easy (always at my side when I do any kind of shell scripting) and asked for his help. He quickly obliged with a quick and dirty XML parser using expr that we both admit is ugly and incomplete, but works well enough for parsing out office location, phone numbers, job titles, and any other standard field. The complete listing of the utility I call resolvename is below.

resolvename

DATA=$(sed 's/alias/'$1'/' resolvenames.txt)

curl -s -u jasonhen@contoso.com:password -L https://servername/ews/exchange.asmx -d "$DATA" -H "Content-Type:text/xml" --ntlm | tr -d '<:>' \

| tr ' ' '+' > /tmp/output$$

expr `cat /tmp/output$$` : '.*'$2'\(.*\)/\t'$2 > /tmp/selection$$

tr + ' ' < /tmp/selection$$

rm /tmp/output$$ /tmp/selection$$

The usage for the utility is: resolvename <alias> <xmlnode>, so for example “resolvename jason JobTitle” returns “Senior Program Manager Lead” or “resolvename jasonhen OfficeLocation” returns my building and office number. Pretty slick if I do say so myself.

Warnings – This code is not even prototype quality. Among numerous other shortcomings, no error codes are handled, and XML parsing only works properly with elements that occur once and have no attributes. If I were going to write a production quality application, I’d probably choose a language like php, python, or perl which have nice xml parsing stacks—but for something quick and dirty that works anywhere that you have curl and a bash environment this fits the bill.