Статьи

Облачные службы Windows Azure, API-интерфейсы расширения и управления службами

Я хочу, чтобы ты попробовал кое-что для меня (довольно, пожалуйста, с вишней сверху  Улыбка). Запустите Visual Studio, создайте простую облачную службу Windows Azure, а затем, не внося никаких изменений, просто опубликуйте эту службу. Когда вы публикуете сервис,  НЕ ВКЛЮЧАЙТЕ УДАЛЕННОЕ НАСТОЛЬНОЕ!

образ

После развертывания службы перейдите на портал Windows Azure ( https://manage.windowsazure.com ), перейдите к только что развернутой облачной службе и перейдите на вкладку « КОНФИГУРАЦИЯ ». Оказавшись там, просто нажмите кнопку « УДАЛЕНО » ниже. Вас встретит следующий экран:

образ

Теперь следуйте инструкциям и включите удаленный рабочий стол. После завершения процесса перейдите на вкладку « INSTANCES » и нажмите кнопку « CONNECT » ниже. Что вы заметите, так это то, что вы можете использовать RDP в своих экземплярах Если вы использовали Windows Azure в течение некоторого времени, вы знаете, что для удаленного доступа к рабочему столу в ваших экземплярах вам нужно будет включить эту функцию при публикации службы. Если вы забыли сделать это во время развертывания, вам нужно будет выполнить обновление или новый процесс развертывания, чтобы включить эту функцию.

Однако то, что вы видели только сейчас, это то, что вы ничего такого не делали Вы опубликовали сервис, а затем включили эту функцию на лету! Итак, как это случилось? У меня есть одно предложение для вас:

Ваш облачный сервис только что получил расширения! Улыбка

В этом посте мы поговорим о « Расширениях облачных сервисов ». Мы поговорим о том, что они из себя представляют, как вы можете с ними работать, и, наконец, увидим некоторый код, который позволит вам включить / отключить эту функцию на лету.

Итак, начнем!

расширения

Расширения — это новейшая функциональность, доступная через Windows Azure Service Management API. Насколько я понимаю,  расширения позволяют динамически добавлять / удалять некоторые функции в облачных сервисах без повторного развертывания кода . На момент написания этого блога можно было сделать две вещи:  включить / отключить удаленный рабочий стол (RDP)  и  включить / отключить диагностику Windows Azure (WAD) . Да, вы правильно прочитали !! Теперь вы можете включать / отключать диагностику для ваших облачных сервисов на лету без повторного развертывания вашего сервиса. Нет больше грязного кода для настройки диагностики и попыток запомнить вещи. Диагностику я расскажу в отдельном посте. А пока давайте сосредоточимся на функциональности основных расширений.

Сегодня это работает так, что команда Windows Azure опубликовала несколько предопределенных расширений, и вы можете включить / отключить эти расширения в облачной службе с помощью API управления службами. В Service Management API добавлены некоторые функции для работы с этими расширениями. Функциональность расширений работает так, что вы определяете настройки, связанные с этим расширением; некоторые из этих настроек будут общедоступными (т.е. вы можете получить их позже, например, имя пользователя RDP), в то время как некоторые настройки являются частными (т.е. вы не сможете получить их после того, как они установлены, например, пароль RDP). Вы бы определили общедоступные настройки в общедоступном файле конфигурации и частные настройки в частном файле конфигурации. Файл конфигурации — это файлы XML. Чтобы определить структуру этих файлов XML,API управления службами предоставляет соответствующие схемы (общедоступная схема конфигурации и частная схема конфигурации), с помощью которых можно создавать файлы XML. Как только файлы конфигурации XML определены, вы можете добавить расширение к своей облачной службе. Однако добавление расширения не делает никакой магии. Вам нужно будет применить это расширение к облачному сервису, что вы можете сделать черезИзменить конфигурацию развертывания ».

В этом разделе мы сосредоточимся на этих функциях, и мы применим их к практическому использованию в следующем разделе. Я создал простое консольное приложение и определил некоторые классы и методы для работы с расширениями.

На основе данных, возвращаемых различными функциями для управления расширениями, я создал простой класс под названием « AzureExtension », и он выглядит следующим образом:

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!