Gaurav Mantri's Personal Blog.

Windows Azure Cloud Services, Extensions and Service Management API – Fun with Remote Desktop

I want you to try something for me (pretty please with cherry on top Smile). Fire up Visual Studio, create a simple Windows Azure Cloud Service and then without making any changes just publish that service. When you publish the service, DO NOT ENABLE REMOTE DESKTOP!

image

Once the service is deployed, head on to Windows Azure Portal (https://manage.windowsazure.com), navigate to the cloud service you just deployed and click on the “CONFIGURE” tab. Once there, just click on the “REMOTE” button below. You’ll be greeted with the following screen:

image

Now follow the instructions there and enable remote desktop. Once the process is complete, go to “INSTANCES” tab and click on “CONNECT” button below. What you’ll notice is that you’re able to RDP into your instances. If you’ve been using Windows Azure for some time, you know that in order to remote desktop into your instances you would have to enable this functionality when you’re publishing the service. If you forgot doing this at the time of deployment, you would need to go through an upgrade or new deployment process just to enable this functionality.

However what you saw just now is that you did not do anything of that sort. You published the service and then enabled this functionality on the fly! So, how did this happen? I’ve got one sentence for you:

Your Cloud Service just got Extensions! Smile

In this blog post, we’ll talk about “Cloud Services Extensions”. We’ll talk about what they are, how you can work with them and finally we’ll see some code which will allow you to enable/disable this functionality on the fly.

So, let’s get started!

Extensions

Extensions are the newest functionality available through Windows Azure Service Management API. The way I understand it is that extensions allow you to dynamically add/remove some functionality in your cloud services without redeploying your code. At the time of writing this blog, there’re two things which you could do – Enable/Disable Remote Desktop (RDP) and Enable/Disable Windows Azure Diagnostics (WAD). Yes, you read it right!! Now you can enable/disable diagnostics for your cloud services on the fly without redeploying your service. No more messy code for diagnostics configuration and trying to remember things. I’ll cover diagnostics in a separate post. For now let’s focus on core extensions functionality.

The way it works today is that Windows Azure team has published some pre-defined extensions and you could enable/disable these extensions in your cloud service using Service Management API. Service Management API has added some functions to work with these extensions. The way the extensions functionality work is that you define settings associated with that extension; some of these settings will be public (i.e. you can fetch them later on e.g. RDP username) while some of the settings are private (i.e. you can’t fetch them after they’re set e.g. RDP password). You would define public settings in a public configuration file and private settings in a private configuration file. The configuration file are XML files. In order to determine the structure of these XML files, Service Management API exposes relevant schemas (public configuration schema and private configuration schema) using which you can construct XML files. Once the XML configuration files are defined, you can add an extension to your cloud service. However adding an extension does not do any magic. You would need to apply that extension to your cloud service which you can do through “Change Deployment Configuration” operation.

In this section we’ll focus on these functions and we’ll put them to practical use in next section. What I did was create a simple console application and defined some classes and methods to work with the extensions.

Based on the data returned by various functions for extensions management, I created a simple class called “AzureExtension” and this is what it looks like:

    public class AzureExtension
    {
        /// <summary>
        /// The provider namespace of the extension. The provider namespace for 
        /// Windows Azure extensions is Microsoft.WindowsAzure.Extensions.
        /// </summary>
        public string ProviderNamespace
        {
            get;
            set;
        }

        /// <summary>
        /// The type of the extension e.g. RDP for Remote Desktop.
        /// </summary>
        public string Type
        {
            get;
            set;
        }

        /// <summary>
        /// The version of the extension.
        /// </summary>
        public string Version
        {
            get;
            set;
        }

        /// <summary>
        /// The label that is used to identify the extension.
        /// </summary>
        public string Label
        {
            get;
            set;
        }

        /// <summary>
        /// The description of the extension.
        /// </summary>
        public string Description
        {
            get;
            set;
        }

        /// <summary>
        /// The type of resource that supports the extension. This value can be 
        /// WebRole, WorkerRole, or WebRole|WorkerRole.
        /// </summary>
        public string HostingResource
        {
            get;
            set;
        }

        /// <summary>
        /// The thumbprint algorithm of the certificate that is used for encryption.
        /// </summary>
        public string ThumbprintAlgorithm
        {
            get;
            set;
        }

        /// <summary>
        /// The thumbprint of the certificate that is used to encrypt the configuration specified in PrivateConfiguration. 
        /// If this element is not specified, a certificate may be automatically generated and added to the cloud service.
        /// </summary>
        public string Thumbprint
        {
            get;
            set;
        }

        /// <summary>
        /// The base64-encoded schema of the public configuration.
        /// </summary>
        public string PublicConfigurationSchema
        {
            get;
            set;
        }

        /// <summary>
        /// XML configuration based on PublicConfigurationSchema
        /// </summary>
        public string PublicConfiguration
        {
            get;
            set;
        }

        /// <summary>
        /// The base64-encoded schema of the private configuration.
        /// </summary>
        public string PrivateConfigurationSchema
        {
            get;
            set;
        }

        /// <summary>
        /// XML configuration based on PrivateConfigurationSchema
        /// </summary>
        public string PrivateConfiguration
        {
            get;
            set;
        }

        /// <summary>
        /// Extension id.
        /// </summary>
        public string Id
        {
            get;
            set;
        }

        public static AzureExtension Parse(XElement extensionXml)
        {
            var extension = new AzureExtension();
            foreach (var xe in extensionXml.Elements())
            {
                var elementName = xe.Name.LocalName;
                var elementValue = xe.Value;
                switch (elementName.ToUpperInvariant())
                {
                    case "PROVIDERNAMESPACE":
                        extension.ProviderNamespace = elementValue;
                        break;
                    case "TYPE":
                        extension.Type = elementValue;
                        break;
                    case "VERSION":
                        extension.Version = elementValue;
                        break;
                    case "THUMBPRINT":
                        extension.Thumbprint = elementValue;
                        break;
                    case "THUMBPRINTALGORITHM":
                        extension.ThumbprintAlgorithm = elementValue;
                        break;
                    case "PUBLICCONFIGURATION":
                        extension.PublicConfiguration = Encoding.UTF8.GetString(Convert.FromBase64String(elementValue));
                        break;
                    case "PRIVATECONFIGURATION":
                        extension.PrivateConfiguration = Encoding.UTF8.GetString(Convert.FromBase64String(elementValue));
                        break;
                    case "PUBLICCONFIGURATIONSCHEMA":
                        extension.PublicConfigurationSchema = Encoding.UTF8.GetString(Convert.FromBase64String(elementValue));
                        break;
                    case "PRIVATECONFIGURATIONSCHEMA":
                        extension.PrivateConfigurationSchema = Encoding.UTF8.GetString(Convert.FromBase64String(elementValue));
                        break;
                    case "LABEL":
                        extension.Label = elementValue;
                        break;
                    case "DESCRIPTION":
                        extension.Description = elementValue;
                        break;
                    case "HOSTINGRESOURCE":
                        extension.HostingResource = elementValue;
                        break;
                    case "ID":
                        extension.Id = elementValue;
                        break;
                }
            }
            return extension;
        }

        public XElement ConvertToXml()
        {
            XElement extensionXElement = new XElement(XName.Get("Extension", "http://schemas.microsoft.com/windowsazure"));
            extensionXElement.Add(new XElement(XName.Get("ProviderNameSpace", "http://schemas.microsoft.com/windowsazure"), this.ProviderNamespace));
            extensionXElement.Add(new XElement(XName.Get("Type", "http://schemas.microsoft.com/windowsazure"), this.Type));
            extensionXElement.Add(new XElement(XName.Get("Id", "http://schemas.microsoft.com/windowsazure"), this.Id));
            if (!string.IsNullOrWhiteSpace(this.Thumbprint))
            {
                extensionXElement.Add(new XElement(XName.Get("Thumbprint", "http://schemas.microsoft.com/windowsazure"), this.Thumbprint));
                extensionXElement.Add(new XElement(XName.Get("ThumbprintAlgorithm", "http://schemas.microsoft.com/windowsazure"), this.ThumbprintAlgorithm));
            }
            extensionXElement.Add(new XElement(XName.Get("PublicConfiguration", "http://schemas.microsoft.com/windowsazure"), Convert.ToBase64String(Encoding.UTF8.GetBytes(this.PublicConfiguration))));
            extensionXElement.Add(new XElement(XName.Get("PrivateConfiguration", "http://schemas.microsoft.com/windowsazure"), Convert.ToBase64String(Encoding.UTF8.GetBytes(this.PrivateConfiguration))));
            return extensionXElement;
        }
    }

Now that the entity to manipulate extensions is defined, let’s look at the functions.

List Available Extensions

This function returns the list of all extensions available to your subscription. Here’s the sample code for listing all extensions available to you:

        /// <summary>
        /// Gets a list of all available extensions.
        /// </summary>
        /// <param name="subscriptionId">
        /// Subscription id of the subscription.
        /// </param>
        /// <param name="cert">
        /// Management certificate for authenticating Service Management API requests.
        /// </param>
        /// <returns>
        /// </returns>
        private static IEnumerable<AzureExtension> ListAvailableExtensions(string subscriptionId, X509Certificate2 cert)
        {
            List<AzureExtension> extensions = new List<AzureExtension>();
            string uri = string.Format("https://management.core.windows.net/{0}/services/extensions", subscriptionId);
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
            request.Method = "GET";
            request.Headers.Add("x-ms-version", "2013-03-01");
            request.ClientCertificates.Add(cert);
            using (HttpWebResponse resp = (HttpWebResponse)request.GetResponse())
            {
                using (var streamReader = new StreamReader(resp.GetResponseStream()))
                {
                    string result = streamReader.ReadToEnd();
                    Console.WriteLine(result);
                    XElement extensionImagesXelement = XElement.Parse(result);
                    foreach (var extensionImageXelement in extensionImagesXelement.Elements(XName.Get("ExtensionImage", "http://schemas.microsoft.com/windowsazure")))
                    {
                        extensions.Add(AzureExtension.Parse(extensionImageXelement));
                    }
                }
            }
            return extensions;
        }

For more details, please click here: http://msdn.microsoft.com/en-us/library/windowsazure/dn169559.aspx.

Add Extension

This function adds an extension to the list of available extensions in a cloud service. As mentioned above, after adding an extension you would need to perform “Change Deployment Configuration” operation for that extension to be enabled on a cloud service. Here’s the sample code for adding an extension to a cloud service:

        /// <summary>
        /// Adds an extension to a cloud service. Just by adding an extension won't do any good!
        /// You must call the "Change Deployment Configuration" to apply this extension to the 
        /// running instance of your cloud service.
        /// </summary>
        /// <param name="subscriptionId">
        /// Subscription id of the subscription.
        /// </param>
        /// <param name="cert">
        /// Management certificate for authenticating Service Management API requests.
        /// </param>
        /// <param name="cloudServiceName">
        /// Name of cloud service.
        /// </param>
        /// <param name="extension">
        /// Extension which needs to be added.
        /// </param>
        private static void AddExtension(string subscriptionId, X509Certificate2 cert, string cloudServiceName, AzureExtension extension)
        {
            string uri = string.Format("https://management.core.windows.net/{0}/services/hostedservices/{1}/extensions", subscriptionId, cloudServiceName);
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
            request.Method = "POST";
            request.ContentType = "application/xml";
            request.Headers.Add("x-ms-version", "2013-03-01");
            request.ClientCertificates.Add(cert);
            string requestPayload = string.Format("<?xml version=\"1.0\" encoding=\"UTF-8\"?>{0}", extension.ConvertToXml());
            byte[] content = Encoding.UTF8.GetBytes(requestPayload);
            request.ContentLength = content.Length;
            using (var requestStream = request.GetRequestStream())
            {
                requestStream.Write(content, 0, content.Length);
            }

            using (HttpWebResponse resp = (HttpWebResponse)request.GetResponse())
            {
            }
        } 

Please note that when adding extension, if certificate thumbprint is not provided Service Management API automatically creates a certificate for you and associate that with the cloud service.

For more details, please click here: http://msdn.microsoft.com/en-us/library/windowsazure/dn169558.aspx.

List Extensions

This function returns all the extensions defined for a particular cloud service. Here’s the sample code to get the list of all extensions for a cloud service:

        /// <summary>
        /// Gets a list of all extensions enabled in a cloud service.
        /// </summary>
        /// <param name="subscriptionId">
        /// Subscription id of the subscription.
        /// </param>
        /// <param name="cert">
        /// Management certificate for authenticating Service Management API requests.
        /// </param>
        /// <param name="cloudServiceName">
        /// Name of cloud service.
        /// </param>
        /// <returns></returns>
        private static IEnumerable<AzureExtension> ListExtensions(string subscriptionId, X509Certificate2 cert, string cloudServiceName)
        {
            List<AzureExtension> extensions = new List<AzureExtension>();
            string uri = string.Format("https://management.core.windows.net/{0}/services/hostedservices/{1}/extensions", subscriptionId, cloudServiceName);
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
            request.Method = "GET";
            request.Headers.Add("x-ms-version", "2013-03-01");
            request.ClientCertificates.Add(cert);
            using (HttpWebResponse resp = (HttpWebResponse)request.GetResponse())
            {
                using (var streamReader = new StreamReader(resp.GetResponseStream()))
                {
                    string result = streamReader.ReadToEnd();
                    Console.WriteLine(result);
                    XElement extensionImagesXelement = XElement.Parse(result);
                    foreach (var extensionImageXelement in extensionImagesXelement.Elements(XName.Get("Extension", "http://schemas.microsoft.com/windowsazure")))
                    {
                        extensions.Add(AzureExtension.Parse(extensionImageXelement));
                    }
                }
            }
            return extensions;
        } 

For more details, please click here: http://msdn.microsoft.com/en-us/library/windowsazure/dn169561.aspx.

Get Extension

This function returns the details of a particular extension in a cloud service. Here’s the sample code for getting extension details:

        /// <summary>
        /// Gets the details of a particular extension in a cloud service.
        /// </summary>
        /// <param name="subscriptionId">
        /// Subscription id of the subscription.
        /// </param>
        /// <param name="cert">
        /// Management certificate for authenticating Service Management API requests.
        /// </param>
        /// <param name="cloudServiceName">
        /// Name of cloud service.
        /// </param>
        /// <param name="extensionId">
        /// Extension id.
        /// </param>
        /// <returns></returns>
        private static AzureExtension GetExtension(string subscriptionId, X509Certificate2 cert, string cloudServiceName, string extensionId)
        {
            string uri = string.Format("https://management.core.windows.net/{0}/services/hostedservices/{1}/extensions/{2}", subscriptionId, cloudServiceName, extensionId);
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
            request.Method = "GET";
            request.Headers.Add("x-ms-version", "2013-03-01");
            request.ClientCertificates.Add(cert);
            using (HttpWebResponse resp = (HttpWebResponse)request.GetResponse())
            {
                using (var streamReader = new StreamReader(resp.GetResponseStream()))
                {
                    string result = streamReader.ReadToEnd();
                    Console.WriteLine(result);
                    XElement extensionDetailsXElement = XElement.Parse(result);
                    return AzureExtension.Parse(extensionDetailsXElement);
                }
            }
        } 

For more details, please click here: http://msdn.microsoft.com/en-us/library/windowsazure/dn169557.aspx.

Delete Extension

This function removes an extension. Here’s the sample code for deleting an extension from a cloud service:

        /// <summary>
        /// Removes a  particular extension from a cloud service.
        /// </summary>
        /// <param name="subscriptionId">
        /// Subscription id of the subscription.
        /// </param>
        /// <param name="cert">
        /// Management certificate for authenticating Service Management API requests.
        /// </param>
        /// <param name="cloudServiceName">
        /// Name of cloud service.
        /// </param>
        /// <param name="extensionId">
        /// Extension id.
        /// </param>
        private static void DeleteExtension(string subscriptionId, X509Certificate2 cert, string cloudServiceName, string extensionId)
        {
            string uri = string.Format("https://management.core.windows.net/{0}/services/hostedservices/{1}/extensions/{2}", subscriptionId, cloudServiceName, extensionId);
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
            request.Method = "DELETE";
            request.Headers.Add("x-ms-version", "2013-03-01");
            request.ClientCertificates.Add(cert);
            using (HttpWebResponse resp = (HttpWebResponse)request.GetResponse())
            {
            }
        } 

For more details, please click here: http://msdn.microsoft.com/en-us/library/windowsazure/dn169560.aspx.

That’s pretty much to it!

Remote Desktop (RDP)

Now that we’ve seen how extensions work, let’s put this together for some practical use. What we’ll do is enable remote desktop on the fly using the extensions. For the sake of simplicity, we’ll enable RDP on all the roles in our cloud service.

Step 1: Get the list of all extensions

To do so, we’ll make use of “List Available Extensions” and find out the public and private configuration schema for RDP. Here’s the sample code to do so:

            var allExtensions = ListAvailableExtensions(subscriptionId, cert);
            var rdpExtension = allExtensions.FirstOrDefault(e => e.Type == "RDP");

Once we get the details about the RDP extension, we can just take the configuration schemas. Here’s what the service currently returns:

Public Configuration Schema:

<?xml version="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="PublicConfig">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="UserName" type="xs:string" minOccurs="1" />
        <xs:element name="Expiration" type="xs:string" minOccurs="1" />
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

Private Configuration Schema:

<?xml version="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="PrivateConfig">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="Password" type="xs:string" />
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

Based on these, we’ll create public and private configuration:

Public Configuration:

<?xml version="1.0" encoding="UTF-8"?>
<PublicConfig>
  <UserName>{0}</UserName>
  <Expiration>{1}</Expiration>
</PublicConfig>

Private Configuration:

<?xml version="1.0" encoding="UTF-8"?>
<PrivateConfig>
  <Password>{0}</Password>
</PrivateConfig>

Step 2: Add Extension

Next step is adding extension to the cloud service. To do so, we’ll make use of “Add Extension” functionality. Here’s the sample code to do so.

            AzureExtension rdpExtension = new AzureExtension()
            {
                ProviderNamespace = "Microsoft.Windows.Azure.Extensions",
                Type = "RDP",
                Id = "RDP-" + Guid.NewGuid().ToString(),
                PublicConfiguration = string.Format(publicConfigurationFormat, userName, expiryDate.ToString("yyyy-MM-dd")),
                PrivateConfiguration = string.Format(privateConfigurationFormat, password),
            };

            AddExtension(subscriptionId, cert, cloudServiceName, rdpExtension);

Here I’m passing the username, password and the RDP expiry date. Please note that since I’ve not specified a certificate thumbprint Windows Azure will automatically create a new certificate and associate with my cloud service. Also I noticed that the password needs to be a strong password. I actually spent quite some time to realize this. If you provide a simple password (like “password”), the operation would complete however you will not be able to connect to your role instances using RDP. You’ll get a “login failed” message. Your password should have 3 of the following – a lowercase character, an uppercase character, a number, and a special character.

Step 3: Update Configuration

Once the extension is added, next step would be to update the cloud service configuration by performing “Change Deployment Configuration” operation. For this, first we may need to fetch the current configuration. To do so, we’ll perform “Get Hosted Service Properties” operation and try to get both production and staging configuration settings. Here’s the sample code to get the configuration settings:

        private static string[] GetCloudServiceDeploymentConfigurations(string subscriptionId, X509Certificate2 cert, string cloudServiceName)
        {
            string[] deploymentConfigurations = new string[2];//We'll try to get both production and staging deployment configurations. The 1st element will always be production and the 2nd will be staging.
            string uri = string.Format("https://management.core.windows.net/{0}/services/hostedservices/{1}?embed-detail=true", subscriptionId, cloudServiceName);
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
            request.Method = "GET";
            request.Headers.Add("x-ms-version", "2013-03-01");
            request.ClientCertificates.Add(cert);
            using (HttpWebResponse resp = (HttpWebResponse)request.GetResponse())
            {
                using (var streamReader = new StreamReader(resp.GetResponseStream()))
                {
                    string result = streamReader.ReadToEnd();
                    XElement cloudServiceProperties = XElement.Parse(result);
                    var deployments = cloudServiceProperties.Elements(XName.Get("Deployments", "http://schemas.microsoft.com/windowsazure"));
                    foreach (var deployment in deployments.Elements())
                    {
                        var slotElement = deployment.Element(XName.Get("DeploymentSlot", "http://schemas.microsoft.com/windowsazure"));
                        var configElement = deployment.Element(XName.Get("Configuration", "http://schemas.microsoft.com/windowsazure"));
                        var deploymentSlot = slotElement.Value;
                        var configurationSettings = Encoding.UTF8.GetString(Convert.FromBase64String(configElement.Value));
                        switch (deploymentSlot.ToUpper())
                        {
                            case "PRODUCTION":
                                deploymentConfigurations[0] = configurationSettings;
                                break;
                            case "STAGING":
                                deploymentConfigurations[1] = configurationSettings;
                                break;
                        }
                    }
                }
            }
            return deploymentConfigurations;
        }

Now that we’ve got the configurations for both production and staging slots, let’s apply the changes. Here’s the sample code for change deployment configuration operation:

        private static void ChangeDeploymentConfiguration(string subscriptionId, X509Certificate2 cert, string cloudServiceName, string slot, string configuration)
        {
            string uri = string.Format("https://management.core.windows.net/{0}/services/hostedservices/{1}/deploymentslots/{2}/?comp=config", subscriptionId, cloudServiceName, slot);
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
            request.Method = "POST";
            request.ContentType = "application/xml";
            request.Headers.Add("x-ms-version", "2013-03-01");
            request.ClientCertificates.Add(cert);
            byte[] content = Encoding.UTF8.GetBytes(configuration);
            request.ContentLength = content.Length;
            using (var requestStream = request.GetRequestStream())
            {
                requestStream.Write(content, 0, content.Length);
            }

            using (HttpWebResponse resp = (HttpWebResponse)request.GetResponse())
            {
            }
        }

And this is how you can call it.

            string updateConfigurationFormat = @"<?xml version=""1.0"" encoding=""utf-8""?>
                <ChangeConfiguration xmlns=""http://schemas.microsoft.com/windowsazure"">
                    <Configuration>{0}</Configuration>
                    <ExtensionConfiguration>
                        <AllRoles>
                          <Extension>
                            <Id>{1}</Id>
                          </Extension>
                        </AllRoles>
                     </ExtensionConfiguration>
                </ChangeConfiguration>";

                var productionConfigurationSetting = string.Format(updateConfigurationFormat, Convert.ToBase64String(Encoding.UTF8.GetBytes(productionConfig)), rdpExtension.Id);
                ChangeDeploymentConfiguration(subscriptionId, cert, cloudServiceName, "Production", productionConfigurationSetting);

Putting it all together nicely in just one function:

        private static void EnableRemoteDesktop(string subscriptionId, X509Certificate2 cert, string cloudServiceName, string userName, string password, DateTime expiryDate)
        {
            string publicConfigurationFormat = @"<?xml version=""1.0"" encoding=""UTF-8""?>
                <PublicConfig>
                    <UserName>{0}</UserName>
                    <Expiration>{1}</Expiration>
                </PublicConfig>";

            string privateConfigurationFormat = @"<?xml version=""1.0"" encoding=""UTF-8""?>
                <PrivateConfig>
                    <Password>{0}</Password>
                </PrivateConfig>";

            string updateConfigurationFormat = @"<?xml version=""1.0"" encoding=""utf-8""?>
                <ChangeConfiguration xmlns=""http://schemas.microsoft.com/windowsazure"">
                    <Configuration>{0}</Configuration>
                    <ExtensionConfiguration>
                        <AllRoles>
                          <Extension>
                            <Id>{1}</Id>
                          </Extension>
                        </AllRoles>
                     </ExtensionConfiguration>
                </ChangeConfiguration>";

            //Define RDP Extension
            AzureExtension rdpExtension = new AzureExtension()
            {
                ProviderNamespace = "Microsoft.Windows.Azure.Extensions",
                Type = "RDP",
                Id = "RDP-" + Guid.NewGuid().ToString(),
                PublicConfiguration = string.Format(publicConfigurationFormat, userName, expiryDate.ToString("yyyy-MM-dd")),
                PrivateConfiguration = string.Format(privateConfigurationFormat, Convert.ToBase64String(Encoding.UTF8.GetBytes(password))),
            };

            //Add extension
            AddExtension(subscriptionId, cert, cloudServiceName, rdpExtension);

            //Get deployment configurations
            var configurations = GetCloudServiceDeploymentConfigurations(subscriptionId, cert, cloudServiceName);

            var productionConfig = configurations[0];
            if (!string.IsNullOrWhiteSpace(productionConfig))
            {
                //Apply RDP extension to production slot.
                var productionConfigurationSetting = string.Format(updateConfigurationFormat, Convert.ToBase64String(Encoding.UTF8.GetBytes(productionConfig)), rdpExtension.Id);
                ChangeDeploymentConfiguration(subscriptionId, cert, cloudServiceName, "Production", productionConfigurationSetting);
            }
            var stagingConfig = configurations[1];
            if (!string.IsNullOrWhiteSpace(stagingConfig))
            var stagingConfig = configurations[1];
            if (!string.IsNullOrWhiteSpace(stagingConfig))
            {
                //Apply RDP extension to staging slot.
                var stagingConfigurationSetting = string.Format(updateConfigurationFormat, Convert.ToBase64String(Encoding.UTF8.GetBytes(stagingConfig)), rdpExtension.Id);
                ChangeDeploymentConfiguration(subscriptionId, cert, cloudServiceName, "Staging", stagingConfigurationSetting);
            }
        }

That’s pretty much it!!! Once the operation is complete, you should be able to RDP into your instances.

Wish List

My only wish here is that the API team has not clubbed this extensions functionality with change deployment configuration and provided direct operations for enabling/disabling the extensions. Clubbing this functionality with change deployment configuration may lead to some inadvertent errors. Other than that, I wish they had an “Update Extension” operation. Currently, I don’t know how I would update an extension. That functionality can be real handy.

Summary

I think the extensions functionality is pretty awesome. In due course of time when Windows Azure team starts accepting components from ISVs, it would open up a lot of opportunities. Even now, the flexibility offered by built-in extensions is quite helpful to the developers. As always, if you find any issues with the post please let me know and I’ll fix it ASAP.

Happy Coding!


[This is the latest product I'm working on]