Second Life of a Hungarian SharePoint Geek

March 23, 2014

HTTP Error: ‘401–Unauthorized’ When Accessing Exchange Web Services via PowerShell 2.0

Filed under: Exchange, PowerShell — Tags: , — Peter Holpar @ 20:25

Last week I had to create a tool to automate the synchronization of an Exchange 2010 folder with a SharePoint 2010 list. Formerly I had some experience with Exchange Web Services and its Managed API, and downloaded the code samples for Exchange 2013 to re-use a few classes of the examples. As my developer account had no access to Exchange, I used explicit credentials of my standard account via the NetworkCredential class in the test (user name hardcoded, password read from the command line). The C# code in Visual Studio 2012 of the proof-of-concept application was running without error, but I thought it’s a good idea to migrate the code to PowerShell, as if the requirements happened to change (that is rather likely in this case), it would be easier to modify the functionality without a recompilation. This idea cost me a few hours debugging later.

I found a few samples on the web, like this one, that illustrate the basic steps in PowerShell. I used the EWS Managed API version 2.1, and PowerShell 2.0, that support .NET CLR version 2 (handy when we want to use the SharePoint 2010 server side object library in the same process). Rewriting the code in PowerShell seemed a trivial task, however when I started the tool I got a 401 Unauthorized error on the first command that tried to access an Exchange resource. I double checked the user name in the code, but it was OK. I found no significant difference when comparing the network traffic generated by the .NET version vs. the PowerShell version up to the point of the error message. I altered the code to use the three-string-parameter constructor of the NetworkCredential class (user name, password, domain name) instead of the two-string-parameter constructor version (domainname\username, password), as I had previously issues from using the two-string-parameter version. But the error remained, so I altered the code back. When I logged in with my other user, that has access to Exchange, and used the default credentials (service.UseDefaultCredentials = $true) the PowerShell code gave no error more.

I read the password from the console using the Read-Host Cmdlet as described here (to tell the truth I simply copied the code and did not check how it works and what that might cause):

$password = Read-Host -assecurestring "Please enter your password"

I assumed that there might be a problem with the password I typed in, so decided to echo back it via Write-Host $password. To my great surprise instead of my password (or a mistyped version of that) the result was: “System.Net.NetworkCredential”. Then I realized the switch assecurestring used with Read-Host, that means not only that the text typed in will be replaced with asterisks, but also that it will be represented internally as SecureString, and not as String. After reading the password without assecurestring, the password was stored as string, but the error remained until I changed the NetworkCredential constructor to the three-string-parameter version again. So as usually, the mystic error was a result of a dual mistake, using SecureString instead of String, and using the wrong version of the NetworkCredential constructor.

The sample application from Microsoft (mentioned above) are working flawlessly with a NetworkCredential constructor that accepts a String for domainname\username, and a SecureString parameter for password, but these samples using the .NET version 4.0. After changing the .NET version of the project to 3.5 in Visual Studio, I had the same issues, but at least the IDE gave me a compilation error because of using a wrong constructor parameter. “Thanks” to the flexibility of PowerShell, it did not warned me because of the SecureString type (I assume it used the “System.Net.NetworkCredential” string as password) and did its best to perform the authentication and failed first there.

May 14, 2010

Accessing internal Exchange web services from an external application through ISA server using forms-based authentication

Filed under: Exchange, FBA, ISA, Web service — Tags: , , , — Peter Holpar @ 23:14

I think it should be a quite common request to access the Exchange Server of a company from an application. In our case, the Exchange Server is version 2007, and it is published through ISA Server 2006 (but it might be a ForeFront TMG 2010 as well) for external access. The authentication method is forms-based through HTTPS channel.

Most common way to access Exchange Server 2007 programmatically is Exchange Web Services (EWS). The authentication scenario outlined above requires a few tricks, as you cannot use the standard NetworkCredential directly for authentication.

FBA requires you to post the credentials (like user name and password) to the authentication URL and the response contains the cookie(s) that must be added to forthcoming request for authentication.

My example is based on the code found here:

Access the Exchange store via WebDAV with Form-Based-Authentication turned on [Updated]

There are a few difference, for example, in our case not the owaauth.dll of OWA is used for authentication, but the CookieAuth.dll of ISA.

I’ve got the simple EWS sample for my post from this article:

Using Exchange Web Services 2007: The Basics

As I’ve mentioned above, in my scenario I cannot use NetworkCredential for the web service. Instead, I should get the FBA cookie and add it to the CookieContainer of the web service proxy.

  1. ExchangeServiceBinding.ExchangeServiceBinding esb = new ExchangeServiceBinding.ExchangeServiceBinding();
  2. esb.Url = "https://mail.company.com/EWS/Exchange.asmx";
  3. esb.Credentials = CredentialCache.DefaultCredentials;
  4. CookieCollection cookies = DoFormbasedAuthentication(esb.Url, new NetworkCredential("user", "password", "doamain"));
  5.  
  6. FindItemType fit = new FindItemType();
  7. fit.ItemShape = new ItemResponseShapeType { BaseShape = DefaultShapeNamesType.Default };
  8. fit.ParentFolderIds = new DistinguishedFolderIdType[]
  9. {
  10.     new DistinguishedFolderIdType
  11.     {
  12.         Mailbox = new EmailAddressType{ EmailAddress="user@gcompany.com"},
  13.         Id = DistinguishedFolderIdNameType.calendar
  14.     }
  15. };
  16. // add cookies to WS
  17. esb.CookieContainer = new CookieContainer();
  18. esb.CookieContainer.Add(cookies);
  19.  
  20. fit.Traversal = ItemQueryTraversalType.Shallow;
  21.  
  22. FindItemResponseType firt = esb.FindItem(fit);

In the DoFormbasedAuthentication method we get the cookie required for FBA authentication.

  1. private CookieCollection DoFormbasedAuthentication(String url, NetworkCredential credential)
  2. {
  3.     String server;
  4.     HttpWebRequest request;
  5.     HttpWebResponse response;
  6.     byte[] body;
  7.     Stream stream;
  8.  
  9.     try
  10.     {
  11.         Uri uri = new Uri(url);
  12.         // Get the server portion of the requested uri and appen the authentication dll from Exchange
  13.         server = String.Format("{0}://{1}/{2}", uri.Scheme, uri.DnsSafeHost, "CookieAuth.dll?Logon");
  14.  
  15.         // Initiate a new WebRequest to the given URI.
  16.         request = (HttpWebRequest)System.Net.WebRequest.Create(server);
  17.  
  18.         request.Method = "POST";
  19.         request.CookieContainer = new CookieContainer();
  20.         request.ContentType = "application/x-www-form-urlencoded";
  21.  
  22.         // Prepare the body of the request
  23.         body = Encoding.UTF8.GetBytes(string.Format("curl=Z2F&flags=0&forcedownlevel=0&formdir=6&trusted=4&username={1}\\{2}&password={3}",
  24.             uri, credential.Domain, credential.UserName, credential.Password));
  25.  
  26.         request.ContentLength = body.Length;
  27.  
  28.         // Send the request to the server
  29.         stream = request.GetRequestStream();
  30.         stream.Write(body, 0, body.Length);
  31.         stream.Close();
  32.  
  33.         // Get the response
  34.         response = (HttpWebResponse)request.GetResponse();
  35.  
  36.         // Check if the login was successful
  37.         if (response.Cookies.Count < 1) throw
  38.             new AuthenticationException("Failed to login to ISA. Be sure your user name, domain name and password are correct.");
  39.  
  40.         return response.Cookies;
  41.     }
  42.     catch (AuthenticationException ex)
  43.     {
  44.         throw;
  45.     }
  46.     catch (Exception ex)
  47.     {
  48.         throw new Exception("Failed to login to OWA. The following error occured: " + ex.Message, ex);
  49.     }
  50. }

You can see that I have included several form parameter beyond the credentials when assembling the request body. These parameters are hidden HTTP form fields or other form input fields from the source of the authentication page. You can verify that if you try to access the page using a browser and checking its source.

image

To add  some really SharePoint specific content to this post either, you might need similar tricks when accessing SharePoint web services (like the other EWS: Excel Web Services) on a FBA site. In this case you have to use the Login method of the Authentication web service to get the cookies you need as illustrated in this article:

How to: Set Various Credentials

Blog at WordPress.com.