Exchange ActiveSync Building Blocks – First Sync

Index for the series:
Exchange ActiveSync Building Blocks – Intro

Background reading (not a pre-requisite):
[MS-ASHTTP] – ActiveSync HTTP Protocol – OPTIONS
[MS-ASCMD] – ActiveSync Command Reference – FolderSync
Digging Into The Exchange ActiveSync Protocol

If you followed along with the Autodiscover coding session you have now passed the initial hurdle of locating an ActiveSync-enabled Exchange Server. So how about we try to make a connection with that server and fetch some information from it?

There are three steps you can take in this process, where the last one will also establish a synchronization partnership with Exchange:
– HTTP GET
– HTTP OPTIONS
– HTTP POST

Yes, that’s a very generic description, so let’s describe them further.

HTTP GET
This step is technically not required at all, and no harm will be done if you skip it completely. It is however a nice test to perform if you’re building a diagnostic utility, (like I do), or just want to separate sync issues from connectivity issues. A common Exchange admin troubleshooting step when verifying ActiveSync connectivity is to open https://fqdn/Microsoft-Server-ActiveSync in your browser. You’ll hopefully be prompted to provide credentials, and then be greeted with a 501 or 505 error. The purpose of doing so is to verify that Exchange handles your request properly even though you’re not getting any low-level diagnostic output. Doing the same in code is of course entirely feasible to do. I use it to troubleshoot the basic connectivity issues:
– Is the Exchange Server responding to requests?
– Are DNS lookups working, or do you see a different result using the IP address?
– Are you using a trusted SSL certificate? (And if so, is the chain correct?)
– Does your authentication mechanism work properly?

All those basic things really.

So, let’s make a small test app for this purpose as well.
image

Yes, I have chosen to make separate apps for the different methods covered here – not because you wouldn’t be able follow along if I did it all in one app, but to focus on one test at a time.

The code is pretty straightforward too:

private void btnTest_Click(object sender, EventArgs e)
{
   string strUsername = txtUsername.Text;
   string strDomain = txtDomain.Text;
   string strPassword = txtPassword.Text;
   string strServerAddress = txtServerAddress.Text;            

   string uri = "" + strServerAddress +
   "/Microsoft-Server-ActiveSync";
   txtOutput.Text += "Test #1:\r\n\r\n";

   HttpWebRequest webRequest =
   (HttpWebRequest)WebRequest.Create(uri);
   WebResponse webResponse = null;

   try {
       webRequest.Method = "GET";
       webRequest.UserAgent = "MobilityDojo.net";
       webRequest.Headers.Add("Authorization", "Basic " +
       getEncCredentials(strUsername, strPassword, strDomain));

       txtOutput.Text += "Testing following address:\r\n" + uri + "\r\n";

       webResponse = webRequest.GetResponse();
       if (webResponse == null)
       {
       }

   }
   catch (WebException ex)
   {
      //If we get a 501/505 response we're happy. //This is expected/correct behaviour for the HTTP GET test. if (ex.Status.ToString() == "ProtocolError")
      {
         HttpWebResponse resEx = (HttpWebResponse)ex.Response;
         int resCode = (int)resEx.StatusCode;
         if (resCode == 501 || resCode == 505)
         {
            txtOutput.Text += "501/505 - It's all good!";
         }
         else {
            txtOutput.Text += "Something went wrong!\r\n";
            txtOutput.Text += ex.Message.ToString() + "\r\n\r\n";
         }
      }
   }
}

HTTP OPTIONS

Technically this step is not required either, but it comes highly recommended if you want a decent Exchange ActiveSync client. (Both Microsoft and Apple do it in their implementations.) The OPTIONS command should be performed before attempting to sync as it is this step that will tell you things about the Exchange Server; most importantly which version the server happens to be running. I have seen EAS clients skipping this, and just opting for a default of 12.0 (Exchange 2007 RTM.) Even if your client does not implement higher level protocols this is just not a clean way of doing it.

If your client does happen to support all the latest and greatest features of Exchange Server you should not assume that all servers out in the wild are running the service pack that was released a week before your client. If you run an OPTIONS and the server returns 12.1 (Exchange 2007 with Service Pack) you should adjust the behavior of your client accordingly.

Another purpose of this method is that you will be able to handle more complex scenarios cleanly as well. Let’s say you connect to an Exchange 2007 Server initially. The Exchange infrastructure is then upgraded to Exchange 2010, and the mailbox is moved to a new server. The new server informs you of this as you connect after the move (through HTTP 302/451), and you do an Autodiscover to make sure you’re connecting to the correct server. As part of this you also think it might make sense to issue the OPTIONS command once more as well, (hey, it is a new server), and you learn that you’re no longer stuck with the old school stuff. You update your relationship with the server, and “magically” your client can do new things. There might be other obstacles, and things you need to consider to make this work entirely smooth client side, but do keep in it mind. You’re not being held back by the Exchange Server at least.

Made the output windows slightly larger to allow for more text in the result area:

image

There are of course a few lines of code that have changed as well:

private void btnTest_Click(object sender, EventArgs e)
{
   string strUsername = txtUsername.Text;
   string strDomain = txtDomain.Text;
   string strPassword = txtPassword.Text;
   string strServerAddress = txtServerAddress.Text;

   string uri = "" + strServerAddress +
   "/Microsoft-Server-ActiveSync";
   txtOutput.Text += "Test #1:\r\n\r\n";

   HttpWebRequest webRequest =
   (HttpWebRequest)WebRequest.Create(uri);
   WebResponse webResponse = null;

   webRequest.Method = "OPTIONS";
   webRequest.Headers.Add("Authorization", "Basic " + getEncCredentials(strUsername, strPassword, strDomain));

   try {
      webResponse = webRequest.GetResponse();
      if (webResponse == null)
      {
      }
      //No body, but we parse out the return headers int optionsHeadersCount = webResponse.Headers.Count;
      for (int i = 0; i < optionsHeadersCount; i++)
      {
         txtOutput.Text += webResponse.Headers.Keys[i].ToString()
         + ":" + webResponse.Headers.Get(i).ToString() + "\r\n";
      }
   }
   catch (WebException ex)
   {
      txtOutput.Text += "Something went wrong!\r\n";
      txtOutput.Text += ex.Message.ToString() + "\r\n\r\n";
   }
}

You’ll notice that all the info is returned as headers that we in this case just pipe directly to the output view:

Connection:Keep-Alive
Allow:OPTIONS,POST
MS-Server-ActiveSync:14.1
MS-ASProtocolVersions:2.0,2.1,2.5,12.0,12.1,14.0,14.1
MS-ASProtocolCommands:Sync,SendMail,SmartForward,SmartReply,
GetAttachment,GetHierarchy,CreateCollection,DeleteCollection,
MoveCollection,FolderSync,FolderCreate,FolderDelete,
FolderUpdate,MoveItems,GetItemEstimate,MeetingResponse,Search,
Settings,Ping,ItemOperations,Provision,ResolveRecipients,ValidateCert
Public:OPTIONS,POST
Content-Length:0
Cache-Control:private Date:Tue, 23 Aug 2011 20:39:51 GMT
Set-Cookie: long-hex-value
Server:Microsoft-IIS/7.5
X-AspNet-Version:2.0.50727
X-Powered-By:ASP.NET

HTTP POST

Up until now we’ve been doing preparatory work, but this would be where the actual action starts taking place. If we performed the first step successfully we know that all network level connectivity issues are sorted, and the second step told us what we needed about the Exchange Server we will be establishing a relationship with.

This means we can start building up an initial FolderSync command. This command will tell us things like the localized name of your inbox, your subfolders, calendar name, etc. It does not pull down any contents of these folders, but it will let you learn the folder structure, receive initial security policies, and just as important – let Exchange know who you are.

At this point an entry will be created, (if you don’t run into other issues), in Active Directory for your client (attached to the user account you authenticate with).

This is also where you start talking the ActiveSync language known as AS-WBXML. (Full name ActiveSync WAP Binary XML – I usually just shorten it to WBXML much like we often say English generically and just add US or UK where it’s necessary to be specific.) I will do a walkthrough of WBXML later, so for the time being you can just trust me that the static byte array I define is what you need for this initial FolderSync.

For this test you should ensure that you are not forcing any security policies, or requiring provisionable devices.

Your “Exchange ActiveSync Mailbox Policy” should look roughly like this on the “General” tab.

image

You’ll be familiar with the look-and-feel of the UI by now:

image

Let me type out the entire response for you:

Response body
jVL1R1NW20OH1I0GInnboksJ2OH2I0GKalenderJ8OH3I0GKladdJ3OH4I0G
KontakterJ9OH5I0GLoggJ11OH6I0GNews FeedJ12OH7I0GNotaterJ10OH8I0G
OppgaverJ7OH9I0GRSS FeedsJ12OH10I9GMobilityDojo.netJ12OH11I0G
Sendte elementerJ5OH12I0GSlettede elementerJ4OH13I0G
Suggested ContactsJ14OH14I0GSync IssuesJ12OH15I14G
ConflictsJ12OH16I14GLocal FailuresJ12OH17I14G
Server FailuresJ12OH18I0GSøppelpostJ12OH19I0GUtboksJ6OHRII0G
RecipientInfoJ19

This might look like something that comes out of a random text generator, (especially since it’s a liberal mix of Norwegian and English as well as a garbled Scandinavian character), but it tells me I have an Inbox (named “Innboks”), Tasks (“Oppgaver”), and a couple of other item types. And most importantly it confirms that I am able to read out actual mailbox data from an Exchange Server. Which is of course a good thing, and means we would be able to move forward performing an actual synchronization.

Any code to back it up? Why, yes, of course I have (he said in a cocky tone that is bound to backfire later on):

private void btnTest_Click(object sender, EventArgs e)
{
   string strUsername = txtUsername.Text;
   string strPassword = txtPassword.Text;
   string strDomain = txtDomain.Text;
   string strServerAddress = txtServerAddress.Text;

   string strEncCredentials =
   getEncCredentials(strUsername, strPassword, strDomain);

   string strDeviceId = "IMEI1234";
   string strDeviceType = "FakeDevice";
   string strUserAgent = "MobilityDojo.net";
   string strASVersion = "14.1";

   string uri = strServerAddress + "/Microsoft-Server-ActiveSync" + "?Cmd=FolderSync&User=" + strUsername + "&DeviceId=" + strDeviceId + "&DeviceType=" + strDeviceType;

   HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(uri);
   webRequest.Method = "POST";
   webRequest.Headers.Add("Authorization", "Basic " + strEncCredentials);
   webRequest.ContentType = "application/vnd.ms-sync.wbxml";
   webRequest.AllowAutoRedirect = false; 

   //Emulate the chosen AS version webRequest.Headers.Add("MS-ASProtocolVersion", strASVersion);

   webRequest.UserAgent = strUserAgent;

   //the wbxml required for initial FolderSync byte[] byteFolderSync =
   new byte[13] { 03, 01, 106, 00, 00, 07, 86, 82, 03, 48, 00, 01, 01 };

   webRequest.ContentLength = byteFolderSync.Length;

   try {
      Stream sw = webRequest.GetRequestStream();
      sw.Write(byteFolderSync, 0, byteFolderSync.Length);
      sw.Close();

      txtOutput.Text += "Testing FolderSync:\r\n";
      WebResponse webResponse = webRequest.GetResponse();
      if (webResponse == null)
      {
      }
      txtOutput.Text += webResponse.ResponseUri.PathAndQuery + "\r\n";

      System.Text.UTF8Encoding utf8Encoding =
      new System.Text.UTF8Encoding();
      StreamReader sr =
      new StreamReader(webResponse.GetResponseStream());

      txtOutput.Text += "Response body \r\n";
      string responseBody = sr.ReadToEnd().Trim();
      Byte[] byteResp = utf8Encoding.GetBytes(responseBody);

      //to be able to represent the response in a textbox //we need to parse the individual chars char charResp = '0';
      for (int i = 0; i < byteResp.Length; i++)
      {
         charResp = (char)byteResp[i];
         txtOutput.Text += charResp;
      }
      txtOutput.Text += "\r\n";
   }
   catch (WebException ex)
   {
      txtOutput.Text += "Something went wrong!\r\n";
      txtOutput.Text += ex.Message.ToString() + "\r\n\r\n";
   }
   txtOutput.Text += "\r\n";
}

I don’t think there’s much comment to append to the code snippet at the moment. As I said just accept the contents of the WBXML for now. Be aware that I have hard-coded 14.1 (Exchange 2010 SP1) as the ActiveSync version. If you are running some other server version you will need to change this accordingly. (If you set it to 12.0 it’ll still work with newer versions as well mind you. I’m not doing anything specific to any server version here.) Feel free to play around with changing the other variables too if you like.

If all goes well, you should also see a new device in your “inventory” view in Outlook Web Access/App after doing this:

image

A lot of the info is missing there, but that’s because we haven’t provided Exchange with the necessary details, but we’ll get back to this in a later post like so many other missing pieces of the puzzle Smile

If you have access to ADSI Edit you’ll find it in Active Directory by now as well just like I hinted a few paragraphs earlier:

image

Feeling dizzy yet? I believe we have covered some ground in this article, and it’s at this point that we’re starting to wrap up the basic stuff, and actually start doing something. Since we haven’t covered any error/exception handling yet you might be stuck at this point though – perhaps your lab environment is less forgiving than mine. I intend to be tackling that in the next post if nothing unforeseen occurs. In the meantime you are of course free to shoot out what kind of error you’re seeing if it fails horribly, and I might be able to help you out.

Downloads:
C# Source – HTTP_GET.cs
C# Source – HTTP_OPTIONS.cs
C# Source – HTTP_POST.cs
Visual Studio Solution – EAS_BB_Part-02_01.zip
Visual Studio Solution – EAS_BB_Part-02_02.zip
Visual Studio Solution – EAS_BB_Part-02_03.zip

3 thoughts on “Exchange ActiveSync Building Blocks – First Sync”

  1. Hello Andreas,

    I have read through many of the article posts on this website trying to understand an error that I am receiving on mobile phones. The specific error is: “The message was rejected by the server.”

    Here is the background details:
    The user’s have iPhones running iOS 8. Autodiscover is configured and working – automatically setups up the users on their phones. The server is running Exchange 2007. The user’s can send and receive messages fine without issue. When the user attempts to send a message over 15 MB (with attachments) their phone responds with “The message was rejected by the server.” I understand that ActiveSync runs through IIS7 and I have adjusted the maximum attachment sizes without much luck (ref: https://social.technet.microsoft.com/forums/exchange/en-US/a55a5769-8fb0-44dd-a92e-28de178493c2/activesync-maximum-attachment-download-size).

    I have tried to send and receive messages on the internal and external network from an iPhone and an Android without success – I receive the same error. I have reviewed message logging within EMC and it appears that the message never hits Exchange 2007. Any advice or guidance on where to go from here?

  2. Exchange 2007 can be real “fun” in general regarding attachment sizes since the policy can be configured on multiple levels.

    Am I right in assuming that the issue is limited to just ActiveSync, and that sending larger messages are no problem when using Outlook on a computer? (If it works outside EAS the policies on Exchange wouldn’t be the blocker.)

    The fix involving editing the IIS settings should work, (I have played around with this setting myself). You should take a look in the IIS logs to see if there are any traffic recorded from the devices. (You should be able to locate them by user string.)

Leave a Reply

Your email address will not be published. Required fields are marked *

*