InfiniTec - Henning Krauses Blog

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

Exchange Managed API autodiscover with Powershell

Powershell is a great tool to automate all sorts of things – including fiddling around with your Exchange mailbox. And the Autodiscover makes it really easy to connect to it – especially if you’re on Office 365 and don’t even know your CAS server.

So first, we need to load the EWS Managed API dll into the current runspace:

[Reflection.Assembly]::LoadFrom("C:\Program Files\Microsoft\Exchange\Web Services\1.1\Microsoft.Exchange.WebServices.dll")

Then, create an ExchangeService instance and set its credentials:

$service =  New-Object Microsoft.Exchange.WebServices.Data.ExchangeService -ArgumentList([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1)
$service.Credentials = New-Object System.Net.NetworkCredential("someone@infinitec.de", "password", "domain");

Now we are ready to use AutoDiscover. But depending on your infrastructure, AutoDiscover might need to follow some redirections before it has discovered your CAS Server. Like in this case:

$service.AutodiscoverUrl("someone@infinitec.de");

Exception calling "AutodiscoverUrl" with "1" argument(s): "Autodiscover blocked a potentially insecure redirection to https://autodiscover-s.outlook.com/autodiscover/autodiscover.xml. To allow Autodiscover to follow the redirection, use the AutodiscoverUrl(string, AutodiscoverRedirectionUrlValidationCallback) overload."
At line:1 char:25
+ $service.AutodiscoverUrl <<<< ("hkrause@infinitec.de");
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : DotNetMethodException

This happens because the AutoDiscover process looks at autodiscover.infinitec.de and instead of an A record pointing to the AutoDiscover service, it finds a CNAME redirecting it to autodiscover.outlook.com. Because this might pose a security risk, the AutoDiscoverUrl method aborts  the discovery process and throws the Exception displayed above. The solution is also outlined: Instead of calling the method AutoDiscoverUrl(mailAddress) call the overload which expects a delegate as a second paramter. This delegate has a string as input and returns the $true if the discovery process should follow the redirection; false otherwise.

How can this overload be used with PowerShell? The answer is a ScriptBlock. If you simply want to allow the discovery process to follow all redirects, simply call it this way:

$service.AutodiscoverUrl("someone@infinitec.de", {$true})

But if you want to verify the discovery process is redirected to the correct url, use this version:

$TestUrlCallback = {
 param ([string] $url)
 if ($url -eq "https://autodiscover-s.outlook.com/autodiscover/autodiscover.xml") {$true} else {$false}
}

$service.AutodiscoverUrl("someone@infinitec.de", $TestUrlCallback)

You can embed whatever checks you need to verify the given url in the third line of the $TestUrlCallback method.


Posted by Henning Krause on Friday, July 22, 2011 5:30 PM, last modified on Sunday, July 24, 2011 1:22 AM
Permalink | Post RSSRSS comment feed

Enumerating Sharepoint Connections in a mailbox with EWS

An interesting question on StackOverflow came up recently: Is it possible to get the SharePoint lists which are connected to an Exchange Mailbox? The data which is synchronized with Outlook is stored in a PST file on the local disk – so no interaction with Exchange on this end. But if a user logs on from another computer, the SharePoint list the user has subscribed to are synchronized there as well. So the configuration seems to be stored in the mailbox somewhere. And indeed, they are. Outlook creates a message item in the associated folder table in the users inbox. Associated items are not visible from Outlook, but they can be accessed using MAPI or EWS. It turns out that Outlook saves the SharePoint connections similar to the RSS feeds. So a good starting point is to have a look at the Sharing Message Object Protocol Specification which lists the properties used for these items.

The SharePoint configuration items have a MessageClass of IPM.Sharing.Index.In, and the property PidLidSharingProviderGuidProperty is set to {0006F0AD-0000-0000-C000-000000000046}.

The configuration data is stored on a few properties. The following method lists all SharePoint connections connected to a mailbox:

   1:  using System;
   2:  using System.Net;
   3:  using Microsoft.Exchange.WebServices.Data;
   4:   
   5:  namespace ExchangeTest
   6:  {
   7:      internal class Program
   8:      {
   9:          private static readonly Guid PropertySetSharing = new Guid("{00062040-0000-0000-C000-000000000046}");
  10:   
  11:          private static readonly ExtendedPropertyDefinition PidLidSharingProviderGuidProperty =
  12:              new ExtendedPropertyDefinition(PropertySetSharing, 0x8A01, MapiPropertyType.CLSID);
  13:   
  14:          private static readonly ExtendedPropertyDefinition SharingRemotePathProperty =
  15:              new ExtendedPropertyDefinition(PropertySetSharing, 0x8A04, MapiPropertyType.String);
  16:   
  17:          private static readonly ExtendedPropertyDefinition SharingLocalNameProperty =
  18:              new ExtendedPropertyDefinition(PropertySetSharing, 0x8A0F, MapiPropertyType.String);
  19:   
  20:          private static readonly ExtendedPropertyDefinition SharingRemoteNameProperty =
  21:              new ExtendedPropertyDefinition(PropertySetSharing, 0x8A05, MapiPropertyType.String);
  22:   
  23:          private static readonly ExtendedPropertyDefinition SharingBrowseUrlProperty =
  24:              new ExtendedPropertyDefinition(PropertySetSharing, 0x8A51, MapiPropertyType.String);
  25:   
  26:          private static readonly ExtendedPropertyDefinition SharingRemoteTypeProperty =
  27:              new ExtendedPropertyDefinition(PropertySetSharing, 0x8A1D, MapiPropertyType.String);
  28:   
  29:          private static readonly Guid SharePointProviderId = new Guid("{0006F0AD-0000-0000-C000-000000000046}");
  30:   
  31:          public static void Main(string[] args)
  32:          {
  33:              var service = new ExchangeService(ExchangeVersion.Exchange2010)
  34:                            {Credentials = new NetworkCredential(test@contoso.com, "Password!")};
  35:              
  36:              service.AutodiscoverUrl(test@contoso.com, url => true);
  37:   
  38:              var folder = Folder.Bind(service, WellKnownFolderName.Inbox);
  39:              var filter = new SearchFilter.SearchFilterCollection(LogicalOperator.And,
  40:                                                                   new SearchFilter.IsEqualTo(ItemSchema.ItemClass,
  41:                                                                                              "IPM.Sharing.Index.In"),
  42:                                                                   new SearchFilter.IsEqualTo(PidLidSharingProviderGuidProperty,
  43:                                                                                              SharePointProviderId.ToString()));
  44:              var view = new ItemView(512)
  45:                         {
  46:                             Traversal = ItemTraversal.Associated,
  47:                             PropertySet = new PropertySet(BasePropertySet.IdOnly,
  48:                                                           SharingRemotePathProperty, SharingBrowseUrlProperty,
  49:                                                           SharingLocalNameProperty, SharingRemoteNameProperty,
  50:                                                           SharingRemoteTypeProperty)
  51:                         };
  52:   
  53:              var items = folder.FindItems(filter, view);
  54:              foreach (var item in items)
  55:              {
  56:                  Console.Out.WriteLine("RemotePath = {0}", item.GetValueOrDefault<string>(SharingRemotePathProperty));
  57:                  Console.Out.WriteLine("BrowseUrl = {0}", item.GetValueOrDefault<string>(SharingBrowseUrlProperty));
  58:                  Console.Out.WriteLine("LocalName = {0}", item.GetValueOrDefault<string>(SharingLocalNameProperty));
  59:                  Console.Out.WriteLine("Remotename = {0}", item.GetValueOrDefault<string>(SharingLocalNameProperty));
  60:                  Console.Out.WriteLine("Type = {0}", item.GetValueOrDefault<string>(SharingRemoteTypeProperty));
  61:                  Console.Out.WriteLine(new string('=', 80));
  62:              }
  63:          }
  64:      }
  65:   
  66:      public static class ItemExtension
  67:      {
  68:          public static T GetValueOrDefault<T>(this Item item, PropertyDefinitionBase property, T defaultValue = default(T))
  69:          {
  70:              T result;
  71:              return item.TryGetProperty(property, out result) ? result : defaultValue;
  72:          }
  73:      }
  74: }

This method dumps the configuration of all SharePoint connections to the console.

To use this method, you’ll need .NET 4. If you are running .NET 2.0, you’ll have to adapt it.

Additionally, this won't work with Exchange 2007, because EWS in that version does not allow a FindItems call on the associated items table. WebDAV is the API of choice in this case.


Posted by Henning Krause on Wednesday, July 13, 2011 9:05 PM, last modified on Wednesday, July 13, 2011 9:05 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