Partager via


Implementing an Exchange ActiveSync client: provisioning

In this article
Prerequisites
Why provision?
Planning the implementation
Implementing the ActiveSync Provisioning protocol
Sample application
Digging deeper
Seeing it in action
What’s next?
Sample source

By Jason Johnston

Microsoft® Exchange ActiveSync® is a collection of protocols that enables mobile devices to synchronize and exchange messaging objects such as email, contacts, or calendar items with a server. The Microsoft Exchange Server protocol documentation includes a number of Exchange ActiveSync protocol specifications that provide the information you need to implement a fully-functional Exchange ActiveSync client or server. This article is the second in a series of articles that will provide detailed guidance for implementing an Exchange ActiveSync client by using the Exchange ActiveSync protocols. In this article, we will examine the process of provisioning an Exchange ActiveSync client. Our discussion will leverage information in the following Exchange Server protocol specification:

Note

The complete set of Exchange ActiveSync protocol specifications is available in the MSDN Library. If you’re new to using the Exchange Server Open Specifications, I’d highly recommend that you review Exploring the Microsoft Exchange Server Open Specifications for information about how the specifications are named and structured, and for tips that’ll help you derive maximum benefit from using the specifications.

Prerequisites

While the Exchange Server protocols can be implemented with any development tools and on any platform, this sample relies on the following items.

Item

Tool/Platform

Development environment

Microsoft® Visual Studio® 2010 Professional (using C#)

Platform

Microsoft® .NET Framework version 4.0

Exchange ActiveSync server

Microsoft® Exchange Server 2010 Service Pack 1 (SP1)

Because we are using Exchange Server 2010 SP1 as the server, we will focus on Exchange ActiveSync version 14.1. This sample will not include any special handling for previous Exchange ActiveSync versions.

Why provision?

Before we begin, it is important to understand exactly what "provisioning" means in the Exchange ActiveSync world, and why it is an important piece of the bigger Exchange ActiveSync picture. Provisioning is basically an information exchange between the client and the server. The client provides information about itself (device type, phone number, operating system, etc.), and the server responds with requirements to which the client must conform in order to access the user's mailbox. These requirements (collectively referred to as a "policy") are configured by an administrator and consist of such things as password requirements (length, complexity, etc.) and allowable device features (camera, text messaging, storage card, etc.).

So why is provisioning so important to an Exchange ActiveSync client implementation? Quite simply, the provisioning process is what enables the client to gain access to a mailbox. If the client cannot or will not conform to the requirements sent down by the server, the server in most cases will not allow any other Exchange ActiveSync commands to succeed. After the client acknowledges conformance with the policy, the server provides a key that the client can use to execute additional Exchange ActiveSync commands.

Planning the implementation

In practice, provisioning for Exchange ActiveSync is very straightforward. Essentially, it is a two-phase process in which each phase consists of a Provision command request submitted by the client, and a Provision command response returned by the server. For a detailed description of the process, see [MS-ASPROV] section 3.1.5.1.

In order to do provisioning correctly, an Exchange ActiveSync client needs to be able to initiate this process at any time. A client should initiate the provisioning process in the following scenarios:

  • The first time the client contacts the server.

  • The server returns a status code indicating the client should provision. This could be because the policy key the client is using is no longer valid, or the policy was updated on the server, requiring the client to re-request the current policy and reconfirm conformance. For more information about the status codes that indicate the client should provision again, see [MS-ASPROV].

  • The server requests a remote wipe. For more information about the remote wipe feature, see Understanding Remote Device Wipe.

This last scenario brings up an important point about the Provision command – its purpose is actually two-fold. Not only is it used to provision a client device, it is also used to process remote wipes.

Now that we know when to provision, let’s examine the high-level order of events in the provisioning process. The first phase is the retrieval of the current policy, and the second phase is the acknowledgment of the policy.

In the first phase, the client sends a Provision command request to the server to initiate the provisioning process. The server determines which policy is configured for the user account, and if the user is enabled for Exchange ActiveSync, the server sends a Provision command response that contains the policy information and a temporary key. Alternatively, if a remote wipe has been requested for this client, the server will instead return a response that indicates the client should perform a remote wipe.

The following figure shows phase 1 of provisioning.

Exchange ActiveSync client workflow – provisioning phase 1

Provisioning Phase 1

In the second phase, the client sends a Provision command request to acknowledge receipt of the policy information or remote wipe directive and indicates to the server whether or not it has complied. The server then returns a Provision command response to indicate success or failure of the client’s provisioning attempt, based on the client’s acknowledgment and the current settings on the server. If the server returns success in response to a policy acknowledgement, it also returns a permanent key that the client can use for other Exchange ActiveSync operations, such as synchronizing email.

The following figure shows phase 2 of provisioning.

Exchange ActiveSync client workflow – provisioning phase 2

Provisioning Phase 2

Now that we have an understanding of the two-phase process of provisioning, let’s dive deeper and look at implementing this process.

Implementing the ActiveSync Provisioning protocol

Note

The implementation discussed in this article builds on the sample implementation in Implementing an Exchange ActiveSync client: the transport mechanism. That sample will provide the transport for our Provision commands.

In order for a client device to implement the ActiveSync Provisioning protocol, it must be able to do the following:

  • Generate and send requests to initiate the provisioning process and receive responses.

  • Generate and send policy acknowledgements and receive responses.

  • Generate and send remote wipe acknowledgements and receive responses.

  • Parse ActiveSync policy data returned by the server.

Sample application

The sample application code (see the "Sample source" section later in this article) consists of several classes that can be used together to implement the ActiveSync Provisioning protocol. Let’s take a look at the classes implemented in the sample.

Class name

Description

ASPolicy

This class contains the functionality to parse the XML representation of an Exchange ActiveSync policy contained in a Provision command response. It can also detect if a remote wipe was requested.

ASProvisionRequest

This class extends the ASCommandRequest class to add functionality specific to the Provision command. This class can generate the XML body for Provision command requests that represent an initial provision request, a policy acknowledgement, or a remote wipe acknowledgement.

ASProvisionResponse

This class extends the ASCommandResponse class to add functionality specific to the Provision command. The class determines the status code returned by the server and uses the ASPolicy class to parse the rest of the response.

Device

This class represents the client as a set of properties (device ID, model, phone number, etc.). It generates the settings:DeviceInformation XML element for use in the initial provision request.

Digging deeper

In this sample we build on the functionality implemented in the example found in Implementing an Exchange ActiveSync client: the transport mechanism. By extending the ASCommandRequest and ASCommandResponse classes, we can focus on implementing the provisioning-specific functionality.

Requests

The ASProvisionRequest class implements Provision command requests. The ASProvisionRequest class serves to send both initial provision requests and acknowledgements for policy settings and remote wipes. To accommodate this, the class exposes two public Boolean properties: IsAcknowledgement and IsRemoteWipe. Setting either of these to true will change the structure of the XML body generated by the class.

For the initial provision request, the class exposes the public property ProvisionDevice, which is an instance of the Device class. The caller instantiates and sets the properties of the Device class to represent the client device.

Responses

The ASProvisionResponse class implements Provision command responses. It parses the parent provision:Provision XML element contained in the response and determines the value of the provision:Status element. The ASProvisionResponse class exposes the value as the public Int32 property Status. The XML response is then passed to a private instance of the ASPolicy class for further parsing.

The ASPolicy class uses the System.Xml namespace to load the XML response and parse the provision:Provision XML element as specified in [MS-ASPROV]. It checks for the presence of the provision:RemoteWipe element, and sets the public Boolean property RemoteWipeRequested to true if the element exists. If it does not exist, the class parses the provision:Policy element and exposes the settings as public properties.

Seeing it in action

The following example shows how to use the ASProvisionRequest and ASProvisionResponse classes to send an initial provision request and the appropriate acknowledgement.

Console.WriteLine("Sending Provision request...");
Device testDevice = new Device();

// Fill in details about the device
testDevice.Model = "Test 1.0";
testDevice.IMEI = "012345678901234";
testDevice.FriendlyName = "My Test Device";
testDevice.OperatingSystem = "Test OS 1.0";
testDevice.OperatingSystemLanguage = "English";
testDevice.PhoneNumber = "555-123-4567";
testDevice.MobileOperator = "My Phone Company";
testDevice.UserAgent = "TestAgent";

NetworkCredential cred = new NetworkCredential("contoso\\deviceuser", "poiqwe1!");

// Phase 1: Initial provision request
ASProvisionRequest initialRequest = new ASProvisionRequest();

initialRequest.ProvisionDevice = testDevice;
initialRequest.Credentials = cred;
initialRequest.DeviceID = "TestDeviceID";
initialRequest.DeviceType = "TestDeviceType";
initialRequest.ProtocolVersion = "14.1";
initialRequest.Server = "mail.contoso.com";
initialRequest.UseEncodedRequestLine = true;
initialRequest.User = "deviceuser";
initialRequest.UseSSL = true;

// Phase 1: Initial provision response
// We expect to either get a policy or a remote wipe request
ASProvisionResponse initialResponse = initialRequest.GetResponse() as ASProvisionResponse;

Console.WriteLine("Response status: {0}", initialResponse.Status);

if (initialResponse.PolicyLoaded == true)
{
    Console.WriteLine("Policy status: {0}", initialResponse.Policy.Status);

    if (initialResponse.Policy.RemoteWipeRequested == true)
    {
        Console.WriteLine("Remote wipe requested!");
    }
    else
    {
        Console.WriteLine("Policy settings:");
        Console.WriteLine("  AllowBlueTooth: {0}", initialResponse.Policy.AllowBlueTooth);
        Console.WriteLine("  AllowBrowser: {0}", initialResponse.Policy.AllowBrowser);
        Console.WriteLine("  AllowCamera: {0}", initialResponse.Policy.AllowCamera);
    }

    // Phase 2: Acknowledgement
    // This will either be an acknowledgement of a policy or
    // an acknowledgement of a remote wipe request
    Console.WriteLine("\r\nSending acknowledgement...");
    ASProvisionRequest policyAcknowledgement = new ASProvisionRequest();

    policyAcknowledgement.Credentials = cred;
    policyAcknowledgement.DeviceID = "TestDeviceID";
    policyAcknowledgement.DeviceType = "TestDeviceType";
    policyAcknowledgement.ProtocolVersion = "14.1";
    policyAcknowledgement.Server = "mail.contoso.com";
    policyAcknowledgement.UseEncodedRequestLine = true;
    policyAcknowledgement.User = "deviceuser";
    policyAcknowledgement.UseSSL = true;

    policyAcknowledgement.Status = (Int32)ASProvisionRequest.PolicyAcknowledgement.Success;
    policyAcknowledgement.PolicyKey = initialResponse.Policy.PolicyKey;

    if (initialResponse.Policy.RemoteWipeRequested == true)
    {
        policyAcknowledgement.IsRemoteWipe = true;
    }
    else
    {
        policyAcknowledgement.IsAcknowledgement = true;
    }

    // Phase 2: Acknowledgement response
    ASProvisionResponse acknowledgementResponse = policyAcknowledgement.GetResponse() as ASProvisionResponse;
    Console.WriteLine("Acknowledgement response status: {0}", acknowledgementResponse.Status);

    if (policyAcknowledgement.IsAcknowledgement == true)
    {
        Console.WriteLine("Permanent policy key: {0}", acknowledgementResponse.Policy.PolicyKey);
    }
}

The following output is generated by the previous example.

Sending Provision request...
Response status: 1
Policy status: 1
Policy settings:
  AllowBlueTooth: 2
  AllowBrowser: True
  AllowCamera: True

Sending acknowledgement...
Acknowledgement response status: 1
Permanent policy key: 2559020928

What’s next?

Now that we understand how an Exchange ActiveSync client uses the provisioning process to gain access to a mailbox, we can move on to the "Sync" part of Exchange ActiveSync. The next article in this series will look at implementing some basic email synchronization scenarios. Stay tuned!

Sample source

For convenience, the following is the complete C# source code for the classes discussed in this article.

ASPolicy.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;

namespace TestApp
{
    class ASPolicy
    {
        #region Enumerations
        public enum EncryptionAlgorithm
        {
            TripleDES = 0,
            DES = 1,
            RC2_128bit = 2,
            RC2_64bit = 3,
            RC2_40bit = 4
        }

        public enum SigningAlgorithm
        {
            SHA1 = 0,
            MD5 = 1
        }

        public enum CalendarAgeFilter
        {
            ALL = 0,
            TWO_WEEKS = 4,
            ONE_MONTH = 5,
            THREE_MONTHS = 6,
            SIX_MONTHS = 7
        }

        public enum MailAgeFilter
        {
            ALL = 0,
            ONE_DAY = 1,
            THREE_DAYS = 2,
            ONE_WEEK = 3,
            TWO_WEEKS = 4,
            ONE_MONTH = 5
        }

        public enum PolicyStatus
        {
            Success = 1,
            NoPolicyDefined = 2,
            PolicyTypeUnknown = 3,
            PolicyDataCorrupt = 4,
            PolicyKeyMismatch = 5
        }
        #endregion

        private Int32 status = 0;
        private UInt32 policyKey = 0;
        private byte allowBlueTooth = 0;
        private bool allowBrowser = false;
        private bool allowCamera = false;
        private bool allowConsumerEmail = false;
        private bool allowDesktopSync = false;
        private bool allowHTMLEmail = false;
        private bool allowInternetSharing = false;
        private bool allowIrDA = false;
        private bool allowPOPIMAPEmail = false;
        private bool allowRemoteDesktop = false;
        private bool allowSimpleDevicePassword = false;
        private Int32 allowSMIMEEncryptionAlgorithmNegotiation = 0;
        private bool allowSMIMESoftCerts = false;
        private bool allowStorageCard = false;
        private bool allowTextMessaging = false;
        private bool allowUnsignedApplications = false;
        private bool allowUnsignedInstallationPackages = false;
        private bool allowWifi = false;
        private bool alphanumericDevicePasswordRequired = false;
        private bool attachmentsEnabled = false;
        private bool devicePasswordEnabled = false;
        private UInt32 devicePasswordExpiration = 0;
        private UInt32 devicePasswordHistory = 0;
        private UInt32 maxAttachmentSize = 0;
        private UInt32 maxCalendarAgeFilter = 0;
        private UInt32 maxDevicePasswordFailedAttempts = 0;
        private UInt32 maxEmailAgeFilter = 0;
        private Int32 maxEmailBodyTruncationSize = -1;
        private Int32 maxEmailHTMLBodyTruncationSize = -1;
        private UInt32 maxInactivityTimeDeviceLock = 0;
        private byte minDevicePasswordComplexCharacters = 1;
        private byte minDevicePasswordLength = 1;
        private bool passwordRecoveryEnabled = false;
        private bool requireDeviceEncryption = false;
        private bool requireEncryptedSMIMEMessages = false;
        private Int32 requireEncryptionSMIMEAlgorithm = 0;
        private bool requireManualSyncWhenRoaming = false;
        private Int32 requireSignedSMIMEAlgorithm = 0;
        private bool requireSignedSMIMEMessages = false;
        private bool requireStorageCardEncryption = false;

        private string[] approvedApplicationList = null;
        private string[] unapprovedInROMApplicationList = null;

        private bool remoteWipeRequested = false;


        #region Property Accessors
        public Int32 Status
        {
            get
            {
                return status;
            }
        }

        public UInt32 PolicyKey
        {
            get
            {
                return policyKey;
            }
        }

        public byte AllowBlueTooth 
        {
            get
            {
                return allowBlueTooth;
            }
        }

        public bool AllowBrowser
        {
            get 
            {
                return allowBrowser;
            }
        }

        public bool AllowCamera
        {
            get
            {
                return allowCamera;
            }
        }

        public bool AllowConsumerEmail
        {
            get
            {
                return allowConsumerEmail;
            }
        }

        public bool AllowDesktopSync
        {
            get
            {
                return allowDesktopSync;
            }
        }

        public bool AllowHTMLEmail
        {
            get
            {
                return allowHTMLEmail;
            }
        }

        public bool AllowInternetSharing
        {
            get
            {
                return allowInternetSharing;
            }
        }

        public bool AllowIrDA
        {
            get
            {
                return allowIrDA;
            }
        }

        public bool AllowPOPIMAPEmail
        {
            get
            {
                return allowPOPIMAPEmail;
            }
        }

        public bool AllowRemoteDesktop
        {
            get
            {
                return allowRemoteDesktop;
            }
        }

        public bool AllowSimpleDevicePassword
        {
            get
            {
                return allowSimpleDevicePassword;
            }
        }

        public Int32 AllowSMIMEEncryptionAlgorithmNegotiation
        {
            get
            {
                return allowSMIMEEncryptionAlgorithmNegotiation;
            }
        }

        public bool AllowSMIMESoftCerts
        {
            get
            {
                return allowSMIMESoftCerts;
            }
        }

        public bool AllowStorageCard
        {
            get
            {
                return allowStorageCard;
            }
        }

        public bool AllowTextMessaging
        {
            get
            {
                return allowTextMessaging;
            }
        }

        public bool AllowUnsignedApplications
        {
            get
            {
                return allowUnsignedApplications;
            }
        }

        public bool AllowUnsignedInstallationPackages
        {
            get
            {
                return allowUnsignedInstallationPackages;
            }
        }

        public bool AllowWifi
        {
            get
            {
                return allowWifi;
            }
        }

        public bool AlphanumericDevicePasswordRequired
        {
            get
            {
                return alphanumericDevicePasswordRequired;
            }
        }

        public bool AttachmentsEnabled
        {
            get
            {
                return attachmentsEnabled;
            }
        }

        public bool DevicePasswordEnabled
        {
            get
            {
                return devicePasswordEnabled;
            }
        }

        public UInt32 DevicePasswordExpiration
        {
            get
            {
                return devicePasswordExpiration;
            }
        }

        public UInt32 DevicePasswordHistory
        {
            get
            {
                return devicePasswordHistory;
            }
        }

        public UInt32 MaxAttachmentSize
        {
            get
            {
                return maxAttachmentSize;
            }
        }

        public UInt32 MaxCalendarAgeFilter
        {
            get
            {
                return maxCalendarAgeFilter;
            }
        }

        public UInt32 MaxDevicePasswordFailedAttempts
        {
            get
            {
                return maxDevicePasswordFailedAttempts;
            }
        }

        public UInt32 MaxEmailAgeFilter
        {
            get
            {
                return maxEmailAgeFilter;
            }
        }

        public Int32 MaxEmailBodyTruncationSize
        {
            get
            {
                return maxEmailBodyTruncationSize;
            }
        }

        public Int32 MaxEmailHTMLBodyTruncationSize
        {
            get
            {
                return maxEmailHTMLBodyTruncationSize;
            }
        }

        public UInt32 MaxInactivityTimeDeviceLock
        {
            get
            {
                return maxInactivityTimeDeviceLock;
            }
        }

        public byte MinDevicePasswordComplexCharacters
        {
            get
            {
                return minDevicePasswordComplexCharacters;
            }
        }

        public byte MinDevicePasswordLength
        {
            get
            {
                return minDevicePasswordLength;
            }
        }

        public bool PasswordRecoveryEnabled
        {
            get
            {
                return passwordRecoveryEnabled;
            }
        }

        public bool RequireDeviceEncryption
        {
            get
            {
                return requireDeviceEncryption;
            }
        }

        public bool RequireEncryptedSMIMEMessages
        {
            get
            {
                return requireEncryptedSMIMEMessages;
            }
        }

        public Int32 RequireEncryptionSMIMEAlgorithm
        {
            get
            {
                return requireEncryptionSMIMEAlgorithm;
            }
        }

        public bool RequireManualSyncWhenRoaming
        {
            get
            {
                return requireManualSyncWhenRoaming;
            }
        }

        public Int32 RequireSignedSMIMEAlgorithm
        {
            get
            {
                return requireSignedSMIMEAlgorithm;
            }
        }

        public bool RequireSignedSMIMEMessages
        {
            get
            {
                return requireSignedSMIMEMessages;
            }
        }

        public bool RequireStorageCardEncryption
        {
            get
            {
                return requireStorageCardEncryption;
            }
        }

        public string[] ApprovedApplicationList
        {
            get
            {
                return approvedApplicationList;
            }
        }

        public string[] UnapprovedInROMApplicationList
        {
            get
            {
                return unapprovedInROMApplicationList;
            }
        }

        public bool RemoteWipeRequested
        {
            get
            {
                return remoteWipeRequested;
            }
        }

        #endregion 
        
        public bool LoadXML(string strXML)
        {
            XmlDocument xmlDoc = new XmlDocument();

            try
            {
                xmlDoc.LoadXml(strXML);

                XmlNamespaceManager xmlNsMgr = new XmlNamespaceManager(xmlDoc.NameTable);
                xmlNsMgr.AddNamespace("provision", "Provision");

                XmlNode remoteWipeNode = xmlDoc.SelectSingleNode(".//provision:RemoteWipe", xmlNsMgr);
                if (remoteWipeNode != null)
                {
                    remoteWipeRequested = true;
                    return true;
                }

                XmlNode policyNode = xmlDoc.SelectSingleNode(".//provision:Policy", xmlNsMgr);

                if (policyNode != null)
                {
                    XmlNode policyTypeNode = policyNode.SelectSingleNode("provision:PolicyType", xmlNsMgr);
                    if (policyTypeNode != null && policyTypeNode.InnerText == "MS-EAS-Provisioning-WBXML")
                    {
                        XmlNode policyStatusNode = policyNode.SelectSingleNode("provision:Status", xmlNsMgr);
                        if (policyStatusNode != null)
                            status = XmlConvert.ToInt32(policyStatusNode.InnerText);

                        XmlNode policyKeyNode = policyNode.SelectSingleNode("provision:PolicyKey", xmlNsMgr);
                        if (policyKeyNode != null)
                            policyKey = XmlConvert.ToUInt32(policyKeyNode.InnerText);

                        XmlNode provisioningDocNode = policyNode.SelectSingleNode(".//provision:EASProvisionDoc", xmlNsMgr);
                        if (provisioningDocNode != null)
                        {
                            foreach (XmlNode policySettingNode in provisioningDocNode.ChildNodes)
                            {
                                switch (policySettingNode.LocalName)
                                {
                                    case ("AllowBluetooth"):
                                        if (policySettingNode.InnerText != "")
                                            allowBlueTooth = XmlConvert.ToByte(policySettingNode.InnerText);
                                        break;
                                    case ("AllowBrowser"):
                                        if (policySettingNode.InnerText != "")
                                            allowBrowser = XmlConvert.ToBoolean(policySettingNode.InnerText);
                                        break;
                                    case ("AllowCamera"):
                                        if (policySettingNode.InnerText != "")
                                            allowCamera = XmlConvert.ToBoolean(policySettingNode.InnerText);
                                        break;
                                    case ("AllowConsumerEmail"):
                                        if (policySettingNode.InnerText != "")
                                            allowConsumerEmail = XmlConvert.ToBoolean(policySettingNode.InnerText);
                                        break;
                                    case ("AllowDesktopSync"):
                                        if (policySettingNode.InnerText != "")
                                            allowDesktopSync = XmlConvert.ToBoolean(policySettingNode.InnerText);
                                        break;
                                    case ("AllowHTMLEmail"):
                                        if (policySettingNode.InnerText != "")
                                            allowHTMLEmail = XmlConvert.ToBoolean(policySettingNode.InnerText);
                                        break;
                                    case ("AllowInternetSharing"):
                                        if (policySettingNode.InnerText != "")
                                            allowInternetSharing = XmlConvert.ToBoolean(policySettingNode.InnerText);
                                        break;
                                    case ("AllowIrDA"):
                                        if (policySettingNode.InnerText != "")
                                            allowIrDA = XmlConvert.ToBoolean(policySettingNode.InnerText);
                                        break;
                                    case ("AllowPOPIMAPEmail"):
                                        if (policySettingNode.InnerText != "")
                                            allowPOPIMAPEmail = XmlConvert.ToBoolean(policySettingNode.InnerText);
                                        break;
                                    case ("AllowRemoteDesktop"):
                                        if (policySettingNode.InnerText != "")
                                            allowRemoteDesktop = XmlConvert.ToBoolean(policySettingNode.InnerText);
                                        break;
                                    case ("AllowSimpleDevicePassword"):
                                        if (policySettingNode.InnerText != "")
                                            allowSimpleDevicePassword = XmlConvert.ToBoolean(policySettingNode.InnerText);
                                        break;
                                    case ("AllowSMIMEEncryptionAlgorithmNegotiation"):
                                        if (policySettingNode.InnerText != "")
                                            allowSMIMEEncryptionAlgorithmNegotiation = XmlConvert.ToInt32(policySettingNode.InnerText);
                                        break;
                                    case ("AllowSMIMESoftCerts"):
                                        if (policySettingNode.InnerText != "")
                                            allowSMIMESoftCerts = XmlConvert.ToBoolean(policySettingNode.InnerText);
                                        break;
                                    case ("AllowStorageCard"):
                                        if (policySettingNode.InnerText != "")
                                            allowStorageCard = XmlConvert.ToBoolean(policySettingNode.InnerText);
                                        break;
                                    case ("AllowTextMessaging"):
                                        if (policySettingNode.InnerText != "")
                                            allowTextMessaging = XmlConvert.ToBoolean(policySettingNode.InnerText);
                                        break;
                                    case ("AllowUnsignedApplications"):
                                        if (policySettingNode.InnerText != "")
                                            allowUnsignedApplications = XmlConvert.ToBoolean(policySettingNode.InnerText);
                                        break;
                                    case ("AllowUnsignedInstallationPackages"):
                                        if (policySettingNode.InnerText != "")
                                            allowUnsignedInstallationPackages = XmlConvert.ToBoolean(policySettingNode.InnerText);
                                        break;
                                    case ("AllowWiFi"):
                                        if (policySettingNode.InnerText != "")
                                            allowWifi = XmlConvert.ToBoolean(policySettingNode.InnerText);
                                        break;
                                    case ("AlphanumericDevicePasswordRequired"):
                                        if (policySettingNode.InnerText != "")
                                            alphanumericDevicePasswordRequired = XmlConvert.ToBoolean(policySettingNode.InnerText);
                                        break;
                                    case ("ApprovedApplicationList"):
                                        if (policySettingNode.InnerText != "")
                                            approvedApplicationList = ParseAppList(policySettingNode);
                                        break;
                                    case ("AttachmentsEnabled"):
                                        if (policySettingNode.InnerText != "")
                                            attachmentsEnabled = XmlConvert.ToBoolean(policySettingNode.InnerText);
                                        break;
                                    case ("DevicePasswordEnabled"):
                                        if (policySettingNode.InnerText != "")
                                            devicePasswordEnabled = XmlConvert.ToBoolean(policySettingNode.InnerText);
                                        break;
                                    case ("DevicePasswordExpiration"):
                                        if (policySettingNode.InnerText != "")
                                            devicePasswordExpiration = XmlConvert.ToUInt32(policySettingNode.InnerText);
                                        break;
                                    case ("DevicePasswordHistory"):
                                        if (policySettingNode.InnerText != "")
                                            devicePasswordHistory = XmlConvert.ToUInt32(policySettingNode.InnerText);
                                        break;
                                    case ("MaxAttachmentSize"):
                                        if (policySettingNode.InnerText != "")
                                            maxAttachmentSize = XmlConvert.ToUInt32(policySettingNode.InnerText);
                                        break;
                                    case ("MaxCalendarAgeFilter"):
                                        if (policySettingNode.InnerText != "")
                                            maxCalendarAgeFilter = XmlConvert.ToUInt32(policySettingNode.InnerText);
                                        break;
                                    case ("MaxDevicePasswordFailedAttempts"):
                                        if (policySettingNode.InnerText != "")
                                            maxDevicePasswordFailedAttempts = XmlConvert.ToUInt32(policySettingNode.InnerText);
                                        break;
                                    case ("MaxEmailAgeFilter"):
                                        if (policySettingNode.InnerText != "")
                                            maxEmailAgeFilter = XmlConvert.ToUInt32(policySettingNode.InnerText);
                                        break;
                                    case ("MaxEmailBodyTruncationSize"):
                                        if (policySettingNode.InnerText != "")
                                            maxEmailBodyTruncationSize = XmlConvert.ToInt32(policySettingNode.InnerText);
                                        break;
                                    case ("MaxEmailHTMLBodyTruncationSize"):
                                        if (policySettingNode.InnerText != "")
                                            maxEmailHTMLBodyTruncationSize = XmlConvert.ToInt32(policySettingNode.InnerText);
                                        break;
                                    case ("MaxInactivityTimeDeviceLock"):
                                        if (policySettingNode.InnerText != "")
                                            maxInactivityTimeDeviceLock = XmlConvert.ToUInt32(policySettingNode.InnerText);
                                        break;
                                    case ("MinDevicePasswordComplexCharacters"):
                                        if (policySettingNode.InnerText != "")
                                            minDevicePasswordComplexCharacters = XmlConvert.ToByte(policySettingNode.InnerText);
                                        break;
                                    case ("MinDevicePasswordLength"):
                                        if (policySettingNode.InnerText != "")
                                            minDevicePasswordLength = XmlConvert.ToByte(policySettingNode.InnerText);
                                        break;
                                    case ("PasswordRecoveryEnabled"):
                                        if (policySettingNode.InnerText != "")
                                            passwordRecoveryEnabled = XmlConvert.ToBoolean(policySettingNode.InnerText);
                                        break;
                                    case ("RequireDeviceEncryption"):
                                        if (policySettingNode.InnerText != "")
                                            requireDeviceEncryption = XmlConvert.ToBoolean(policySettingNode.InnerText);
                                        break;
                                    case ("RequireEncryptedSMIMEMessages"):
                                        if (policySettingNode.InnerText != "")
                                            requireEncryptedSMIMEMessages = XmlConvert.ToBoolean(policySettingNode.InnerText);
                                        break;
                                    case ("RequireEncryptionSMIMEAlgorithm"):
                                        if (policySettingNode.InnerText != "")
                                            requireEncryptionSMIMEAlgorithm = XmlConvert.ToInt32(policySettingNode.InnerText);
                                        break;
                                    case ("RequireManualSyncWhenRoaming"):
                                        if (policySettingNode.InnerText != "")
                                            requireManualSyncWhenRoaming = XmlConvert.ToBoolean(policySettingNode.InnerText);
                                        break;
                                    case ("RequireSignedSMIMEAlgorithm"):
                                        if (policySettingNode.InnerText != "")
                                            requireSignedSMIMEAlgorithm = XmlConvert.ToInt32(policySettingNode.InnerText);
                                        break;
                                    case ("RequireSignedSMIMEMessages"):
                                        if (policySettingNode.InnerText != "")
                                            requireSignedSMIMEMessages = XmlConvert.ToBoolean(policySettingNode.InnerText);
                                        break;
                                    case ("RequireStorageCardEncryption"):
                                        if (policySettingNode.InnerText != "")
                                            requireStorageCardEncryption = XmlConvert.ToBoolean(policySettingNode.InnerText);
                                        break;
                                    case ("UnapprovedInROMApplicationList"):
                                        if (policySettingNode.InnerText != "")
                                            unapprovedInROMApplicationList = ParseAppList(policySettingNode);
                                        break;
                                    default:
                                        break;
                                }
                            }
                        }
                    }
                }
            }
            catch (Exception)
            {
                return false;
            }

            return true;
        }

        private string[] ParseAppList(XmlNode appListNode)
        {
            List<string> appList = new List<string>();

            foreach (XmlNode appNode in appListNode.ChildNodes)
            {
                appList.Add(appNode.InnerText);
            }

            return appList.ToArray();
        }
    }
}

ASProvisionRequest.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Xml;

namespace TestApp
{
    class ASProvisionRequest : ASCommandRequest
    {
        public enum PolicyAcknowledgement
        {
            Success = 1,
            PartialSuccess = 2,
            PolicyIgnored = 3,
            ExternalManagement = 4
        }

        private static string strProvXMLNS = "provision";
        private static string strProvNS = "Provision";
        private static string strPolicyType = "MS-EAS-Provisioning-WBXML";

        private bool isAcknowledgement = false;
        private bool isRemoteWipe = false;
        private Int32 status = 0;

        private Device provisionDevice = null;

        #region Property Accessors

        public bool IsAcknowledgement
        {
            get
            {
                return isAcknowledgement;
            }
            set
            {
                isAcknowledgement = value;
            }
        }

        public bool IsRemoteWipe
        {
            get 
            {
                return isRemoteWipe;
            }
            set
            {
                isRemoteWipe = value;
            }
        }

        public Device ProvisionDevice
        {
            get
            {
                return provisionDevice;
            }
            set
            {
                provisionDevice = value;
            }
        }

        public Int32 Status
        {
            get
            {
                return status;
            }
            set
            {
                status = value;
            }
        }

        #endregion

        public ASProvisionRequest()
        {
            Command = "Provision";
        }

        protected override ASCommandResponse WrapHttpResponse(HttpWebResponse httpResp)
        {
            return new ASProvisionResponse(httpResp);
        }

        protected override void GenerateXMLPayload()
        {
            // If WBXML was explicitly set, we'll use that
            if (WbxmlBytes != null)
                return;

            // Otherwise, we'll use our properties to build the XML and then WBXML encode it
            XmlDocument provisionXML = new XmlDocument();

            XmlDeclaration xmlDec = provisionXML.CreateXmlDeclaration("1.0", "utf-8", null);
            provisionXML.InsertBefore(xmlDec, null);

            XmlNode provisionNode = provisionXML.CreateElement(strProvXMLNS, "Provision", strProvNS);
            provisionNode.Prefix = strProvXMLNS;
            provisionXML.AppendChild(provisionNode);

            if (isRemoteWipe)
            {
                // Build response to RemoteWipe request
                XmlNode remoteWipeNode = provisionXML.CreateElement(strProvXMLNS, "RemoteWipe", strProvNS);
                remoteWipeNode.Prefix = strProvXMLNS;
                provisionNode.AppendChild(remoteWipeNode);

                // Always return success for remote wipe
                XmlNode statusNode = provisionXML.CreateElement(strProvXMLNS, "Status", strProvNS);
                statusNode.Prefix = strProvXMLNS;
                statusNode.InnerText = "1";
                remoteWipeNode.AppendChild(statusNode);
            }

            else
            {
                if (!isAcknowledgement)
                {
                    XmlNode deviceNode = provisionXML.ImportNode(provisionDevice.GetDeviceInformationNode(), true);
                    provisionNode.AppendChild(deviceNode);
                }

                XmlNode policiesNode = provisionXML.CreateElement(strProvXMLNS, "Policies", strProvNS);
                policiesNode.Prefix = strProvXMLNS;
                provisionNode.AppendChild(policiesNode);

                XmlNode policyNode = provisionXML.CreateElement(strProvXMLNS, "Policy", strProvNS);
                policyNode.Prefix = strProvXMLNS;
                policiesNode.AppendChild(policyNode);

                XmlNode policyTypeNode = provisionXML.CreateElement(strProvXMLNS, "PolicyType", strProvNS);
                policyTypeNode.Prefix = strProvXMLNS;
                policyTypeNode.InnerText = strPolicyType;
                policyNode.AppendChild(policyTypeNode);

                if (isAcknowledgement)
                {
                    // Need to also include policy key when acknowledging
                    XmlNode policyKeyNode = provisionXML.CreateElement(strProvXMLNS, "PolicyKey", strProvNS);
                    policyKeyNode.Prefix = strProvXMLNS;
                    policyKeyNode.InnerText = PolicyKey.ToString();
                    policyNode.AppendChild(policyKeyNode);

                    // Always return success for provisioning
                    XmlNode statusNode = provisionXML.CreateElement(strProvXMLNS, "Status", strProvNS);
                    statusNode.Prefix = strProvXMLNS;
                    statusNode.InnerText = status.ToString();
                    policyNode.AppendChild(statusNode);
                }
            }

            StringWriter sw = new StringWriter();
            XmlTextWriter xmlw = new XmlTextWriter(sw);
            xmlw.Formatting = Formatting.Indented;
            provisionXML.WriteTo(xmlw);
            xmlw.Flush();

            XmlString = sw.ToString();
        }
    }
}

ASProvisionResponse.cs

using System;
using System.Collections.Generic;

using System.Linq;
using System.Net;
using System.Text;
using System.Xml;

namespace TestApp
{
    class ASProvisionResponse : ASCommandResponse 
    {
        public enum ProvisionStatus
        {
            Success = 1,
            SyntaxError = 2,
            ServerError = 3,
            DeviceNotFullyProvisionable = 139,
            LegacyDeviceOnStrictPolicy = 141,
            ExternallyManagedDevicesNotAllowed = 145
        }

        private bool policyLoaded = false;
        private ASPolicy policy = null;
        private Int32 status = 0;

        public ASProvisionResponse(HttpWebResponse httpResponse) : base (httpResponse)
        {
            policy = new ASPolicy();
            policyLoaded = policy.LoadXML(XmlString);
            SetStatus();
        }

        #region Property Accessors

        public bool PolicyLoaded
        {
            get
            {
                return policyLoaded;
            }
        }

        public ASPolicy Policy
        {
            get
            {
                return policy;
            }
        }

        public Int32 Status
        {
            get
            {
                return status;
            }
        }

        #endregion

        private void SetStatus()
        {
            XmlDocument responseXml = new XmlDocument();
            responseXml.LoadXml(XmlString);

            XmlNamespaceManager xmlNsMgr = new XmlNamespaceManager(responseXml.NameTable);
            xmlNsMgr.AddNamespace("provision", "Provision");

            XmlNode provisionNode = responseXml.SelectSingleNode(".//provision:Provision", xmlNsMgr);
            XmlNode statusNode = null;
            if (provisionNode != null)
                statusNode = provisionNode.SelectSingleNode(".//provision:Status", xmlNsMgr);

            if (statusNode != null)
                status = XmlConvert.ToInt32(statusNode.InnerText);
        }
    }
}

Device.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

using System.Text;
using System.Xml;

namespace TestApp
{
    class Device
    {
        const string strSettingsXmlns = "settings";
        const string strSettingsNamespace = "Settings";

        private string deviceID = null;
        private string deviceType = null;
        private string model = null;
        private string IMEINumber = null;
        private string friendlyName = null;
        private string operatingSystem = null;
        private string operatingSystemLanguage = null;
        private string phoneNumber = null;
        private string mobileOperator = null;
        private string userAgent = null;

        #region Property Accessors
        public string DeviceID
        {
            get
            {
                return deviceID;
            }
            set
            {
                deviceID = value;
            }
        }

        public string DeviceType
        {
            get
            {
                return deviceType;
            }
            set
            {
                deviceType = value;
            }
        }

        public string Model
        {
            get
            {
                return model;
            }
            set
            {
                model = value;
            }
        }

        public string IMEI
        {
            get
            {
                return IMEINumber;
            }
            set
            {
                IMEINumber = value;
            }
        }

        public string FriendlyName
        {
            get
            {
                return friendlyName;
            }
            set
            {
                friendlyName = value;
            }
        }

        public string OperatingSystem
        {
            get
            {
                return operatingSystem;
            }
            set
            {
                operatingSystem = value;
            }
        }

        public string OperatingSystemLanguage
        {
            get
            {
                return operatingSystemLanguage;
            }
            set
            {
                operatingSystemLanguage = value;
            }
        }

        public string PhoneNumber
        {
            get
            {
                return phoneNumber;
            }
            set
            {
                phoneNumber = value;
            }
        }

        public string MobileOperator
        {
            get
            {
                return mobileOperator;
            }
            set
            {
                mobileOperator = value;
            }
        }

        public string UserAgent
        {
            get
            {
                return userAgent;
            }
            set
            {
                userAgent = value;
            }
        }
        #endregion

        public XmlNode GetDeviceInformationNode()
        {
            XmlDocument xmlDoc = new XmlDocument();

            XmlElement deviceInfoElement = xmlDoc.CreateElement(strSettingsXmlns, "DeviceInformation", strSettingsNamespace);
            xmlDoc.AppendChild(deviceInfoElement);

            XmlElement setElement = xmlDoc.CreateElement(strSettingsXmlns, "Set", strSettingsNamespace);
            deviceInfoElement.AppendChild(setElement);

            if (Model != null)
            {
                XmlElement modelElement = xmlDoc.CreateElement(strSettingsXmlns, "Model", strSettingsNamespace);
                modelElement.InnerText = Model;
                setElement.AppendChild(modelElement);
            }

            if (IMEI != null)
            {
                XmlElement IMEIElement = xmlDoc.CreateElement(strSettingsXmlns, "IMEI", strSettingsNamespace);
                IMEIElement.InnerText = IMEI;
                setElement.AppendChild(IMEIElement);
            }

            if (FriendlyName != null)
            {
                XmlElement friendlyNameElement = xmlDoc.CreateElement(strSettingsXmlns, "FriendlyName", strSettingsNamespace);
                friendlyNameElement.InnerText = FriendlyName;
                setElement.AppendChild(friendlyNameElement);
            }

            if (OperatingSystem != null)
            {
                XmlElement operatingSystemElement = xmlDoc.CreateElement(strSettingsXmlns, "OS", strSettingsNamespace);
                operatingSystemElement.InnerText = OperatingSystem;
                setElement.AppendChild(operatingSystemElement);
            }

            if (OperatingSystemLanguage != null)
            {
                XmlElement operatingSystemLanguageElement = xmlDoc.CreateElement(strSettingsXmlns, "OSLanguage", strSettingsNamespace);
                operatingSystemLanguageElement.InnerText = OperatingSystemLanguage;
                setElement.AppendChild(operatingSystemLanguageElement);
            }

            if (PhoneNumber != null)
            {
                XmlElement phoneNumberElement = xmlDoc.CreateElement(strSettingsXmlns, "PhoneNumber", strSettingsNamespace);
                phoneNumberElement.InnerText = PhoneNumber;
                setElement.AppendChild(phoneNumberElement);
            }

            if (MobileOperator != null)
            {
                XmlElement mobileOperatorElement = xmlDoc.CreateElement(strSettingsXmlns, "MobileOperator", strSettingsNamespace);
                mobileOperatorElement.InnerText = MobileOperator;
                setElement.AppendChild(mobileOperatorElement);
            }

            if (UserAgent != null)
            {
                XmlElement userAgentElement = xmlDoc.CreateElement(strSettingsXmlns, "UserAgent", strSettingsNamespace);
                userAgentElement.InnerText = UserAgent;
                setElement.AppendChild(userAgentElement);
            }

            return xmlDoc.DocumentElement;
        }
    }
}

See Also

Other Resources

[MS-ASPROV]: ActiveSync Provisioning Protocol Specification

Provisioning, policies, remote wipe, and the Allow/Block/Quarantine list in Exchange ActiveSync

Implementing an Exchange ActiveSync client: the transport mechanism

Understanding Exchange ActiveSync Mailbox Policies

Understanding Remote Device Wipe