2013/04/19

Exchange Web Services is better than WebDAV...

(...but SOAP still sucks!)

My mind is rambling again.  Fair warning - this post likely lacks coherency, context, critical details and color.  A pint of scotch (or two) to help you through is recommended.


In late 2011 I found myself filling out another I-9 after my last company shuttered the domestic developers in favor of outsourcing.  While my last boss went on an all-expenses paid "trip" to the Phillipines, I found a new position slinging PHP and used my severance "bonus" to go on a cruise.  Turned out to be a pretty sweet deal all-around.

The new company has an immediate need for some automated services to pull emails from various accounts, parse them and push the data to a third-party BMS.  Having done a little of this back in 2005, and in consideration of the companies IT department still chugging along with Exchange 2003, I dusted off some WebDAV tutorials and got to work.

Fast forward to later the next year, when the big chief decides it's time to migrate to Exchange 2007 - a little due-diligence and I discover that the WebDAV service, and around ten or twelve automated services, will be dust in the wind once the upgrade is completed.  Looking around the web for alternative solutions to implement, Zarafa's MAPI solution looks...well, like a giant migraine to be honest.  But this new Exchange Web Services sounds pretty slick, and there's even a PHP implementation that just graduated from Google Code to github, is referenced on Stack Overflow, and looks to have a fair user-base that I can look up if I run into any difficulties.  So I make a git-clone and start migrating one of the automated processes over.

First thing about php-ews - it's actually pretty sweet.  James Armes has put no small amount of effort in writing a cohesive toolkit that nearly encompasses the entirety of EWS and, if you know what you are doing, makes implementation trivial.  Extensive, functional, and approached with a goal of community contribution.  Those are the pros.

Unfortunately there are handful of cons, some trivial and some not so much.  For one the documentation...well, there isn't much and what has been prepared kinda sucks.  Granted the project is neatly documented with phpDoc, so spinning up your own reference API isn't the hardest thing to do, but I cannot seem to find an online instance anywhere.  Here's a short sample:

/** * Function Description * * @param UpdateDelegateType $request * @return UpdateDelegateResponseMessageType */public function UpdateDelegate($request){    $this->initializeSoapClient();    $response = $this->soap->{__FUNCTION__}($request);
    return $this->processResponse($response);}
/** * Function Description * * @param UpdateFolderType $request * @return UpdateFolderResponseType */public function UpdateFolder($request){    $this->initializeSoapClient();    $response = $this->soap->{__FUNCTION__}($request);
    return $this->processResponse($response);}
/** * Function Description * * @param UpdateItemType $request * @return UpdateItemResponseType */public function UpdateItem($request){    $this->initializeSoapClient();    $response = $this->soap->{__FUNCTION__}($request);
    return $this->processResponse($response);}

So...this kinda sucks.  There's a certain sensibility about it (although a proper usage of the magic method __call() could have cut this down from 300+ lines to less than 50), however to get a feel for this library you're stuck with Reading the Source, Luke, which every programmer should do eventually, but a pretty poor way of winning hearts and minds.

EWS has some significant differences from ye olde WebDAV.  For example here's the WebDav approach for marking an email as "read":

$this->request_xml = '<?xml version="1.0"?><d:propertyupdate xmlns:d="DAV:" xmlns:hm="urn:schemas:httpmail:"><d:set><d:prop><hm:read>1</hm:read></d:prop></d:set></d:propertyupdate>'; 
if (!$sock = @fsockopen( "https://$server", 80, $errno, $errstr )){    throw new \Exception( 'Could not open connection. Error '.$errno.': '.$errstr );}
$this->headers["Host"] = $server.":".$port;$this->headers["Content-Length"] = strlen($this->request_xml);
$request = "{$this->verb} $path HTTP/1.0\r\n";
// append headers followed by header EOLforeach ($this->headers as $key=>$value){    $request .= $key.": ".$value."\r\n";}$request .= "\r\n";
// append the response for POST or XML requests followed by response EOL$request .= $this->request_xml . "\r\n";if (fwrite($sock, $request) === false){    fclose($sock);    throw new \Exception( "Error writing request to socket" );}return $sock;

Manual socket manipulation, whoo!  And here is how you do it with php-ews:
$request = new EWSType_UpdateItemType();
$request->SendMeetingInvitationsOrCancellations = 'SendToNone';
$request->MessageDisposition = 'SaveOnly';
$request->ConflictResolution = 'AlwaysOverwrite';
$request->ItemChanges = array();
// Build out item change request.
$change = new EWSType_ItemChangeType();
$change->ItemId = new EWSType_ItemIdType();
$change->ItemId->Id = $msgid;
$change->ItemId->ChangeKey = $modkey;
// Build the set item field object and set the item on it.
$field = new EWSType_SetItemFieldType();
$field->FieldURI = new EWSType_PathToUnindexedFieldType();
$field->FieldURI->FieldURI = "message:IsRead";
$field->Message = new EWSType_MessageType();
$field->Message->IsRead = true;
$change->Updates->SetItemField[] = $field;
$request->ItemChanges[] = $change;
$ews = new ExchangeWebServices( $uri, $u_id, $u_auth );
$response = $ews->UpdateItem($request);
Other than the onerous Microsoft crap (apparently you have to update both the Message and the field-meta-data) this is a fairly clean solution.  php-ews will translate this into a SOAP request, schick it over to the Exchange server and *poof* the message is now read.  You don't even have to juggle port assignments.  

And if you are intimately familiar with both the Exchange api and the xml-schema required by Exchange services you are off to the races.  However if, like me, php-ews is your first exposure to Exchange Web Services and what you want to do isn't covered in the extremely limited wiki, then you are going to need a couple bottles of Advil on-hand.

No comments:

Post a Comment