Friday, September 30, 2011

Using EWS Streaming notifications in Powershell to trigger a script when new mail arrives in Exchange 2010 SP1

EWS Streaming notifications where a great addition to Exchange 2010 SP1 that went some way to filling that large emotional gap that all Exchange developers felt with the demise of Store Event sinks in Exchange 2010. There is some good information and samples of using Streaming notifications in .NET which are a good read at http://msdn.microsoft.com/en-us/library/hh312849%28v=exchg.140%29.aspx and http://blogs.msdn.com/b/exchangedev/archive/2010/12/22/working-with-streaming-notifications-by-using-the-ews-managed-api.aspx

Notifications in EWS are the method you can use to get real-time notifications of email arriving or a calendar appointments being created. To distill what happens with streaming notification in a few lines is that when you create a subscription request for a particular Exchange mailbox folder with a CAS server that connection remains open for a maximum time of 30 minutes (this bit is important). During the next 30 minutes if something happens in the folder your subscribed to the server can use the connection your app has opened to notify you that something has happened. If you understand how ActiveSync works the process is very similar using a hanging http request.  After 30 minutes you then need to re-establish the connection which re-establishes the notifications channel to your application. (this 30 minute period can be changed but 30 is the MAX)

To use this within Powershell requires using some of the event features added in version 2.0 in particular the Register-ObjectEvent cmdlet. The cmldet allows you to subscribe to the events that are generated by a Microsoft .NET Framework object the object in question in this context is the StreamingSubscriptionConnection. This object has a few events the main one that fires when a NewMail arrives or an Item is created is the "OnNotificationEvent". The other import event is the "OnDisconnect" event which will fire when the subscription period ends or something else happens on the CAS side. The third event is the OnSubscriptionError which you need to work with for error checking and reliability.

The following script uses Register-ObjectEvent to create a subscription that will start a process that will log the Subject of the Message that caused the notification to fire. What you receive from the server when a notification is fired is the ItemId of the object that fired it. To get more information about what caused the notification you need to bind to this Item Id. To achieve this the ExchangeService object is passed in via the MessageData and a normal EWS bind is done.

The OnDisconnect Event is also registered so when this event fires the StreamingSubscriptionConnection open method is called which re-establishes the registration for another 30 minutes.

Reliability as this is pretty simple example it just listens to one folder in a mailbox (you can change the subscription type to listen to all folder in a mailbox) and only works withing a currently opened powershell session. To create a reliable process that could be relied on in a business context requires that you put some form of maintenance routine around the Notification subscription which in fully blown .NET is a lot easier to handle so using powershell and notifications outside of prototyping isn't something I'd recommend. The other things to consider when using EWS notification is the default throttling limits in 2010 SP1. For EWS Subscriptions the maximum number subscription you can have by default is 20 Subs per account so if your looking a subscribing to say a large number of calendar folders your going to have problems with throttling once you go past 20 users (remembering throttling is enabled by default) this is particular acute if your trying to use ExchangeOnline where you can't change since (ouch).

I've put a download of this script here the code itself looks like

  1. $MailboxName = "user@domain.com"  
  2. $dllpath = "C:\Program Files\Microsoft\Exchange\Web Services\1.1\Microsoft.Exchange.WebServices.dll"  
  3. [void][Reflection.Assembly]::LoadFile($dllpath)  
  4. $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1)  
  5. $service.TraceEnabled = $false  
  6.   
  7. $service.Credentials = New-Object System.Net.NetworkCredential("user@domain.com","password")  
  8. $service.AutodiscoverUrl($MailboxName ,{$true})  
  9.   
  10. $fldArray = new-object Microsoft.Exchange.WebServices.Data.FolderId[] 1  
  11. $Inboxid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)  
  12. $fldArray[0] = $Inboxid  
  13. $stmsubscription = $service.SubscribeToStreamingNotifications($fldArray, [Microsoft.Exchange.WebServices.Data.EventType]::NewMail)  
  14. $stmConnection = new-object Microsoft.Exchange.WebServices.Data.StreamingSubscriptionConnection($service, 30);  
  15. $stmConnection.AddSubscription($stmsubscription)  
  16. Register-ObjectEvent -inputObject $stmConnection -eventName "OnNotificationEvent" -Action {  
  17.     foreach($notEvent in $event.SourceEventArgs.Events){      
  18.         [String]$itmId = $notEvent.ItemId.UniqueId.ToString()  
  19.         $message = [Microsoft.Exchange.WebServices.Data.EmailMessage]::Bind($event.MessageData,$itmId)  
  20.         "Subject : " + $message.Subject + " " + (Get-Date) | Out-File c:\log2.txt -Append   
  21.     }   
  22. } -MessageData $service  
  23. Register-ObjectEvent -inputObject $stmConnection -eventName "OnDisconnect" -Action {$event.MessageData.Open()} -MessageData $stmConnection  
  24. $stmConnection.Open()