InfiniTec - Henning Krauses Blog

Don't adjust your mind - it's reality that is malfunctioning

Troubleshooting Push notifications failures

It can sometimes be tricky to get Exchange push notifications working. The development of a listener is only part of the solution. The other part is getting the notifications from the Exchange server to the listener, especially when the listener expects the notifications on a secure channel. Exchange is of no particular help here. The only error message that is generated when Exchange can’t send a notification message to a listener is something like this:

image

This message is not helpful at all. Ok – Exchange could not send a notification a specific subscription. But why?

That’s why I wrote a small utility that should be able to diagnose the three most common problems:

  • The Exchange server cannot reach the notification listener because a firewall is blocking the access.
  • The host specified in the callback address can not be resolved to an IP address
  • If the callback address of the listener is secured using TLS/SSL, Exchange will not send notifications if the server certificate used by the listener is not trusted.

Instructions:

Copy the program on the Exchange server. Open a command prompt and start the program. It expects the address of the listener as the first parameter. The execution of the program can take up to one minute.

Download the program here:

   (105kb)

The source code can be downloaded from CodePlex.


Posted by Henning Krause on Sunday, June 21, 2009 12:14 AM, last modified on Sunday, June 21, 2009 10:59 AM
Permalink | Post RSSRSS comment feed

Least privilege with Windows Services

Some actions in Windows require the logged on account to have certain privileges. A good explanation about these special permissions can be found on MSDN. The privilege assignment can viewed and modified through either Group Policy (on a domain) or the Local Security Policy (secpol.msc).

image

The privileges that an account has on a certain application can be monitored with the Process Explorer from Microsoft (former SysInternals). Here is an example of my explorer.exe:

image

The explorer has a number of privileges, bust most of them are disabled. The SeChangeNotifyPrivilege (which is called "Bypass traverse checking”) is the only one that is enabled by default. All other must be explicitly enabled before actions can be executed which are guarded by the respective privilege. But they also can be disabled for the lifetime of the process. Once turned off, they can’t be turned on again unless the process is restarted.

A rather important privilege, at least for WCF hosts and web server is the “Impersonate a client after authentication”.

Depending on the account your service and/or program is running, the account may have a number of privileges that are not required by your application or service. Especially since there are very mighty privileges such as the “Debug programs” or “Create global objects”. To comply with a least-privilege account, it is a good practice to turn off unnecessary privileges. This is especially true for services or applications which either utilize addins or exchange data over the network.

Unfortunately, there is no managed API available, but the required interop is not very hard. I have wrapped the whole stuff and it’s available from my InfiniTec.Common library. Currently, the latest version is available from CodePlex (its part of my Exchange Push Notification component. Just get the latest build or download the source from the repository). After you have added a reference to your service, it’s a matter of three lines of code to disable all expendable privileges:

   1: var priviliges = new PrivilegeCollection();
   2: priviliges.Refresh();
   3: priviliges.ReducePrivilegesToMinimum();

This call removes all unnecessary priviliges from the current process token. If you are hosting WCF services in your application and need to impersonate the caller, you should leave the “Impersonate a client after authentication” privilege intact. This requirement changes the code to the following three lines of code:

   1: var priviliges = new PrivilegeCollection();
   2: priviliges.Refresh();
   3: priviliges.ReducePrivilegesToMinimum(Privilege.Impersonate);

The InfiniTec.Security.Privilege class contains the internal names of most of the privileges in Windows, and the ReducePrivilegesToMinimum takes a string array as its first parameter, so it is very easy to keep multiple privileges intact.

Enabling a required privilege

It is not enough just to have a certain privilege to execute a secured operation. For example, to shutdown a computer it is not enough to have the "Shutdown the Computer” privilege. A program must explicitly enable it to call the InitiateShutdown function. This can also be easily achieved by executing these lines of code:

   1: var priviliges = new PrivilegeCollection();
   2: priviliges.Refresh();
   3:  
   4: var privilege = priviliges[Privilege.Shutdown];
   5: privilege.Enable();

Technorati:

Posted by Henning Krause on Tuesday, June 16, 2009 4:23 PM, last modified on Monday, November 29, 2010 8:50 PM
Permalink | Post RSSRSS comment feed

Developing Windows Services with .NET

Developing Windows services with .NET is not always easy – sure, one can attach a debugger once the service is started (at least when developing on Windows XP / 2003). But this is not exactly an ‘F5’ experience one has with normal programs or even Websites. Another drawback of the “Attach to process” method is that the start of the service cannot be debugged. A common way to circumvent this is to write a System.Diagnostics.Debugger.Break statement within the first lines. This will bring up the Just-In-Time Debugger window that let’s you choose a debugger. Sadly, this does not work any more with Windows Vista / 2008 because services run on an isolated window station.

Luckily, there is a solution: A windows service is nothing more than a console application that is started slightly differently. One can take advantage of this by starting the service as a stand-alone program when started normally (via F5 or starting the service from the command line). But if the program detects if it is started by the Service Control manager, run it as a service. So, how can a program check whether it is started by the Service control manager? It depends on the user account the program runs under. If it’s running as “LocalSystem”, one can safely assume it’s running as a service. In any other case, the user account token has a special group membership: The System.Security.Principal.WellknownSidType.Service.

The default body of a program.cs file of blank service solution looks like this:

   1: using System.ServiceProcess;
   2:  
   3: namespace WindowsService1
   4: {
   5:     static class Program
   6:     {
   7:         /// <summary>
   8:         /// The main entry point for the application.
   9:         /// </summary>
  10:         static void Main()
  11:         {
  12:             var servicesToRun = new ServiceBase[] 
  13:                                               { 
  14:                                                   new Service1() 
  15:                                               };
  16:             ServiceBase.Run(servicesToRun);
  17:         }
  18:     }
  19: }

With a few additional lines, a comfortable F5 experience can be gained:

   1: using System;
   2: using System.Linq;
   3: using System.Security.Principal;
   4: using System.ServiceProcess;
   5:  
   6: namespace WindowsService1
   7: {
   8:     static class Program
   9:     {
  10:         /// <summary>
  11:         /// The main entry point for the application.
  12:         /// </summary>
  13:         static void Main()
  14:         {
  15:             var identity = WindowsIdentity.GetCurrent();
  16:             var principal = new WindowsPrincipal(identity);
  17:             // Check whether the current user account is the LocalSystem account
  18:             var isLocalSystemUser = 
  19:                 identity.User == new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null);
  20:  
  21:             if (isLocalSystemUser || principal.IsInRole(new SecurityIdentifier(WellKnownSidType.ServiceSid, null)))
  22:             {
  23:                 var servicesToRun = new ServiceBase[]
  24:                                         {
  25:                                             new Service1()
  26:                                         };
  27:                 ServiceBase.Run(servicesToRun);
  28:             }
  29:             else
  30:             {
  31:                 var service = new Service1();
  32:                 service.StartAsApplication();
  33:             }
  34:         }
  35:  
  36:     }
  37: }

I have added a StartAsApplication() method to the Service1 class, because the OnStart() method of the default service template is protected and therefore not accessible. And that is all there is. The service can now be started by just pressing F5.

Drawbacks

The gain of the F5 experience does not come without a cost – normally, a Windows service runs under a special account (which is hopefully not LocalSystem): NetworkService, LocalService or a dedicated service account. With this approach, the service runs with the permissions of the currently logged on user. Normally, that user account has far more security permissions than the service account would have. For example, the current user might have the permission to host WCF services on arbitrary ports – something only administrators are allowed to do. A service account would need explicit permission to host that service. Another example are private keys of certificates in the LocalComputer store – not accessible for the NetworkService account by default. But if a service needs to access those keys, it won’t fail unless during development. This should be kept in mind.!


Posted by Henning Krause on Saturday, June 13, 2009 12:26 PM, last modified on Monday, November 29, 2010 7:30 PM
Permalink | Post RSSRSS comment feed

Custom Forms with Outlook Web Access 2007

In my first article about custom forms for Outlook WebAccess I wrote about the difficulties that are associated with the development of custom forms. Thanks to a hint from reader Ciusso I was able to come up with another solutions that makes the whole stuff a lot easier. Here is a walkthrough to create custom forms the easy way – with support for standard postback, AJAX… the whole enchilada. As a prerequisite, download the EWS Managed API. It’s much simpler to use than the Exchange Web Services.

First, start with a new WebProject. If you are developing directly on an Exchange 2007 server (which is hopefully a test machine and not production – don’t do that!), the new solution can be created directly under the directory for custom OWA forms: %ProgramFiles%\Microsoft\Exchange Server\ClientAccess\Owa\forms. This simplifies development because no files have to be deployed after each build.

image

The problems I outlined in the first article are caused by an HTTP module, which is defined in the web.config file of Outlook WebAccess. The web.config file of Outlook Web Access is located in the folder %ProgramFiles%\Microsoft\Exchange Server\ClientAccess\Owa:

image

When you open the web.config file, you’ll see that Outlook Web Access adds two assemblies, one HTTP module and two HTTP handlers.

 image

Since the custom form is located beneath the OWA folder, it inherits all its configuration settings from the Outlook Web Access configuration. The trick is to remove the artifacts introduced  by Outlook Web Access. For the HTTP module and the two HTTP handlers this is an easy task: As shown in the next picture, just add “remove” tags for each artifact.

image

Things get more complicated with the assemblies, however. The two assemblies can not be removed via “remove tags”. It seems to be necessary to clear the entire collection of assemblies with a “clear” tag. But this has a nasty side effect: The assembly of the custom form is also removed and needs to be re-added.

image

The next step is to configure the web application in the IIS Manager: Open the IIS snapin and navigate to the owa virtual directory and then select the directory of the custom form:

image

Open the properties of the CustomForm directory configure a Web Applicaton by clicking on the “Create” button. Ensure that the application pool is MSExchangeOWAAppPool.

image

Next, ensure that the ASP.NET version of the application is set to 2.0:

image

Close the dialog and return to Visual Studio.

The custom form will not be loaded by Outlook Web Access until it finds a registry.xml describing a mapping from an item class to the custom forms. Here is the registry.xml from this example:

   1: <Registry xmlns="http://schemas.microsoft.com/exchange/2004/02/formsregistry.xsd" Name="PremiumExtensions" InheritsFrom="Premium" IsRichClient="true">
   2:   <Experience Name="Premium">
   3:     <Client Application="MSIE" MinimumVersion="6" Platform="Windows NT" />
   4:     <Client Application="MSIE" MinimumVersion="6" Platform="Windows 2000" />
   5:     <Client Application="MSIE" MinimumVersion="6" Platform="Windows 98; Win 9x 4.90" />
   6:     <ApplicationElement Name="Item">
   7:       <ElementClass Value="IPM.Note.CustomClass">
   8:         <Mapping Action="New" Form="https://w2k3x64/owa/forms/CustomForm/EditItem.aspx"/>
   9:         <Mapping Action="Open" Form="https://w2k3x64/owa/forms/CustomForm/EditItem.aspx"/>
  10:         <Mapping Action="Preview" Form="https://w2k3x64/owa/forms/CustomForm/ViewItem.aspx"/>
  11:         <Mapping Action="Reply" Form="https://w2k3x64/owa/forms/CustomForm/EditItem.aspx"/>
  12:         <Mapping Action="ReplyAll" Form="https://w2k3x64/owa/forms/CustomForm/EditItem.aspx"/>
  13:         <Mapping Action="Forward" Form="https://w2k3x64/owa/forms/CustomForm/EditItem.aspx"/>
  14:       </ElementClass>
  15:     </ApplicationElement>
  16:     <ApplicationElement Name="PreFormAction">
  17:       <ElementClass Value="IPM.Note.CustomClass">
  18:         <Mapping Action="Open" Form="Microsoft.Exchange.Clients.Owa.Premium.Controls.CustomFormRedirectPreFormAction,Microsoft.Exchange.Clients.Owa"/>
  19:         <Mapping Action="Preview" Form="Microsoft.Exchange.Clients.Owa.Premium.Controls.CustomFormRedirectPreFormAction,Microsoft.Exchange.Clients.Owa"/>
  20:         <Mapping Action="Reply" Form="Microsoft.Exchange.Clients.Owa.Premium.Controls.CustomFormRedirectPreFormAction,Microsoft.Exchange.Clients.Owa"/>
  21:         <Mapping Action="ReplyAll" Form="Microsoft.Exchange.Clients.Owa.Premium.Controls.CustomFormRedirectPreFormAction,Microsoft.Exchange.Clients.Owa"/>
  22:         <Mapping Action="Forward" Form="Microsoft.Exchange.Clients.Owa.Premium.Controls.CustomFormRedirectPreFormAction,Microsoft.Exchange.Clients.Owa"/>
  23:         <Mapping Action="New" Form="Microsoft.Exchange.Clients.Owa.Premium.Controls.CustomFormRedirectPreFormAction,Microsoft.Exchange.Clients.Owa"/>
  24:       </ElementClass>
  25:     </ApplicationElement>
  26:   </Experience>
  27: </Registry>

The mapping above is for the message class “IPM.Note.CustomClass”. It seems that it’s necessary to specify a full-qualified name for the address of the formulars – relative path will not work.

Now, add a reference to the Managed API and the System.DirectoryServices.AccountManagement. Create a new form named “ViewItem.aspx”.

For the sake of simplicity, just add this fragment to the page below the scriptmanager reference:

   1: <div>
   2:     <asp:Label runat="server" ID="Label1" Text="Subject: " />
   3:     <asp:Label runat="server" ID="SubjectLabel" Text="Subject" />
   4: </div>

In the code behind file, add the following code:

   1: using System;
   2: using System.DirectoryServices.AccountManagement;
   3: using System.Net;
   4: using System.Security.Principal;
   5: using System.Web;
   6: using System.Web.UI;
   7: using Microsoft.Exchange.WebServices.Data;
   8:  
   9: namespace CustomForm
  10: {
  11:     public partial class ViewItem : Page
  12:     {
  13:         static ViewItem()
  14:         {
  15:             ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, errors) => true;
  16:         }
  17:  
  18:         protected string OwaItemId
  19:         {
  20:             get { return HttpUtility.UrlEncode(Request["id"]); }
  21:         }
  22:  
  23:         protected string ContentClass
  24:         {
  25:             get { return Request["t"]; }
  26:         }
  27:  
  28:         protected string State
  29:         {
  30:             get { return Request["s"]; }
  31:         }
  32:  
  33:         protected string Action
  34:         {
  35:             get { return Request["a"]; }
  36:         }
  37:  
  38:         protected void Page_Load(object sender, EventArgs e)
  39:         {
  40:             Item item = LoadItem();
  41:             SubjectLabel.Text = item.Subject;
  42:         }
  43:  
  44:         private Item LoadItem()
  45:         {
  46:             using (((WindowsIdentity) HttpContext.Current.User.Identity).Impersonate())
  47:             {
  48:                 string emailAddress = UserPrincipal.Current.EmailAddress;
  49:                 var exchangeService = new ExchangeService(ExchangeVersion.Exchange2007_SP1)
  50:                                           {
  51:                                               Url = new Uri("https://localhost/ews/exchange.asmx"),
  52:                                               UseDefaultCredentials = true
  53:                                           };
  54:                 ServiceResponseCollection<ConvertIdResponse> convertIds =
  55:                     exchangeService.ConvertIds(new[] {new AlternateId(IdFormat.OwaId, OwaItemId, emailAddress)}, 
  56:                         IdFormat.EwsId);
  57:                 ServiceResponseCollection<GetItemResponse> items =
  58:                     exchangeService.BindToItems(new[] {new ItemId(((AlternateId) convertIds[0].ConvertedId).UniqueId)},
  59:                                                 new PropertySet(BasePropertySet.FirstClassProperties));
  60:                 return items[0].Item;
  61:             }
  62:         }
  63:     }
  64: }

In the static constructor, SSL certificate checking is effectively disabled. Since this example binds to the Exchange Server with the hostname "localhost”, certificate would fail regardless of the certificate used.

All the heavy lifting is done in the LoadItems method. In the first line the code impersonates the user logged on to OWA. The next step requires a prerequisite: The website gets the OWA id of the item. this id cannot directly be used with the Exchange Web Services (or the EWS Managed API, for that matter). The ExchangeService.ConvertIds method is used here. But that method requires the primary email address of the user the OWA id belongs to. For simple environments (read: the mail attribute of the user object in Active Directory contains the primary email address), the method used here can be used. The System.DirectoryServices.AccountManagement.UserPrincipal class was introduced with .NET 3.5 and offers a very simple way to get the required information. After the ID has been converted to an EWS id, the item can finally be loaded from the store. In this example, only the subject of the item is displayed on the form.

Finally, you need to restart the IIS – contrary to what to official documentation states, Outlook Web Access only scans for new custom forms on startup and not periodically. You need to keep this in mind when deploying your solution. After the IIS has been restarted, you can check wether the custom form has been loaded by taking a look at the EventLog. If the form was loaded successfully, you’ll find an entry similar to this one:

image

The form created here will be used to preview every item with the message class of IPM.Post.Custom.

The solution containing the relevant files for this example is attached to this post. The file has a digital signature to ensure it’s not modified.


Technorati:

Posted by Henning Krause on Friday, June 12, 2009 11:31 PM, last modified on Wednesday, December 8, 2010 11:35 AM
Permalink | Post RSSRSS comment feed

Getting the body of an Email with a FindItems request

The FindItem operation (or the corresponding ExchangeService.FindItems method) does not return the body of an email by default. And when trying to explicitly request them via a custom propertyset, the call fails. Consider this method, which uses the EWS Managed API to execute a FindItems method to get every message from the inbox folder of a mailbox, fetching only the item id and the body:

   1: private static void GetAllItems(ExchangeService exchangeService)
   2: {
   3:     var offset = 0;
   4:     const int pageSize = 100;
   5:  
   6:     FindItemsResults<Item> result;
   7:     do
   8:     {
   9:         var view = new ItemView(pageSize, offset)
  10:                        {
  11:                            PropertySet = new PropertySet(BasePropertySet.IdOnly)
  12:                                              {
  13:                                                  ItemSchema.Body
  14:                                              }
  15:                        };
  16:  
  17:         result = exchangeService.FindItems(WellKnownFolderName.Inbox, view);
  18:  
  19:         foreach (var item in result)
  20:         {
  21:             ProcessItem(item);
  22:         }
  23:         offset += pageSize;
  24:     } while (result.MoreAvailable);
  25: }

When executed, the ExchangeService instance throws a ServiceValidationException stating “The property Body cannot be used in FindItem requests”. The official workaround proposed by Microsoft is to use a FindItem request to get the item ids of the items in a folder and afterward issue a GetItem request containing all the item ids and request the body property. However, there is another solution: The body properties can be fetched by requesting the MAPI properties containing the body:

   1: private static ExtendedPropertyDefinition TextBodyProperty = new ExtendedPropertyDefinition(0x1000, MapiPropertyType.String);
   2: private static ExtendedPropertyDefinition HtmlBodyProperty = new ExtendedPropertyDefinition(0x1013, MapiPropertyType.Binary);

The first property definition can be used to fetch the text body of a mail. The second one fetches the Html body. The new GetAllItemsMethod now looks like this:

   1: private static void GetAllItems(ExchangeService exchangeService)
   2: {
   3:     var offset = 0;
   4:     const int pageSize = 100;
   5:  
   6:     FindItemsResults<Item> result;
   7:     do
   8:     {
   9:         var view = new ItemView(pageSize, offset)
  10:                        {
  11:                            PropertySet = new PropertySet(BasePropertySet.IdOnly)
  12:                                              {
  13:                                                  HtmlBodyProperty
  14:                                              }
  15:                        };
  16:  
  17:         result = exchangeService.FindItems(WellKnownFolderName.Inbox, view);
  18:  
  19:         foreach (var item in result)
  20:         {
  21:             object body;
  22:             if (item.ExtendedProperties.TryGetValue(HtmlBodyProperty, out body))
  23:             {
  24:                 Console.Out.WriteLine("item.Body = {0}", Encoding.UTF8.GetString(Convert.FromBase64String((string)body)));
  25:             }
  26:         }
  27:         offset += pageSize;
  28:     } while (result.MoreAvailable);
  29: }

The HtmlBody is requested with the addtion of the HtmlBodyProperty to the ItemView in line 13. Since the Html body is stored in binary form and returned in Base64 encoded format, it needs to be decoded before it can be displayed. This is done in line 24. If the plaintext body is requested, the value of the body property in line 24 can simply be converted to a string.


Posted by Henning Krause on Tuesday, June 9, 2009 9:00 PM, last modified on Wednesday, April 20, 2011 6:19 PM
Permalink | Post RSSRSS comment feed

FindItems and SyncFolderItems performance

By default, Exchange returns a rather large set of properties with each item during a FindItem or SyncFolderItems request. If the query returns a large set of items, this slows down the entire process: On one hand, Exchange needs to get all properties from the store and secondly, all the data needs to be sent over the wire to the calling application. The calling application can specify which items to fetch for a FindItem or SyncFolderItems request by specifying it in the PropertySet parameter. The default property set looks like this:

   1: var PropertySet = new PropertySet(BasePropertySet.FirstClassProperties)

To request only the property id of the items, use this declaration instead:

   1: var PropertySet = new PropertySet(BasePropertySet.IdOnly)

I’ve measured the FindItems call with the Exchange server at my workplace from home. So the call went over the Internet using a 2 MBit connection (on both sides). The inbox folder of my mailbox currently contains approximately 4500 items and I have repeated the measurement two times for each of the propertysets. When querying only for the id of the items, the entire process took 46 seconds to complete. On the other hand, when querying the default set of properties, the whole process took between 1:37 minutes and 1:41 minutes to complete.

But item id is seldom the only property needed. Luckily, the additional properties can be specified on the property set:

   1: var PropertySet = new PropertySet(BasePropertySet.IdOnly)
   2:                   {
   3:                      ItemSchema.Subject,
   4:                      ItemSchema.DateTimeReceived
   5:                   }

Executing this query on my mailbox, took about 50 seconds to complete. This is a significant improvement of the default property set.


Posted by Henning Krause on Sunday, June 7, 2009 12:56 PM, last modified on Saturday, November 27, 2010 6:35 AM
Permalink | Post RSSRSS comment feed

Processing items in an Exchange folder using EWS Managed API

A requirement I often hear is something like this: “We have a mailbox that receives emails of some kind that need to be processed somehow”. What options are there to fulfill the requirement? As always, it depends. When the items need to be processed as soon as possible and the client processing the items can be directly reached by the Exchange server, push notifications are certainly a good choice. If, on the other hand, item processing is not time critical a suitable method of processing those items is polling. It’s much simpler to use than push notifications. A simple example might look like this:

   1: private static void ProcessItems(ExchangeService exchangeService)
   2: {
   3:     var offset = 0;
   4:     const int pageSize = 100;
   6:     FindItemsResults<Item> result;
   7:     do
   8:     {
   9:         var view = new ItemView(pageSize, offset);
   5:  
  10:  
  11:         result = exchangeService.FindItems(WellKnownFolderName.Inbox, view);
  12:  
  13:         foreach (var item in result)
  14:         {
  15:             ProcessItem(item);
  16:         }
  17:         offset += pageSize;
  18:     } while (result.MoreAvailable);
  19: }

This is a very naïve implementation as it always returns every item from the inbox folder of the mailbox. But at least it uses paging. This breaks the processing down from one very large request into many small requests (in this case 100 items are returned per request).

This method is suitable if you delete the processed items from a mailbox after they are processed. Of course, the items should not be removed until the every item has been processed. Otherwise items may be skipped since the offsets of the individual items change when an item is removed from the folder.

If the items are not removed from the store, the calling application must distinguish new items from items already processed. An obvious way to do this is to mark each item once it has been processed by marking it as read. This changes the requirement for the ProcessItems method: It should only process unread items. This modifications has been incorporated into the following example:

   1: private static void ProcessItems(ExchangeService exchangeService)
   2: {
   3:     var offset = 0;
   4:     const int pageSize = 100;
   5:  
   6:     FindItemsResults<Item> result;
   7:     do
   8:     {
   9:         var view = new ItemView(pageSize, offset)
  10:         {
  11:             SearchFilter = new SearchFilter.IsEqualTo(EmailMessageSchema.IsRead, false)
  12:         };
  13:  
  14:         result = exchangeService.FindItems(WellKnownFolderName.Inbox, view);
  15:  
  16:         foreach (var item in result)
  17:         {
  18:             ProcessItem(item);
  19:         }
  20:         offset += pageSize;
  21:     } while (result.MoreAvailable);
  22: }

The only change in this example is in line 11: A search filter has been added to filter for unread items. What’s missing here is the modification of the Unread status of each message. And that is a considerable drawback of the whole solution. To touch each processed item, the UpdateItems method must be called. It is sufficient to call the UpdateItems method and pass all item ids from the last resultset to the method. But this adds a significant burden on the Exchange server and slows overall processing down. Furthermore, if someone accesses the mailbox (either with Outlook or Outlook Web Access) and accidently marks one or more items as read, those items will not be processed by the client application.

Next idea: Find all items that were received after the last time the mailbox was checked. A ProcessItems method that implements this behavior might look like this:

   1: private static void ProcessItems(ExchangeService exchangeService, DateTime lastCheck)
   2: {
   3:     var offset = 0;
   4:     const int pageSize = 100;
   5:  
   6:     FindItemsResults<Item> result;
   7:     do
   8:     {
   9:         var view = new ItemView(pageSize, offset)
  10:                        {
  11:                            SearchFilter = new SearchFilter.IsGreaterThanOrEqualTo(ItemSchema.DateTimeReceived, lastCheck)
  12:                        };
  13:  
  14:         result = exchangeService.FindItems(WellKnownFolderName.Inbox, view);
  15:  
  16:         foreach (var item in result)
  17:         {
  18:             ProcessItem(item);
  19:         }
  20:         offset += pageSize;
  21:     } while (result.MoreAvailable);
  22: }

As with the second example, the difference to the first one is line 11. A search filter has been added that restricts the FindItems call to those items received after a specific time. This method removes the requirement to mark each processed item on the server. But it adds another caveat: A mail that is received during a ProcessItems call will be missed if no additional checks are performed. This can be a little tricky.

Luckily, the Exchange WebServices offer a more suitable solution for the whole requirement: The SyncFolderItems method. This method not only solves the problems mentioned above but also returns deleted items, if such a processing is necessary. A ProcessItems method that uses this API now looks like this:

   1: private static string SyncItems(ExchangeService exchangeService, string syncState)
   2: {
   3:     const int pageSize = 100;
   4:  
   5:     ChangeCollection<ItemChange> changeCollection;
   6:     do
   7:     {
   8:         changeCollection = exchangeService.SyncFolderItems(new FolderId(WellKnownFolderName.Inbox),
   9:                                                            new PropertySet(BasePropertySet.FirstClassProperties), null, pageSize,
  10:                                                            SyncFolderItemsScope.NormalItems, syncState);
  11:  
  12:         foreach (var change in changeCollection)
  13:         {
  14:             if (change.ChangeType == ChangeType.Create)
  15:             {
  16:                 ProcessItem(change.Item);
  17:             }
  18:         }
  19:         syncState = changeCollection.SyncState;
  20:  
  21:     } while (changeCollection.MoreChangesAvailable);
  22:     return syncState;
  23: }

This method only processes newly created items and ignores all other item changes or deletions. The application calling this method needs to store the synchronization state between each call. If an empty sycnState is provided, Exchange will return every item from the mailbox as “Created” item. This makes it possible to process all existing items once and then, with the same logic, every changed item. The only drawback with this method is the fact that the synchronization state can become quite big (in a folder with ~4500 items, the syncstate has a size of approx. 60kb).


Posted by Henning Krause on Sunday, June 7, 2009 12:54 PM, last modified on Monday, November 29, 2010 9:21 PM
Permalink | Post RSSRSS comment feed

Getting the fullqualified DNS name of the current computer

Under certain circumstances a program needs to determine the name of the computer it’s running on. The first approach to get this name is to use the System.Environment.MachineName property. However, this name only reflects the NETBIOS name of the current machine. But in larger environments a full-qualified name including the DNS domain the computer belongs to. This can be something like computername.contoso.local. One example where this full qualified name might be needed are Exchange Push notification. I’ve published a component to CodePlex makes it really easy to incorporate them in an application. However, for the notifications to reach the client the component needs to tell the Exchange server a correct callback address. In a very simple network environment, it is sufficient to specify the NETBIOS hostname. But in more complex environments, Exchange might not be able to send a notification because it cannot correctly resolve the unqualified hostname to an IP address.

The full qualified domain name of the current host can be resolved with a call tot the System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties method. This method returns, among other things, the required information:

   1: var ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties();
   2: string fullQualifiedDomainName;
   3:  
   4: if (!string.IsNullOrEmpty(ipGlobalProperties.DomainName))
   5: {
   6:     fullQualifiedDomainName = string.Format("{0}.{1}", ipGlobalProperties.HostName, ipGlobalProperties.DomainName);
   7: }
   8: else
   9: {
  10:     fullQualifiedDomainName = ipGlobalProperties.HostName;
  11: }

I have updated the PushNotification component to reflect this new behavior.


Posted by Henning Krause on Saturday, June 6, 2009 6:23 PM, last modified on Saturday, June 6, 2009 11:39 PM
Permalink | Post RSSRSS comment feed

InfiniTec.Exchange.Notifications updated to 1.5.0.0

I have just published a new version of the my notification component on CodePlex. The new version has some breaking changes to the previous version but it should be simpler to use. Additionally, I have added a small sample application that shows how to use the component. It’s a small WPF application that allows a user to subscribe to calendars of multiple users. The application uses the new Managed API which made the whole thing a whole lot easier to write. Here is a class diagram of the component:

ClassDiagram

If you have worked with the component before you’ll notice that the Subscription class has lost some of its members. I have removed those methods to avoid confusion on how the component is utilized. The only way to create a new subscription now is to use the SubscriptionCollection class. Here is an example on how to use the component:

   1: using System;
   2: using System.ComponentModel;
   3: using System.IO;
   4: using System.Net;
   5: using InfiniTec.Exchange.Notifications;
   6: using InfiniTec.Threading;
   7:  
   8: namespace NewMailNotificationExample
   9: {
  10:     internal class Program
  11:     {
  12:         private const string StateSaverFilename = "notificationstate.bin";
  13:  
  14:         private static void Main()
  15:         {
  16:             // Ignore any certificate errors
  17:             ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) => true;
  18:  
  19:             // Setup the adapter which will be used to call into the Exchange WebService  
  20:             using (var adapter = new ExchangeWebServicesAdapter(new Uri("https://w2k3x64/ews/exchange.asmx"), 
  21:                 new NetworkCredential("administrator", "password", "contoso")))
  22:             using (var listener = new PushNotificationListener())
  23:             {
  24:                 Console.Out.WriteLine("Starting Notification Service...");
  25:                 listener.Start();
  26:  
  27:                 SubscriptionCollection subscriptionCollection;
  28:  
  29:                 if (File.Exists(StateSaverFilename))
  30:                 {
  31:                     using (var stream = File.OpenRead(StateSaverFilename))
  32:                     {
  33:                         subscriptionCollection = SubscriptionCollection.Deserialize(stream, adapter, listener);
  34:                         subscriptionCollection.SubscriptionRestartCompleted +=
  35:                             SubscriptionCollection_OnSubscriptionRestartCompleted;
  36:                         subscriptionCollection.SubscriptionRestartProgressChanged +=
  37:                             SubscriptionCollection_OnSubscriptionRestartProgressChanged;
  38:  
  39:                         subscriptionCollection.RestartAsync();
  40:                     }
  41:                 }
  42:                 else
  43:                 {
  44:                     // Create a new subscription collection to manage all the subscriptions  
  45:                     // Register for a NewMail notification on the inbox of the administrator
  46:                     subscriptionCollection = new SubscriptionCollection(adapter, listener)
  47:                                                  {
  48:                                                      {new[] {new FolderReference(WellKnownFolderId.Inbox)}, EventTypes.All}
  49:                                                  };
  50:                 }
  51:  
  52:                 Console.Out.WriteLine("Creating subscription");
  53:                 foreach (var subscription in subscriptionCollection)
  54:                 {
  55:                     // Write a line to the console for each new mail received  38:  
  56:                     subscription.NewMail += (sender, e) =>
  57:                                             Console.Out.WriteLine(string.Format("{0}: New Mail arrived in your inbox", e.Timestamp));
  58:                     subscription.Start();
  59:                 }
  60:                 Console.Out.WriteLine("Waiting for notifications... Hit [Enter] to quit...");
  61:                 Console.ReadLine();
  62:  
  63:                 Console.Out.WriteLine("Saving the current state of the notification listener...");
  64:                 using (var stream = File.OpenWrite(StateSaverFilename))
  65:                 {
  66:                     subscriptionCollection.Serialize(stream);
  67:                 }
  68:                 Console.Out.WriteLine("State saved to {0}", Path.GetFullPath(StateSaverFilename));
  69:             }
  70:         }
  71:  
  72:  
  73:         private static void SubscriptionCollection_OnSubscriptionRestartProgressChanged(object sender, ProgressChangedEventArgs args)
  74:         {
  75:             Console.Out.WriteLine("Subscription restart {0}% complete.", args.ProgressPercentage);
  76:         }
  77:  
  78:         private static void SubscriptionCollection_OnSubscriptionRestartCompleted(object sender, AsyncCompletedEventArgs<SubscriptionRestartErrorSummary> args)
  79:         {
  80:             Console.Out.WriteLine("Subscription restart is complete. {0} subscriptions could not be restarted.", args.Result.Errors.Count);
  81:         }
  82:     }
  83: }

Posted by Henning Krause on Saturday, May 23, 2009 12:38 PM, last modified on Saturday, May 23, 2009 12:40 PM
Permalink | Post RSSRSS comment feed

Searching a meeting with a specific UID using Exchange Web Services 2007

For most items, the item id (which is based on the MAPI entry id) is a good candidate for a long-term identifier. As long as the item is not moved between folders, it remains stable. At least for most items. This is, however, not true for meetings. Exchange recreates meeting items under certain circumstances and the meeting items loose their original entry id, thus the item id is also modified. But a meeting has a different property which uniquely identifies it, even across mailbox boundaries: The UID property. This property is available as a first-class property since Exchange 2007 Service Pack 1. It is a string that looks like this:

   1: 040000008200E00074C5B7101A82E008000000007069352667BBC9010000000000000000100000000C74ABD802575A41BC09B0E12352657B

Since this property is not an Item Id, the GetItem operation cannot be used to bind to a meeting element directly. Sadly, the FindItem operation doesn’t work either, because the Exchange WebServices do not accept a restriction that is based on the UID property.

Luckily, the UID can be read and searched via a MAPI property: The GlobalObjectId. This is a binary property and when fetched looks like this:

   1: BAAAAIIA4AB0xbcQGoLgCAAAAABwaTUmZ7vJAQAAAAAAAAAAEAAAAAx0q9gCV1pBvAmw4SNSZXs=

The value is the equivalent to the UID displayed above, but in a different format. The UID is formatted as a hex-string and the GlobalObjectId is displayes as a Base64 string. Using the following small function, the first representation can be converted into the latter:

   1: private static string GetObjectIdStringFromUid(string id)
   2: {
   3:     var buffer = new byte[id.Length/2];
   4:     for (int i = 0; i < id.Length/2; i++)
   5:     {
   6:         var hexValue = byte.Parse(id.Substring(i*2, 2), NumberStyles.AllowHexSpecifier);
   7:         buffer[i] = hexValue;
   8:     }
   9:     return Convert.ToBase64String(buffer);
  10: }

The value returned from the GetObjectIdStringFromUid can now be used to execute a FindItem operation, specifying a restriction on the GlobalObjectId property. Here is the whole function:

   1: static void Main(string[] args)
   2: {
   3:     ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, errors) => true;
   4:  
   5:     const string id = "040000008200E00074C5B7101A82E008000000007069352667BBC9010000000000000000100000000C74ABD802575A41BC09B0E12352657B";
   6:  
   7:     string bufferString = GetObjectIdStringFromUid(id);
   8:  
   9:     var service = new ExchangeServiceBinding
  10:         {
  11:           Url = "https://w2k3x64/ews/exchange.asmx",
  12:           Credentials = new NetworkCredential("bob", "Password!", "contoso"),
  13:           RequestServerVersionValue = new RequestServerVersion { Version = ExchangeVersionType.Exchange2007_SP1}
  14:         };
  15:  
  16:     var response = service.FindItem(new FindItemType
  17:         {
  18:             ParentFolderIds = new[] { new DistinguishedFolderIdType { Id = DistinguishedFolderIdNameType.calendar } },
  19:             ItemShape = new ItemResponseShapeType
  20:                 {
  21:                     BaseShape = DefaultShapeNamesType.AllProperties,
  22:                 },
  23:             Restriction = new RestrictionType {
  24:                 Item = new IsEqualToType
  25:                     {
  26:                       Item = new PathToExtendedFieldType
  27:                         {
  28:                            DistinguishedPropertySetId = DistinguishedPropertySetType.Meeting,
  29:                            DistinguishedPropertySetIdSpecified = true,
  30:                            PropertyId = 0x03,
  31:                            PropertyIdSpecified = true,
  32:                            PropertyType = MapiPropertyTypeType.Binary
  33:                         },
  34:                       FieldURIOrConstant = new FieldURIOrConstantType
  35:                         {
  36:                           Item = new ConstantValueType
  37:                           {
  38:                               Value = bufferString
  39:                           }
  40:                         }
  41:                     }
  42:                 }
  43:         });
  44:     var messageType = ((FindItemResponseMessageType) response.ResponseMessages.Items[0]);
  45:     Console.Out.WriteLine("messageType.ResponseClass = {0}", messageType.ResponseClass);
  46:  
  47:     if (messageType.ResponseClass != ResponseClassType.Success)
  48:     {
  49:         Console.Out.WriteLine(messageType.MessageText);
  50:         return;
  51:     }
  52:     var returnedItems = messageType.RootFolder.Item as ArrayOfRealItemsType;
  53:  
  54:     if (returnedItems == null || returnedItems.Items == null)
  55:     {
  56:         Console.Out.WriteLine("Nothing found.");
  57:         return;
  58:     }
  59:  
  60:     foreach (CalendarItemType item in returnedItems.Items)
  61:     {
  62:         Console.Out.WriteLine("item.Subject = {0}", item.Subject);
  63:         Console.Out.WriteLine("item.UID = {0}", item.UID);
  64:     }
  65:     Console.ReadLine();
  66: }

Posted by Henning Krause on Monday, April 13, 2009 11:45 PM, last modified on Sunday, May 8, 2011 8:37 AM
Permalink | Post RSSRSS comment feed