Helper code: DeviceIdManager class

 

Applies To: Dynamics 365 (online), Dynamics 365 (on-premises), Dynamics CRM 2016, Dynamics CRM Online

The DeviceIdManager class registers a computing device with Microsoft account, through the generation of a device ID and password, and optionally stores that information in an encrypted format on the local disk for later reuse. This functionality is required when authenticating with Microsoft Dynamics 365 (online) through the Microsoft account identity provider. The device registration is stored in the %USERPROFILE%\LiveDeviceID folder on the computing device.

The primary class method to call from your code is LoadOrRegisterDevice. For an example of how the DeviceIdManager class is used, see the GetDeviceCredentials method in the Helper code: ServerConnection class topic.

A Microsoft Azure hosted application that must authenticate with Microsoft Dynamics 365 (online) should either set the PersistToFile property of DeviceIdManager to false or call the two-parameter LoadOrRegisterDevice method passing in the required device ID and password. In both cases, the DeviceIdManager won’t attempt to save the device ID and password to a file.

Download the Microsoft Dynamics CRM SDK package. The source code for the class can be found in the following location in the download package:

SDK\SampleCode\CS\HelperCode\DeviceIdManager.cs

SDK\SampleCode\VB\HelperCode\DeviceIdManager.vb

Requirements

For more information about the requirements for running the sample code provided in this SDK, see Use the sample and helper code.

Demonstrates

The DeviceIdManager class demonstrates how to manage the registration of computing devices with Microsoft account.

Example


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Net;
using System.Runtime.Serialization;
using System.Security.Cryptography;
using System.ServiceModel.Description;
using System.Text;
using System.Xml;
using System.Xml.Serialization;

namespace Microsoft.Crm.Services.Utility
{
    /// <summary>
    /// Management utility for the Device Id
    /// </summary>
    public static class DeviceIdManager
    {
        #region Fields
        private static readonly Random RandomInstance = new Random();

        public const int MaxDeviceNameLength = 24;
        public const int MaxDevicePasswordLength = 24;
        #endregion

        #region Constructor
        static DeviceIdManager()
        {
            PersistToFile = true;
        }
        #endregion

        #region Properties
        /// <summary>
        /// Indicates whether the registered device credentials should be persisted to the database
        /// </summary>
        public static bool PersistToFile { get; set; }

        /// <summary>
        /// Indicates that the credentials should be persisted to the disk if registration fails with DeviceAlreadyExists.
        /// </summary>
        /// <remarks>
        /// If the device already exists, there is a possibility that the credentials are the same as the current credentials that
        /// are being registered. This is especially true in automated environments where the same credentials are used continually (to avoid
        /// registering spurious device credentials.
        /// </remarks>
        public static bool PersistIfDeviceAlreadyExists { get; set; }
        #endregion

        #region Methods
        /// <summary>
        /// Loads the device credentials (if they exist).
        /// </summary>
        /// <returns></returns>
        public static ClientCredentials LoadOrRegisterDevice()
        {
            return LoadOrRegisterDevice(null);
        }

        /// <summary>
        /// Loads the device credentials (if they exist).
        /// </summary>
        /// <param name="deviceName">Device name that should be registered</param>
        /// <param name="devicePassword">Device password that should be registered</param>
        public static ClientCredentials LoadOrRegisterDevice(string deviceName, string devicePassword)
        {
            return LoadOrRegisterDevice(null, deviceName, devicePassword);
        }

        /// <summary>
        /// Loads the device credentials (if they exist).
        /// </summary>
        /// <param name="issuerUri">URL for the current token issuer</param>
        /// <remarks>
        /// The issuerUri can be retrieved from the IServiceConfiguration interface's CurrentIssuer property.
        /// </remarks>
        public static ClientCredentials LoadOrRegisterDevice(Uri issuerUri)
        {
            return LoadOrRegisterDevice(issuerUri, null, null);
        }

        /// <summary>
        /// Loads the device credentials (if they exist).
        /// </summary>
        /// <param name="issuerUri">URL for the current token issuer</param>
        /// <param name="deviceName">Device name that should be registered</param>
        /// <param name="devicePassword">Device password that should be registered</param>
        /// <remarks>
        /// The issuerUri can be retrieved from the IServiceConfiguration interface's CurrentIssuer property.
        /// </remarks>
        public static ClientCredentials LoadOrRegisterDevice(Uri issuerUri, string deviceName, string devicePassword)
        {
            ClientCredentials credentials = LoadDeviceCredentials(issuerUri);
            if (null == credentials)
            {
                credentials = RegisterDevice(Guid.NewGuid(), issuerUri, deviceName, devicePassword);
            }

            return credentials;
        }

        /// <summary>
        /// Registers the given device with Microsoft account with a random application ID
        /// </summary>
        /// <returns>ClientCredentials that were registered</returns>
        public static ClientCredentials RegisterDevice()
        {
            return RegisterDevice(Guid.NewGuid());
        }

        /// <summary>
        /// Registers the given device with Microsoft account
        /// </summary>
        /// <param name="applicationId">ID for the application</param>
        /// <returns>ClientCredentials that were registered</returns>
        public static ClientCredentials RegisterDevice(Guid applicationId)
        {
            return RegisterDevice(applicationId, (Uri)null);
        }

        /// <summary>
        /// Registers the given device with Microsoft account
        /// </summary>
        /// <param name="applicationId">ID for the application</param>
        /// <param name="issuerUri">URL for the current token issuer</param>
        /// <returns>ClientCredentials that were registered</returns>
        /// <remarks>
        /// The issuerUri can be retrieved from the IServiceConfiguration interface's CurrentIssuer property.
        /// </remarks>
        public static ClientCredentials RegisterDevice(Guid applicationId, Uri issuerUri)
        {
            return RegisterDevice(applicationId, issuerUri, null, null);
        }

        /// <summary>
        /// Registers the given device with Microsoft account
        /// </summary>
        /// <param name="applicationId">ID for the application</param>
        /// <param name="deviceName">Device name that should be registered</param>
        /// <param name="devicePassword">Device password that should be registered</param>
        /// <returns>ClientCredentials that were registered</returns>
        public static ClientCredentials RegisterDevice(Guid applicationId, string deviceName, string devicePassword)
        {
            return RegisterDevice(applicationId, (Uri)null, deviceName, devicePassword);
        }

        /// <summary>
        /// Registers the given device with Microsoft account
        /// </summary>
        /// <param name="applicationId">ID for the application</param>
        /// <param name="issuerUri">URL for the current token issuer</param>
        /// <param name="deviceName">Device name that should be registered</param>
        /// <param name="devicePassword">Device password that should be registered</param>
        /// <returns>ClientCredentials that were registered</returns>
        /// <remarks>
        /// The issuerUri can be retrieved from the IServiceConfiguration interface's CurrentIssuer property.
        /// </remarks>
        public static ClientCredentials RegisterDevice(Guid applicationId, Uri issuerUri, string deviceName, string devicePassword)
        {
            if (string.IsNullOrEmpty(deviceName) &amp;&amp; !PersistToFile)
            {
                throw new ArgumentNullException("deviceName", "If PersistToFile is false, then deviceName must be specified.");
            }
            else if (string.IsNullOrEmpty(deviceName) != string.IsNullOrEmpty(devicePassword))
            {
                throw new ArgumentNullException("deviceName", "Either deviceName/devicePassword should both be specified or they should be null.");
            }

            LiveDevice device = GenerateDevice(deviceName, devicePassword);
            return RegisterDevice(applicationId, issuerUri, device);
        }

        /// <summary>
        /// Loads the device's credentials from the file system
        /// </summary>
        /// <returns>Device Credentials (if set) or null</returns>
        public static ClientCredentials LoadDeviceCredentials()
        {
            return LoadDeviceCredentials(null);
        }

        /// <summary>
        /// Loads the device's credentials from the file system
        /// </summary>
        /// <param name="issuerUri">URL for the current token issuer</param>
        /// <returns>Device Credentials (if set) or null</returns>
        /// <remarks>
        /// The issuerUri can be retrieved from the IServiceConfiguration interface's CurrentIssuer property.
        /// </remarks>
        public static ClientCredentials LoadDeviceCredentials(Uri issuerUri)
        {
            //If the credentials should not be persisted to a file, then they won't be present on the disk.
            if (!PersistToFile)
            {
                return null;
            }

            EnvironmentConfiguration environment = DiscoverEnvironmentInternal(issuerUri);

            LiveDevice device = ReadExistingDevice(environment);
            if (null == device || null == device.User)
            {
                return null;
            }

            return device.User.ToClientCredentials();
        }

        /// <summary>
        /// Discovers the Microsoft account environment based on the Token Issuer
        /// </summary>
        public static string DiscoverEnvironment(Uri issuerUri)
        {
            return DiscoverEnvironmentInternal(issuerUri).Environment;
        }
        #endregion

        #region Private Methods
        private static EnvironmentConfiguration DiscoverEnvironmentInternal(Uri issuerUri)
        {
            if (null == issuerUri)
            {
                return new EnvironmentConfiguration(EnvironmentType.LiveDeviceID, "login.live.com", null);
            }

            Dictionary<EnvironmentType, string> searchList = new Dictionary<EnvironmentType, string>();
            searchList.Add(EnvironmentType.LiveDeviceID, "login.live");
            searchList.Add(EnvironmentType.OrgDeviceID, "login.microsoftonline");

            foreach (KeyValuePair<EnvironmentType, string> searchPair in searchList)
            {
                if (issuerUri.Host.Length > searchPair.Value.Length &amp;&amp;
                    issuerUri.Host.StartsWith(searchPair.Value, StringComparison.OrdinalIgnoreCase))
                {
                    string environment = issuerUri.Host.Substring(searchPair.Value.Length);

                    //Parse out the environment
                    if ('-' == environment[0])
                    {
                        int separatorIndex = environment.IndexOf('.', 1);
                        if (-1 != separatorIndex)
                        {
                            environment = environment.Substring(1, separatorIndex - 1);
                        }
                        else
                        {
                            environment = null;
                        }
                    }
                    else
                    {
                        environment = null;
                    }

                    return new EnvironmentConfiguration(searchPair.Key, issuerUri.Host, environment);
                }
            }

            //In all other cases the environment is either not applicable or it is a production system
            return new EnvironmentConfiguration(EnvironmentType.LiveDeviceID, issuerUri.Host, null);
        }

        private static void Serialize<T>(Stream stream, T value)
        {
            XmlSerializer serializer = new XmlSerializer(typeof(T), string.Empty);

            XmlSerializerNamespaces xmlNamespaces = new XmlSerializerNamespaces();
            xmlNamespaces.Add(string.Empty, string.Empty);

            serializer.Serialize(stream, value, xmlNamespaces);
        }

        private static T Deserialize<T>(string operationName, Stream stream)
        {
            //Read the XML into memory so that the data can be used in an exception if necessary
            using (StreamReader reader = new StreamReader(stream))
            {
                return Deserialize<T>(operationName, reader.ReadToEnd());
            }
        }

        private static T Deserialize<T>(string operationName, string xml)
        {
            //Attempt to deserialize the data. If deserialization fails, include the XML in the exception that is thrown for further
            //investigation
            using (StringReader reader = new StringReader(xml))
            {
                try
                {
                    XmlSerializer serializer = new XmlSerializer(typeof(T), string.Empty);
                    return (T)serializer.Deserialize(reader);
                }
                catch (InvalidOperationException ex)
                {
                    throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture,
                        "Unable to Deserialize XML (Operation = {0}):{1}{2}", operationName, Environment.NewLine, xml), ex);
                }
            }
        }

        private static FileInfo GetDeviceFile(EnvironmentConfiguration environment)
        {
            return new FileInfo(string.Format(CultureInfo.InvariantCulture, LiveIdConstants.FileNameFormat,
                environment.Type,
                string.IsNullOrEmpty(environment.Environment) ? null : "-" + environment.Environment.ToUpperInvariant()));
        }

        private static ClientCredentials RegisterDevice(Guid applicationId, Uri issuerUri, LiveDevice device)
        {
            EnvironmentConfiguration environment = DiscoverEnvironmentInternal(issuerUri);

            DeviceRegistrationRequest request = new DeviceRegistrationRequest(applicationId, device);

            string url = string.Format(CultureInfo.InvariantCulture, LiveIdConstants.RegistrationEndpointUriFormat,
                environment.HostName);

            DeviceRegistrationResponse response = ExecuteRegistrationRequest(url, request);
            if (!response.IsSuccess)
            {
                bool throwException = true;
                if (DeviceRegistrationErrorCode.DeviceAlreadyExists == response.Error.RegistrationErrorCode)
                {
                    if (!PersistToFile)
                    {
                        //If the file is not persisted, the registration will always occur (since the credentials are not
                        //persisted to the disk. However, the credentials may already exist. To avoid an exception being continually
                        //processed by the calling user, DeviceAlreadyExists will be ignored if the credentials are not persisted to the disk.
                        return device.User.ToClientCredentials();
                    }
                    else if (PersistIfDeviceAlreadyExists)
                    {
                        // This flag indicates that the 
                        throwException = false;
                    }
                }

                if (throwException)
                {
                    throw new DeviceRegistrationFailedException(response.Error.RegistrationErrorCode, response.ErrorSubCode);
                }
            }

            if (PersistToFile || PersistIfDeviceAlreadyExists)
            {
                WriteDevice(environment, device);
            }

            return device.User.ToClientCredentials();
        }

        private static LiveDevice GenerateDevice(string deviceName, string devicePassword)
        {
            // If the deviceName hasn't been specified, it should be generated using random characters.
            DeviceUserName userNameCredentials;
            if (string.IsNullOrEmpty(deviceName))
            {
                userNameCredentials = GenerateDeviceUserName();
            }
            else
            {
                userNameCredentials = new DeviceUserName() { DeviceName = deviceName, DecryptedPassword = devicePassword };
            }

            return new LiveDevice() { User = userNameCredentials, Version = 1 };
        }

        private static LiveDevice ReadExistingDevice(EnvironmentConfiguration environment)
        {
            //Retrieve the file info
            FileInfo file = GetDeviceFile(environment);
            if (!file.Exists)
            {
                return null;
            }

            using (FileStream stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                return Deserialize<LiveDevice>("Loading Device Credentials from Disk", stream);
            }
        }

        private static void WriteDevice(EnvironmentConfiguration environment, LiveDevice device)
        {
            FileInfo file = GetDeviceFile(environment);
            if (!file.Directory.Exists)
            {
                file.Directory.Create();
            }

            using (FileStream stream = file.Open(FileMode.CreateNew, FileAccess.Write, FileShare.None))
            {
                Serialize(stream, device);
            }
        }

        private static DeviceRegistrationResponse ExecuteRegistrationRequest(string url, DeviceRegistrationRequest registrationRequest)
        {
            //Create the request that will submit the request to the server
            WebRequest request = WebRequest.Create(url);
            request.ContentType = "application/soap+xml; charset=UTF-8";
            request.Method = "POST";
            request.Timeout = 180000;

            //Write the envelope to the RequestStream
            using (Stream stream = request.GetRequestStream())
            {
                Serialize(stream, registrationRequest);
            }

            // Read the response into an XmlDocument and return that doc
            try
            {
                using (WebResponse response = request.GetResponse())
                {
                    using (Stream stream = response.GetResponseStream())
                    {
                        return Deserialize<DeviceRegistrationResponse>("Deserializing Registration Response", stream);
                    }
                }
            }
            catch (WebException ex)
            {
                System.Diagnostics.Trace.TraceError("Microsoft account Device Registration Failed (HTTP Code: {0}): {1}",
                    ex.Status, ex.Message);

                if (null != ex.Response)
                {
                    using (Stream stream = ex.Response.GetResponseStream())
                    {
                        return Deserialize<DeviceRegistrationResponse>("Deserializing Failed Registration Response", stream);
                    }
                }

                throw;
            }
        }

        private static DeviceUserName GenerateDeviceUserName()
        {
            DeviceUserName userName = new DeviceUserName();
            userName.DeviceName = GenerateRandomString(LiveIdConstants.ValidDeviceNameCharacters, MaxDeviceNameLength);
            userName.DecryptedPassword = GenerateRandomString(LiveIdConstants.ValidDevicePasswordCharacters, MaxDevicePasswordLength);

            return userName;
        }

        private static string GenerateRandomString(string characterSet, int count)
        {
            //Create an array of the characters that will hold the final list of random characters
            char[] value = new char[count];

            //Convert the character set to an array that can be randomly accessed
            char[] set = characterSet.ToCharArray();

            lock (RandomInstance)
            {
                //Populate the array with random characters from the character set
                for (int i = 0; i < count; i++)
                {
                    value[i] = set[RandomInstance.Next(0, set.Length)];
                }
            }

            return new string(value);
        }
        #endregion

        #region Private Classes
        private enum EnvironmentType
        {
            LiveDeviceID,
            OrgDeviceID
        }

        private sealed class EnvironmentConfiguration
        {
            public EnvironmentConfiguration(EnvironmentType type, string hostName, string environment)
            {
                if (string.IsNullOrWhiteSpace(hostName))
                {
                    throw new ArgumentNullException("hostName");
                }

                this.Type = type;
                this.HostName = hostName;
                this.Environment = environment;
            }

            #region Properties
            public EnvironmentType Type { get; private set; }

            public string HostName { get; private set; }

            public string Environment { get; private set; }
            #endregion
        }

        private static class LiveIdConstants
        {
            public const string RegistrationEndpointUriFormat = @"https://{0}/ppsecure/DeviceAddCredential.srf";

            public static readonly string FileNameFormat = Path.Combine(
                Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "LiveDeviceID"),
                "{0}{1}.xml");

            public const string ValidDeviceNameCharacters = "0123456789abcdefghijklmnopqrstuvqxyz";

            //Consists of the list of characters specified in the documentation
            public const string ValidDevicePasswordCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^*()-_=+;,./?`~";
        }
        #endregion
    }

    #region Public Classes &amp; Enums
    /// <summary>
    /// Indicates an error during registration
    /// </summary>
    public enum DeviceRegistrationErrorCode
    {
        /// <summary>
        /// Unspecified or Unknown Error occurred
        /// </summary>
        Unknown = 0,

        /// <summary>
        /// Interface Disabled
        /// </summary>
        InterfaceDisabled = 1,

        /// <summary>
        /// Invalid Request Format
        /// </summary>
        InvalidRequestFormat = 3,

        /// <summary>
        /// Unknown Client Version
        /// </summary>
        UnknownClientVersion = 4,

        /// <summary>
        /// Blank Password
        /// </summary>
        BlankPassword = 6,

        /// <summary>
        /// Missing Device User Name or Password
        /// </summary>
        MissingDeviceUserNameOrPassword = 7,

        /// <summary>
        /// Invalid Parameter Syntax
        /// </summary>
        InvalidParameterSyntax = 8,

        /// <summary>
        /// Invalid Characters are used in the device credentials.
        /// </summary>
        InvalidCharactersInCredentials = 9,

        /// <summary>
        /// Internal Error
        /// </summary>
        InternalError = 11,

        /// <summary>
        /// Device Already Exists
        /// </summary>
        DeviceAlreadyExists = 13
    }

    /// <summary>
    /// Indicates that Device Registration failed
    /// </summary>
    [Serializable]
    public sealed class DeviceRegistrationFailedException : Exception
    {
        /// <summary>
        /// Construct an instance of the DeviceRegistrationFailedException class
        /// </summary>
        public DeviceRegistrationFailedException()
            : base()
        {
        }

        /// <summary>
        /// Construct an instance of the DeviceRegistrationFailedException class
        /// </summary>
        /// <param name="message">Message to pass</param>
        public DeviceRegistrationFailedException(string message)
            : base(message)
        {
        }

        /// <summary>
        /// Construct an instance of the DeviceRegistrationFailedException class
        /// </summary>
        /// <param name="message">Message to pass</param>
        /// <param name="innerException">Exception to include</param>
        public DeviceRegistrationFailedException(string message, Exception innerException)
            : base(message, innerException)
        {
        }

        /// <summary>
        /// Construct an instance of the DeviceRegistrationFailedException class
        /// </summary>
        /// <param name="code">Error code that occurred</param>
        /// <param name="subCode">Subcode that occurred</param>
        public DeviceRegistrationFailedException(DeviceRegistrationErrorCode code, string subCode)
            : this(code, subCode, null)
        {
        }

        /// <summary>
        /// Construct an instance of the DeviceRegistrationFailedException class
        /// </summary>
        /// <param name="code">Error code that occurred</param>
        /// <param name="subCode">Subcode that occurred</param>
        /// <param name="innerException">Inner exception</param>
        public DeviceRegistrationFailedException(DeviceRegistrationErrorCode code, string subCode, Exception innerException)
            : base(string.Concat(code.ToString(), ": ", subCode), innerException)
        {
            this.RegistrationErrorCode = code;
        }

        /// <summary>
        /// Construct an instance of the DeviceRegistrationFailedException class
        /// </summary>
        /// <param name="si"></param>
        /// <param name="sc"></param>
        private DeviceRegistrationFailedException(SerializationInfo si, StreamingContext sc)
            : base(si, sc)
        {
        }

        #region Properties
        /// <summary>
        /// Error code that occurred during registration
        /// </summary>
        public DeviceRegistrationErrorCode RegistrationErrorCode { get; private set; }
        #endregion

        #region Methods
        public override void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            base.GetObjectData(info, context);
        }
        #endregion
    }

    #region Serialization Classes
    #region DeviceRegistrationRequest Class
    [EditorBrowsable(EditorBrowsableState.Never)]
    [XmlRoot("DeviceAddRequest")]
    public sealed class DeviceRegistrationRequest
    {
        #region Constructors
        public DeviceRegistrationRequest()
        {
        }

        public DeviceRegistrationRequest(Guid applicationId, LiveDevice device)
            : this()
        {
            if (null == device)
            {
                throw new ArgumentNullException("device");
            }

            this.ClientInfo = new DeviceRegistrationClientInfo() { ApplicationId = applicationId, Version = "1.0" };
            this.Authentication = new DeviceRegistrationAuthentication()
            {
                MemberName = device.User.DeviceId,
                Password = device.User.DecryptedPassword
            };
        }
        #endregion

        #region Properties
        [XmlElement("ClientInfo")]
        public DeviceRegistrationClientInfo ClientInfo { get; set; }

        [XmlElement("Authentication")]
        public DeviceRegistrationAuthentication Authentication { get; set; }
        #endregion
    }
    #endregion

    #region DeviceRegistrationClientInfo Class
    [EditorBrowsable(EditorBrowsableState.Never)]
    [XmlRoot("ClientInfo")]
    public sealed class DeviceRegistrationClientInfo
    {
        #region Properties
        [XmlAttribute("name")]
        public Guid ApplicationId { get; set; }

        [XmlAttribute("version")]
        public string Version { get; set; }
        #endregion
    }
    #endregion

    #region DeviceRegistrationAuthentication Class
    [EditorBrowsable(EditorBrowsableState.Never)]
    [XmlRoot("Authentication")]
    public sealed class DeviceRegistrationAuthentication
    {
        #region Properties
        [XmlElement("Membername")]
        public string MemberName { get; set; }

        [XmlElement("Password")]
        public string Password { get; set; }
        #endregion
    }
    #endregion

    #region DeviceRegistrationResponse Class
    [EditorBrowsable(EditorBrowsableState.Never)]
    [XmlRoot("DeviceAddResponse")]
    public sealed class DeviceRegistrationResponse
    {
        #region Properties
        [XmlElement("success")]
        public bool IsSuccess { get; set; }

        [XmlElement("puid")]
        public string Puid { get; set; }

        [XmlElement("Error")]
        public DeviceRegistrationResponseError Error { get; set; }

        [XmlElement("ErrorSubcode")]
        public string ErrorSubCode { get; set; }
        #endregion
    }
    #endregion

    #region DeviceRegistrationResponse Class
    [EditorBrowsable(EditorBrowsableState.Never)]
    [XmlRoot("Error")]
    public sealed class DeviceRegistrationResponseError
    {
        private string _code;

        #region Properties
        [XmlAttribute("Code")]
        public string Code
        {
            get
            {
                return this._code;
            }

            set
            {
                this._code = value;

                //Parse the error code
                if (!string.IsNullOrEmpty(value))
                {
                    //Parse the error code
                    if (value.StartsWith("dc", StringComparison.Ordinal))
                    {
                        int code;
                        if (int.TryParse(value.Substring(2), NumberStyles.Integer,
                            CultureInfo.InvariantCulture, out code) &amp;&amp;
                            Enum.IsDefined(typeof(DeviceRegistrationErrorCode), code))
                        {
                            this.RegistrationErrorCode = (DeviceRegistrationErrorCode)Enum.ToObject(
                                typeof(DeviceRegistrationErrorCode), code);
                        }
                    }
                }
            }
        }

        [XmlIgnore]
        public DeviceRegistrationErrorCode RegistrationErrorCode { get; private set; }
        #endregion
    }
    #endregion

    #region LiveDevice Class
    [EditorBrowsable(EditorBrowsableState.Never)]
    [XmlRoot("Data")]
    public sealed class LiveDevice
    {
        #region Properties
        [XmlAttribute("version")]
        public int Version { get; set; }

        [XmlElement("User")]
        public DeviceUserName User { get; set; }

        [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes", MessageId = "System.Xml.XmlNode", Justification = "This is required for proper XML Serialization")]
        [XmlElement("Token")]
        public XmlNode Token { get; set; }

        [XmlElement("Expiry")]
        public string Expiry { get; set; }

        [XmlElement("ClockSkew")]
        public string ClockSkew { get; set; }
        #endregion
    }
    #endregion

    #region DeviceUserName Class
    [EditorBrowsable(EditorBrowsableState.Never)]
    public sealed class DeviceUserName
    {
        private string _encryptedPassword;
        private string _decryptedPassword;
        private bool _encryptedValueIsUpdated;

        #region Constants
        private const string UserNamePrefix = "11";
        #endregion

        #region Constructors
        public DeviceUserName()
        {
            this.UserNameType = "Logical";
        }
        #endregion

        #region Properties
        [XmlAttribute("username")]
        public string DeviceName { get; set; }

        [XmlAttribute("type")]
        public string UserNameType { get; set; }

        [XmlElement("Pwd")]
        public string EncryptedPassword
        {
            get
            {
                this.ThrowIfNoEncryption();

                if (!this._encryptedValueIsUpdated)
                {
                    this._encryptedPassword = this.Encrypt(this._decryptedPassword);
                    this._encryptedValueIsUpdated = true;
                }

                return this._encryptedPassword;
            }

            set
            {
                this.ThrowIfNoEncryption();
                this.UpdateCredentials(value, null);
            }
        }

        public string DeviceId
        {
            get
            {
                return UserNamePrefix + DeviceName;
            }
        }

        [XmlIgnore]
        public string DecryptedPassword
        {
            get
            {
                return this._decryptedPassword;
            }

            set
            {
                this.UpdateCredentials(null, value);
            }
        }

        private bool IsEncryptionEnabled
        {
            get
            {
                //If the object is not going to be persisted to a file, then the value does not need to be encrypted. This is extra
                //overhead and will not function in partial trust.
                return DeviceIdManager.PersistToFile;
            }
        }
        #endregion

        #region Methods
        public ClientCredentials ToClientCredentials()
        {
            ClientCredentials credentials = new ClientCredentials();
            credentials.UserName.UserName = this.DeviceId;
            credentials.UserName.Password = this.DecryptedPassword;

            return credentials;
        }

        private void ThrowIfNoEncryption()
        {
            if (!this.IsEncryptionEnabled)
            {
                throw new NotSupportedException("Not supported when DeviceIdManager.UseEncryptionApis is false.");
            }
        }

        private void UpdateCredentials(string encryptedValue, string decryptedValue)
        {
            bool isValueUpdated = false;
            if (string.IsNullOrEmpty(encryptedValue) &amp;&amp; string.IsNullOrEmpty(decryptedValue))
            {
                isValueUpdated = true;
            }
            else if (string.IsNullOrEmpty(encryptedValue))
            {
                if (this.IsEncryptionEnabled)
                {
                    encryptedValue = this.Encrypt(decryptedValue);
                    isValueUpdated = true;
                }
                else
                {
                    encryptedValue = null;
                    isValueUpdated = false;
                }
            }
            else
            {
                this.ThrowIfNoEncryption();

                decryptedValue = this.Decrypt(encryptedValue);
                isValueUpdated = true;
            }

            this._encryptedPassword = encryptedValue;
            this._decryptedPassword = decryptedValue;
            this._encryptedValueIsUpdated = isValueUpdated;
        }

        private string Encrypt(string value)
        {
            if (string.IsNullOrEmpty(value))
            {
                return value;
            }

            byte[] encryptedBytes = ProtectedData.Protect(Encoding.UTF8.GetBytes(value), null, DataProtectionScope.CurrentUser);
            return Convert.ToBase64String(encryptedBytes);
        }

        private string Decrypt(string value)
        {
            if (string.IsNullOrEmpty(value))
            {
                return value;
            }

            byte[] decryptedBytes = ProtectedData.Unprotect(Convert.FromBase64String(value), null, DataProtectionScope.CurrentUser);
            if (null == decryptedBytes || 0 == decryptedBytes.Length)
            {
                return null;
            }

            return Encoding.UTF8.GetString(decryptedBytes, 0, decryptedBytes.Length);
        }
        #endregion
    }
    #endregion
    #endregion
    #endregion
}

Imports System
Imports System.Collections.Generic
Imports System.ComponentModel
Imports System.Diagnostics.CodeAnalysis
Imports System.Globalization
Imports System.IO
Imports System.Net
Imports System.Runtime.Serialization
Imports System.Security.Cryptography
Imports System.ServiceModel.Description
Imports System.Text
Imports System.Xml
Imports System.Xml.Serialization

Namespace Microsoft.Crm.Services.Utility
    ''' <summary>
    ''' Management utility for the Device Id
    ''' </summary>
    Public NotInheritable Class DeviceIdManager
        #Region "Fields"
        Private Shared ReadOnly RandomInstance As New Random()

        Public Const MaxDeviceNameLength As Integer = 24
        Public Const MaxDevicePasswordLength As Integer = 24
        #End Region

        #Region "Constructor"
        Private Sub New()
        End Sub
        Shared Sub New()
            PersistToFile = True
        End Sub
        #End Region

        #Region "Properties"
        ''' <summary>
        ''' Indicates whether the registered device credentials should be persisted to the database
        ''' </summary>
        Public Shared Property PersistToFile() As Boolean

        ''' <summary>
        ''' Indicates that the credentials should be persisted to the disk if registration fails with DeviceAlreadyExists.
        ''' </summary>
        ''' <remarks>
        ''' If the device already exists, there is a possibility that the credentials are the same as the current credentials that
        ''' are being registered. This is especially true in automated environments where the same credentials are used continually (to avoid
        ''' registering spurious device credentials.
        ''' </remarks>
        Public Shared Property PersistIfDeviceAlreadyExists() As Boolean
        #End Region

        #Region "Methods"
        ''' <summary>
        ''' Loads the device credentials (if they exist).
        ''' </summary>
        ''' <returns></returns>
        Public Shared Function LoadOrRegisterDevice() As ClientCredentials
            Return LoadOrRegisterDevice(Nothing)
        End Function

        ''' <summary>
        ''' Loads the device credentials (if they exist).
        ''' </summary>
        ''' <param name="deviceName">Device name that should be registered</param>
        ''' <param name="devicePassword">Device password that should be registered</param>
        Public Shared Function LoadOrRegisterDevice(ByVal deviceName As String, ByVal devicePassword As String) As ClientCredentials
            Return LoadOrRegisterDevice(Nothing, deviceName, devicePassword)
        End Function

        ''' <summary>
        ''' Loads the device credentials (if they exist).
        ''' </summary>
        ''' <param name="issuerUri">URL for the current token issuer</param>
        ''' <remarks>
        ''' The issuerUri can be retrieved from the IServiceConfiguration interface's CurrentIssuer property.
        ''' </remarks>
        Public Shared Function LoadOrRegisterDevice(ByVal issuerUri As Uri) As ClientCredentials
            Return LoadOrRegisterDevice(issuerUri, Nothing, Nothing)
        End Function

        ''' <summary>
        ''' Loads the device credentials (if they exist).
        ''' </summary>
        ''' <param name="issuerUri">URL for the current token issuer</param>
        ''' <param name="deviceName">Device name that should be registered</param>
        ''' <param name="devicePassword">Device password that should be registered</param>
        ''' <remarks>
        ''' The issuerUri can be retrieved from the IServiceConfiguration interface's CurrentIssuer property.
        ''' </remarks>
        Public Shared Function LoadOrRegisterDevice(ByVal issuerUri As Uri, ByVal deviceName As String, ByVal devicePassword As String) _
               As ClientCredentials
            Dim credentials As ClientCredentials = LoadDeviceCredentials(issuerUri)
            If Nothing Is credentials Then
                credentials = RegisterDevice(Guid.NewGuid(), issuerUri, deviceName, devicePassword)
            End If

            Return credentials
        End Function

        ''' <summary>
        ''' Registers the given device with Microsoft account with a random application ID
        ''' </summary>
        ''' <returns>ClientCredentials that were registered</returns>
        Public Shared Function RegisterDevice() As ClientCredentials
            Return RegisterDevice(Guid.NewGuid())
        End Function

        ''' <summary>
        ''' Registers the given device with Microsoft account
        ''' </summary>
        ''' <param name="applicationId">ID for the application</param>
        ''' <returns>ClientCredentials that were registered</returns>
        Public Shared Function RegisterDevice(ByVal applicationId As Guid) As ClientCredentials
            Return RegisterDevice(applicationId, CType(Nothing, Uri))
        End Function

        ''' <summary>
        ''' Registers the given device with Microsoft account
        ''' </summary>
        ''' <param name="applicationId">ID for the application</param>
        ''' <param name="issuerUri">URL for the current token issuer</param>
        ''' <returns>ClientCredentials that were registered</returns>
        ''' <remarks>
        ''' The issuerUri can be retrieved from the IServiceConfiguration interface's CurrentIssuer property.
        ''' </remarks>
        Public Shared Function RegisterDevice(ByVal applicationId As Guid, ByVal issuerUri As Uri) As ClientCredentials
            Return RegisterDevice(applicationId, issuerUri, Nothing, Nothing)
        End Function

        ''' <summary>
        ''' Registers the given device with Microsoft account
        ''' </summary>
        ''' <param name="applicationId">ID for the application</param>
        ''' <param name="deviceName">Device name that should be registered</param>
        ''' <param name="devicePassword">Device password that should be registered</param>
        ''' <returns>ClientCredentials that were registered</returns>
        Public Shared Function RegisterDevice(ByVal applicationId As Guid, ByVal deviceName As String, ByVal devicePassword As String) _
               As ClientCredentials
            Return RegisterDevice(applicationId, CType(Nothing, Uri), deviceName, devicePassword)
        End Function

        ''' <summary>
        ''' Registers the given device with Microsoft account
        ''' </summary>
        ''' <param name="applicationId">ID for the application</param>
        ''' <param name="issuerUri">URL for the current token issuer</param>
        ''' <param name="deviceName">Device name that should be registered</param>
        ''' <param name="devicePassword">Device password that should be registered</param>
        ''' <returns>ClientCredentials that were registered</returns>
        ''' <remarks>
        ''' The issuerUri can be retrieved from the IServiceConfiguration interface's CurrentIssuer property.
        ''' </remarks>
        Public Shared Function RegisterDevice(ByVal applicationId As Guid, ByVal issuerUri As Uri, ByVal deviceName As String, _
                                              ByVal devicePassword As String) As ClientCredentials
            If String.IsNullOrEmpty(deviceName) AndAlso (Not PersistToFile) Then
                Throw New ArgumentNullException("deviceName", "If PersistToFile is false, then deviceName must be specified.")
            ElseIf String.IsNullOrEmpty(deviceName) <> String.IsNullOrEmpty(devicePassword) Then
                Throw New ArgumentNullException("deviceName", _
                                                "Either deviceName/devicePassword should both be specified or they should be null.")
            End If

            Dim device As LiveDevice = GenerateDevice(deviceName, devicePassword)
            Return RegisterDevice(applicationId, issuerUri, device)
        End Function

        ''' <summary>
        ''' Loads the device's credentials from the file system
        ''' </summary>
        ''' <returns>Device Credentials (if set) or null</returns>
        Public Shared Function LoadDeviceCredentials() As ClientCredentials
            Return LoadDeviceCredentials(Nothing)
        End Function

        ''' <summary>
        ''' Loads the device's credentials from the file system
        ''' </summary>
        ''' <param name="issuerUri">URL for the current token issuer</param>
        ''' <returns>Device Credentials (if set) or null</returns>
        ''' <remarks>
        ''' The issuerUri can be retrieved from the IServiceConfiguration interface's CurrentIssuer property.
        ''' </remarks>
        Public Shared Function LoadDeviceCredentials(ByVal issuerUri As Uri) As ClientCredentials
            'If the credentials should not be persisted to a file, then they won't be present on the disk.
            If Not PersistToFile Then
                Return Nothing
            End If

            Dim environment As EnvironmentConfiguration = DiscoverEnvironmentInternal(issuerUri)

            Dim device As LiveDevice = ReadExistingDevice(environment)
            If Nothing Is device OrElse Nothing Is device.User Then
                Return Nothing
            End If

            Return device.User.ToClientCredentials()
        End Function

        ''' <summary>
        ''' Discovers the Windows Live environment based on the Token Issuer
        ''' </summary>
        Public Shared Function DiscoverEnvironment(ByVal issuerUri As Uri) As String
            Return DiscoverEnvironmentInternal(issuerUri).Environment
        End Function
        #End Region

        #Region "Private Methods"
        Private Shared Function DiscoverEnvironmentInternal(ByVal issuerUri As Uri) As EnvironmentConfiguration
            If Nothing Is issuerUri Then
                Return New EnvironmentConfiguration(EnvironmentType.LiveDeviceID, "login.live.com", Nothing)
            End If

            Dim searchList As New Dictionary(Of EnvironmentType, String)()
            searchList.Add(EnvironmentType.LiveDeviceID, "login.live")
            searchList.Add(EnvironmentType.OrgDeviceID, "login.microsoftonline")

            For Each searchPair As KeyValuePair(Of EnvironmentType, String) In searchList
                If issuerUri.Host.Length > searchPair.Value.Length AndAlso issuerUri.Host.StartsWith(searchPair.Value, StringComparison.OrdinalIgnoreCase) Then
                    Dim environment As String = issuerUri.Host.Substring(searchPair.Value.Length)

                    'Parse out the environment
                If "-"c = environment.Chars(0) Then
                    Dim separatorIndex As Integer = environment.IndexOf("."c, 1)
                    If -1 <> separatorIndex Then
                            environment = environment.Substring(1, separatorIndex - 1)
                        Else
                            environment = Nothing
                        End If
                    Else
                        environment = Nothing
                    End If

                    Return New EnvironmentConfiguration(searchPair.Key, issuerUri.Host, environment)
                End If
            Next searchPair

            'In all other cases the environment is either not applicable or it is a production system
            Return New EnvironmentConfiguration(EnvironmentType.LiveDeviceID, issuerUri.Host, Nothing)
        End Function

        Private Shared Sub Serialize(Of T)(ByVal stream As Stream, ByVal value As T)
            Dim serializer As New XmlSerializer(GetType(T), String.Empty)

            Dim xmlNamespaces As New XmlSerializerNamespaces()
            xmlNamespaces.Add(String.Empty, String.Empty)

            serializer.Serialize(stream, value, xmlNamespaces)
        End Sub

        Private Shared Function Deserialize(Of T)(ByVal operationName As String, ByVal stream As Stream) As T
            'Read the XML into memory so that the data can be used in an exception if necessary
            Using reader As New StreamReader(stream)
                Return Deserialize(Of T)(operationName, reader.ReadToEnd())
            End Using
        End Function

        Private Shared Function Deserialize(Of T)(ByVal operationName As String, ByVal xml As String) As T
            'Attempt to deserialize the data. If deserialization fails, include the XML in the exception that is thrown for further
            'investigation
            Using reader As New StringReader(xml)
                Try
                    Dim serializer As New XmlSerializer(GetType(T), String.Empty)
                    Return CType(serializer.Deserialize(reader), T)
                Catch ex As InvalidOperationException
                    Throw New InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Unable to Deserialize XML (Operation = {0}):{1}{2}", operationName, Environment.NewLine, xml), ex)
                End Try
            End Using
        End Function

        Private Shared Function GetDeviceFile(ByVal environment As EnvironmentConfiguration) As FileInfo
            Return New FileInfo(String.Format(CultureInfo.InvariantCulture, LiveIdConstants.FileNameFormat, environment.Type,If(String.IsNullOrEmpty(environment.Environment), Nothing, "-" &amp; environment.Environment.ToUpperInvariant())))
        End Function

        Private Shared Function RegisterDevice(ByVal applicationId As Guid, ByVal issuerUri As Uri, ByVal device As LiveDevice) As ClientCredentials
            Dim environment As EnvironmentConfiguration = DiscoverEnvironmentInternal(issuerUri)

            Dim request As New DeviceRegistrationRequest(applicationId, device)

            Dim url As String = String.Format(CultureInfo.InvariantCulture, LiveIdConstants.RegistrationEndpointUriFormat, environment.HostName)

            Dim response As DeviceRegistrationResponse = ExecuteRegistrationRequest(url, request)
            If Not response.IsSuccess Then
                Dim throwException As Boolean = True
                If DeviceRegistrationErrorCode.DeviceAlreadyExists = response.Error.RegistrationErrorCode Then
                    If Not PersistToFile Then
                        'If the file is not persisted, the registration will always occur (since the credentials are not
                        'persisted to the disk. However, the credentials may already exist. To avoid an exception being continually
                        'processed by the calling user, DeviceAlreadyExists will be ignored if the credentials are not persisted to the disk.
                        Return device.User.ToClientCredentials()
                    ElseIf PersistIfDeviceAlreadyExists Then
                        ' This flag indicates that the 
                        throwException = False
                    End If
                End If

                If throwException Then
                    Throw New DeviceRegistrationFailedException(response.Error.RegistrationErrorCode, response.ErrorSubCode)
                End If
            End If

            If PersistToFile OrElse PersistIfDeviceAlreadyExists Then
                WriteDevice(environment, device)
            End If

            Return device.User.ToClientCredentials()
        End Function

        Private Shared Function GenerateDevice(ByVal deviceName As String, ByVal devicePassword As String) As LiveDevice
            ' If the deviceName hasn't been specified, it should be generated using random characters.
            Dim userNameCredentials As DeviceUserName
            If String.IsNullOrEmpty(deviceName) Then
                userNameCredentials = GenerateDeviceUserName()
            Else
                userNameCredentials = New DeviceUserName() With {.DeviceName = deviceName, .DecryptedPassword = devicePassword}
            End If

            Return New LiveDevice() With {.User = userNameCredentials, .Version = 1}
        End Function

        Private Shared Function ReadExistingDevice(ByVal environment As EnvironmentConfiguration) As LiveDevice
            'Retrieve the file info
            Dim file As FileInfo = GetDeviceFile(environment)
            If Not file.Exists Then
                Return Nothing
            End If

            Using stream As FileStream = file.Open(FileMode.Open, FileAccess.Read, FileShare.Read)
                Return Deserialize(Of LiveDevice)("Loading Device Credentials from Disk", stream)
            End Using
        End Function

        Private Shared Sub WriteDevice(ByVal environment As EnvironmentConfiguration, ByVal device As LiveDevice)
            Dim file As FileInfo = GetDeviceFile(environment)
            If Not file.Directory.Exists Then
                file.Directory.Create()
            End If

            Using stream As FileStream = file.Open(FileMode.CreateNew, FileAccess.Write, FileShare.None)
                Serialize(stream, device)
            End Using
        End Sub

        Private Shared Function ExecuteRegistrationRequest(ByVal url As String, ByVal registrationRequest As DeviceRegistrationRequest) _
               As DeviceRegistrationResponse
            'Create the request that will submit the request to the server
            Dim request As WebRequest = WebRequest.Create(url)
            request.ContentType = "application/soap+xml; charset=UTF-8"
            request.Method = "POST"
            request.Timeout = 180000

            'Write the envelope to the RequestStream
            Using stream As Stream = request.GetRequestStream()
                Serialize(stream, registrationRequest)
            End Using

            ' Read the response into an XmlDocument and return that doc
            Try
                Using response As WebResponse = request.GetResponse()
                    Using stream As Stream = response.GetResponseStream()
                        Return Deserialize(Of DeviceRegistrationResponse)("Deserializing Registration Response", stream)
                    End Using
                End Using
            Catch ex As WebException
                System.Diagnostics.Trace.TraceError("Live ID Device Registration Failed (HTTP Code: {0}): {1}", ex.Status, ex.Message)
                If Nothing IsNot ex.Response Then
                    Using stream As Stream = ex.Response.GetResponseStream()
                        Return Deserialize(Of DeviceRegistrationResponse)("Deserializing Failed Registration Response", stream)
                    End Using
                End If

                Throw
            End Try
        End Function

        Private Shared Function GenerateDeviceUserName() As DeviceUserName
            Dim userName As New DeviceUserName()
            userName.DeviceName = GenerateRandomString(LiveIdConstants.ValidDeviceNameCharacters, MaxDeviceNameLength)
            userName.DecryptedPassword = GenerateRandomString(LiveIdConstants.ValidDevicePasswordCharacters, MaxDevicePasswordLength)

            Return userName
        End Function

        Private Shared Function GenerateRandomString(ByVal characterSet As String, ByVal count As Integer) As String
            'Create an array of the characters that will hold the final list of random characters
            Dim value(count - 1) As Char

            'Convert the character set to an array that can be randomly accessed
            Dim [set]() As Char = characterSet.ToCharArray()

            SyncLock RandomInstance
                'Populate the array with random characters from the character set
                For i As Integer = 0 To count - 1
                    value(i) = [set](RandomInstance.Next(0, [set].Length))
                Next i
            End SyncLock

            Return New String(value)
        End Function
        #End Region

        #Region "Private Classes"
        Private Enum EnvironmentType
            LiveDeviceID
            OrgDeviceID
        End Enum

        Private NotInheritable Class EnvironmentConfiguration
            Public Sub New(ByVal type As EnvironmentType, ByVal hostName As String, ByVal environment As String)
                If String.IsNullOrWhiteSpace(hostName) Then
                    Throw New ArgumentNullException("hostName")
                End If

                Me.Type = type
                Me.HostName = hostName
                Me.Environment = environment
            End Sub

            #Region "Properties"
            Private privateType As EnvironmentType
            Public Property Type() As EnvironmentType
                Get
                    Return privateType
                End Get
                Private Set(ByVal value As EnvironmentType)
                    privateType = value
                End Set
            End Property

            Private privateHostName As String
            Public Property HostName() As String
                Get
                    Return privateHostName
                End Get
                Private Set(ByVal value As String)
                    privateHostName = value
                End Set
            End Property

            Private privateEnvironment As String
            Public Property Environment() As String
                Get
                    Return privateEnvironment
                End Get
                Private Set(ByVal value As String)
                    privateEnvironment = value
                End Set
            End Property
            #End Region
        End Class

        Private NotInheritable Class LiveIdConstants
            Public Const RegistrationEndpointUriFormat As String = "https://{0}/ppsecure/DeviceAddCredential.srf"

            Public Shared ReadOnly FileNameFormat As String = Path.Combine(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "LiveDeviceID"), "{0}{1}.xml")

            Public Const ValidDeviceNameCharacters As String = "0123456789abcdefghijklmnopqrstuvqxyz"

            'Consists of the list of characters specified in the documentation
            Public Const ValidDevicePasswordCharacters As String = _
                "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^*()-_=+;,./?`~"
        End Class
        #End Region
    End Class

    #Region "Public Classes &amp; Enums"
    ''' <summary>
    ''' Indicates an error during registration
    ''' </summary>
    Public Enum DeviceRegistrationErrorCode
        ''' <summary>
        ''' Unspecified or Unknown Error occurred
        ''' </summary>
        Unknown = 0

        ''' <summary>
        ''' Interface Disabled
        ''' </summary>
        InterfaceDisabled = 1

        ''' <summary>
        ''' Invalid Request Format
        ''' </summary>
        InvalidRequestFormat = 3

        ''' <summary>
        ''' Unknown Client Version
        ''' </summary>
        UnknownClientVersion = 4

        ''' <summary>
        ''' Blank Password
        ''' </summary>
        BlankPassword = 6

        ''' <summary>
        ''' Missing Device User Name or Password
        ''' </summary>
        MissingDeviceUserNameOrPassword = 7

        ''' <summary>
        ''' Invalid Parameter Syntax
        ''' </summary>
        InvalidParameterSyntax = 8

        ''' <summary>
        ''' Invalid Characters are used in the device credentials.
        ''' </summary>
        InvalidCharactersInCredentials = 9

        ''' <summary>
        ''' Internal Error
        ''' </summary>
        InternalError = 11

        ''' <summary>
        ''' Device Already Exists
        ''' </summary>
        DeviceAlreadyExists = 13
    End Enum

    ''' <summary>
    ''' Indicates that Device Registration failed
    ''' </summary>
    <Serializable> _
    Public NotInheritable Class DeviceRegistrationFailedException
        Inherits Exception
        ''' <summary>
        ''' Construct an instance of the DeviceRegistrationFailedException class
        ''' </summary>
        Public Sub New()
            MyBase.New()
        End Sub

        ''' <summary>
        ''' Construct an instance of the DeviceRegistrationFailedException class
        ''' </summary>
        ''' <param name="message">Message to pass</param>
        Public Sub New(ByVal message As String)
            MyBase.New(message)
        End Sub

        ''' <summary>
        ''' Construct an instance of the DeviceRegistrationFailedException class
        ''' </summary>
        ''' <param name="message">Message to pass</param>
        ''' <param name="innerException">Exception to include</param>
        Public Sub New(ByVal message As String, ByVal innerException As Exception)
            MyBase.New(message, innerException)
        End Sub

        ''' <summary>
        ''' Construct an instance of the DeviceRegistrationFailedException class
        ''' </summary>
        ''' <param name="code">Error code that occurred</param>
        ''' <param name="subCode">Subcode that occurred</param>
        Public Sub New(ByVal code As DeviceRegistrationErrorCode, ByVal subCode As String)
            Me.New(code, subCode, Nothing)
        End Sub

        ''' <summary>
        ''' Construct an instance of the DeviceRegistrationFailedException class
        ''' </summary>
        ''' <param name="code">Error code that occurred</param>
        ''' <param name="subCode">Subcode that occurred</param>
        ''' <param name="innerException">Inner exception</param>
        Public Sub New(ByVal code As DeviceRegistrationErrorCode, ByVal subCode As String, ByVal innerException As Exception)
            MyBase.New(String.Concat(code.ToString(), ": ", subCode), innerException)
            Me.RegistrationErrorCode = code
        End Sub

        ''' <summary>
        ''' Construct an instance of the DeviceRegistrationFailedException class
        ''' </summary>
        ''' <param name="si"></param>
        ''' <param name="sc"></param>
        Private Sub New(ByVal si As SerializationInfo, ByVal sc As StreamingContext)
            MyBase.New(si, sc)
        End Sub

        #Region "Properties"
        ''' <summary>
        ''' Error code that occurred during registration
        ''' </summary>
        Private privateRegistrationErrorCode As DeviceRegistrationErrorCode
        Public Property RegistrationErrorCode() As DeviceRegistrationErrorCode
            Get
                Return privateRegistrationErrorCode
            End Get
            Private Set(ByVal value As DeviceRegistrationErrorCode)
                privateRegistrationErrorCode = value
            End Set
        End Property
        #End Region

        #Region "Methods"
        Public Overrides Sub GetObjectData(ByVal info As SerializationInfo, ByVal context As StreamingContext)
            MyBase.GetObjectData(info, context)
        End Sub
        #End Region
    End Class

    #Region "Serialization Classes"
    #Region "DeviceRegistrationRequest Class"
    <EditorBrowsable(EditorBrowsableState.Never), XmlRoot("DeviceAddRequest")> _
    Public NotInheritable Class DeviceRegistrationRequest
        #Region "Constructors"
        Public Sub New()
        End Sub

        Public Sub New(ByVal applicationId As Guid, ByVal device As LiveDevice)
            Me.New()
            If Nothing Is device Then
                Throw New ArgumentNullException("device")
            End If

            Me.ClientInfo = New DeviceRegistrationClientInfo() With {.ApplicationId = applicationId, .Version = "1.0"}
            Me.Authentication = New DeviceRegistrationAuthentication() With {.MemberName = device.User.DeviceId, _
                                                                             .Password = device.User.DecryptedPassword}
        End Sub
        #End Region

        #Region "Properties"
        <XmlElement("ClientInfo")> _
        Public Property ClientInfo() As DeviceRegistrationClientInfo

        <XmlElement("Authentication")> _
        Public Property Authentication() As DeviceRegistrationAuthentication
        #End Region
    End Class
    #End Region

    #Region "DeviceRegistrationClientInfo Class"
    <EditorBrowsable(EditorBrowsableState.Never), XmlRoot("ClientInfo")> _
    Public NotInheritable Class DeviceRegistrationClientInfo
        #Region "Properties"
        <XmlAttribute("name")> _
        Public Property ApplicationId() As Guid

        <XmlAttribute("version")> _
        Public Property Version() As String
        #End Region
    End Class
    #End Region

    #Region "DeviceRegistrationAuthentication Class"
    <EditorBrowsable(EditorBrowsableState.Never), XmlRoot("Authentication")> _
    Public NotInheritable Class DeviceRegistrationAuthentication
        #Region "Properties"
        <XmlElement("Membername")> _
        Public Property MemberName() As String

        <XmlElement("Password")> _
        Public Property Password() As String
        #End Region
    End Class
    #End Region

    #Region "DeviceRegistrationResponse Class"
    <EditorBrowsable(EditorBrowsableState.Never), XmlRoot("DeviceAddResponse")> _
    Public NotInheritable Class DeviceRegistrationResponse
        #Region "Properties"
        <XmlElement("success")> _
        Public Property IsSuccess() As Boolean

        <XmlElement("puid")> _
        Public Property Puid() As String

        <XmlElement("Error")> _
        Public Property [Error]() As DeviceRegistrationResponseError

        <XmlElement("ErrorSubcode")> _
        Public Property ErrorSubCode() As String
        #End Region
    End Class
    #End Region

    #Region "DeviceRegistrationResponse Class"
    <EditorBrowsable(EditorBrowsableState.Never), XmlRoot("Error")> _
    Public NotInheritable Class DeviceRegistrationResponseError
        Private _code As String

        #Region "Properties"
        <XmlAttribute("Code")> _
        Public Property Code() As String
            Get
                Return Me._code
            End Get

            Set(ByVal value As String)
                Me._code = value

                'Parse the error code
                If Not String.IsNullOrEmpty(value) Then
                    'Parse the error code
                    If value.StartsWith("dc", StringComparison.Ordinal) Then
                        Dim code_Renamed As Integer
                        If Integer.TryParse(value.Substring(2), NumberStyles.Integer, CultureInfo.InvariantCulture, code_Renamed) _
                               AndAlso System.Enum.IsDefined(GetType(DeviceRegistrationErrorCode), code_Renamed) Then
                            Me.RegistrationErrorCode = CType(System.Enum.ToObject(GetType(DeviceRegistrationErrorCode), code_Renamed), DeviceRegistrationErrorCode)
                        End If
                    End If
                End If
            End Set
        End Property

        Private privateRegistrationErrorCode As DeviceRegistrationErrorCode
        <XmlIgnore> _
        Public Property RegistrationErrorCode() As DeviceRegistrationErrorCode
            Get
                Return privateRegistrationErrorCode
            End Get
            Private Set(ByVal value As DeviceRegistrationErrorCode)
                privateRegistrationErrorCode = value
            End Set
        End Property
        #End Region
    End Class
    #End Region

    #Region "LiveDevice Class"
    <EditorBrowsable(EditorBrowsableState.Never), XmlRoot("Data")> _
    Public NotInheritable Class LiveDevice
        #Region "Properties"
        <XmlAttribute("version")> _
        Public Property Version() As Integer

        <XmlElement("User")> _
        Public Property User() As DeviceUserName

        <SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes", _
            MessageId:="System.Xml.XmlNode", Justification:="This is required for proper XML Serialization"), XmlElement("Token")> _
        Public Property Token() As XmlNode

        <XmlElement("Expiry")> _
        Public Property Expiry() As String

        <XmlElement("ClockSkew")> _
        Public Property ClockSkew() As String
        #End Region
    End Class
    #End Region

    #Region "DeviceUserName Class"
    <EditorBrowsable(EditorBrowsableState.Never)> _
    Public NotInheritable Class DeviceUserName
        Private _encryptedPassword As String
        Private _decryptedPassword As String
        Private _encryptedValueIsUpdated As Boolean

        #Region "Constants"
        Private Const UserNamePrefix As String = "11"
        #End Region

        #Region "Constructors"
        Public Sub New()
            Me.UserNameType = "Logical"
        End Sub
        #End Region

        #Region "Properties"
        <XmlAttribute("username")> _
        Public Property DeviceName() As String

        <XmlAttribute("type")> _
        Public Property UserNameType() As String

        <XmlElement("Pwd")> _
        Public Property EncryptedPassword() As String
            Get
                Me.ThrowIfNoEncryption()

                If Not Me._encryptedValueIsUpdated Then
                    Me._encryptedPassword = Me.Encrypt(Me._decryptedPassword)
                    Me._encryptedValueIsUpdated = True
                End If

                Return Me._encryptedPassword
            End Get

            Set(ByVal value As String)
                Me.ThrowIfNoEncryption()
                Me.UpdateCredentials(value, Nothing)
            End Set
        End Property

        Public ReadOnly Property DeviceId() As String
            Get
                Return UserNamePrefix &amp; DeviceName
            End Get
        End Property

        <XmlIgnore> _
        Public Property DecryptedPassword() As String
            Get
                Return Me._decryptedPassword
            End Get

            Set(ByVal value As String)
                Me.UpdateCredentials(Nothing, value)
            End Set
        End Property

        Private ReadOnly Property IsEncryptionEnabled() As Boolean
            Get
                'If the object is not going to be persisted to a file, then the value does not need to be encrypted. This is extra
                'overhead and will not function in partial trust.
                Return DeviceIdManager.PersistToFile
            End Get
        End Property
        #End Region

        #Region "Methods"
        Public Function ToClientCredentials() As ClientCredentials
            Dim credentials As New ClientCredentials()
            credentials.UserName.UserName = Me.DeviceId
            credentials.UserName.Password = Me.DecryptedPassword

            Return credentials
        End Function

        Private Sub ThrowIfNoEncryption()
            If Not Me.IsEncryptionEnabled Then
                Throw New NotSupportedException("Not supported when DeviceIdManager.UseEncryptionApis is false.")
            End If
        End Sub

        Private Sub UpdateCredentials(ByVal encryptedValue As String, ByVal decryptedValue As String)
            Dim isValueUpdated As Boolean = False
            If String.IsNullOrEmpty(encryptedValue) AndAlso String.IsNullOrEmpty(decryptedValue) Then
                isValueUpdated = True
            ElseIf String.IsNullOrEmpty(encryptedValue) Then
                If Me.IsEncryptionEnabled Then
                    encryptedValue = Me.Encrypt(decryptedValue)
                    isValueUpdated = True
                Else
                    encryptedValue = Nothing
                    isValueUpdated = False
                End If
            Else
                Me.ThrowIfNoEncryption()

                decryptedValue = Me.Decrypt(encryptedValue)
                isValueUpdated = True
            End If

            Me._encryptedPassword = encryptedValue
            Me._decryptedPassword = decryptedValue
            Me._encryptedValueIsUpdated = isValueUpdated
        End Sub

        Private Function Encrypt(ByVal value As String) As String
            If String.IsNullOrEmpty(value) Then
                Return value
            End If

            Dim encryptedBytes() As Byte = ProtectedData.Protect(Encoding.UTF8.GetBytes(value), Nothing, DataProtectionScope.CurrentUser)
            Return Convert.ToBase64String(encryptedBytes)
        End Function

        Private Function Decrypt(ByVal value As String) As String
            If String.IsNullOrEmpty(value) Then
                Return value
            End If

            Dim decryptedBytes() As Byte = ProtectedData.Unprotect(Convert.FromBase64String(value), Nothing, DataProtectionScope.CurrentUser)
            If Nothing Is decryptedBytes OrElse 0 = decryptedBytes.Length Then
                Return Nothing
            End If

            Return Encoding.UTF8.GetString(decryptedBytes, 0, decryptedBytes.Length)
        End Function
        #End Region
    End Class
    #End Region
    #End Region
    #End Region
End Namespace

See Also

Use the sample and helper code
Helper code: SystemUserProvider class
Use the sample and helper code
Authenticate users in Microsoft Dynamics 365
Helper code: ServerConnection class

Microsoft Dynamics 365

© 2016 Microsoft. All rights reserved. Copyright