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()  

15 comments:

Matthew Mulholland said...

Is it possible to use EWS streaming to monitor many mailboxes, or would you need to run this script individually for each mailbox you would want to monitor?

Glen said...

You can monitor as many mailboxes you want but within 2010 the default throttling restriction limit you to a max of 20 Subs. If your using OnPremise you can change this setting for the particular user your subscribing with in Office365 you can't.

Cheers
Glen

Anonymous said...

Hi Glen,

is this possible with public folders too?

Thanks, Karsten

Glen said...

AFAIK yes

cheers
Glen

Craig Connoll said...

Hi,

How do you get this to execute commands when you are monitoring your inbox for new emails?

Glen Scales said...

cmds go in the Action script block eg

Action {
foreach($notEvent in $event.SourceEventArgs.Events){
[String]$itmId = $notEvent.ItemId.UniqueId.ToString()
$message = [Microsoft.Exchange.WebServices.Data.EmailMessage]::Bind($event.MessageData,$itmId)
"Subject : " + $message.Subject + " " + (Get-Date) | Out-File c:\log2.txt -Append
}

Cheers
Glen

Anonymous said...

Does this also work for Exchange 2010 SP2?

Thx

Glen Scales said...

Yes

Anonymous said...

Can we monitor for say 40K mailboxes with single or minimum # of connections or each mailbox needs to maintain their own connection?

Glen Scales said...

Streaming notifications will require one connection per Mailbox monitored. You might want to look at push subscription instead however you will still need to maintain each subscription per mailbox

Cheers
Glen

Anonymous said...

Hello Glen,

Great article,
Does it work the same way for exchange 2013 ?

Glen Scales said...

Yes Streaming notifications are supported on Exchange 2013 and work the same as 2010. Slightly different if you need to use them against Exchange Online

J said...

Hi Glen I tried the script and it works great; a couple of questions:

1) Does notification expire or disconnect after a while ? If not, how I unregister it manually ?

2) I would like to continuously monitor a mail address .. do I have to launch the script at intervals to be sure notification event is registered ?

Thanks !!!!

Glen Scales said...

1. Stream subscription only have a life of 30 minutes max if not resubscribed in 15 post the server will remove the subscription.
2. You would be better to include some type of polling as well to ensure the Subscription is active eg this simple script won't deal with a Mailbox failover between database, server rebooting etc

Unknown said...

Glen,

This works for me in ISE but not a normal PS window.
It shows it opening but never fires when a new email arrives

Id Name PSJobTypeName State HasMoreData Location Command
-- ---- ------------- ----- ----------- -------- -------
4 6e8db9ce-8ab... NotStarted False ...
5 896a6ea8-b77... NotStarted False $event.MessageData.Open()