Windows Azure Companion

 

 
Una de las ventajas más importantes de la plataforma Windows Azure es que no sólo nos permite ejecutar nuestras aplicaciones web en .NET en la nube sino que además podemos desplegar cualquier aplicación web en cualquier lenguaje :D
En este post voy a centrarme en todos aquellos CMS por excelencia implementados en PHP: Drupal, Joomla y WordPress. Si bien podemos adaptarlos de forma manual utilizando el Web role de FastCGI, me gustaría mostraros una forma mucho más sencilla y con un entorno de administración muy útil gracias a Windows Azure Companion :)

¿Qué es Windows Azure Companion?

WAC se trata en realidad de una solución cloud debidamente preparada para la instalación y ejecución de aplicaciones PHP.
Como podemos ver en la imagen, el modo de proceder es bastante simple:
  1. Descargamos el paquete y el archivo de configuración de Windows Azure Companion.
  2. Establecemos los valores de configuración.
  3. Desplegamos el paquete.
  4. Elegimos las aplicaciones que queremos instalar.

Descarga de los paquetes de Windows Azure Companion

Actualmente, con la última release del mes de diciembre, tenemos varios tipos de descargas:
Tanto el primero como el segundo se tratan de los paquetes de despliegue con la única diferencia de que el segundo de ellos tiene configurados los endpoints necesarios para HTTPS. Por otro lado, ambos están pensados para ser ejecutados en una máquina virtual small.
Si necesitáramos cambiar algo más específico o simplemente modificar el tamaño de la máquina virtual por uno mayor, disponemos de un tercer paquete que contiene el código fuente.
En este post vamos a descargar el primero de ellos.

Configuración

Para poder hacer uso de esta aplicación, es necesario tanto un Hosted Service como una Storage Account, además de una o varias bases de datos, dependiendo del número de CMS que queramos instalar. 
Abrimos el archivo ServiceConfiguration.cscfg el cual muestra el siguiente aspecto:
<?xml version="1.0" encoding="utf-8"?>
<!--
  **********************************************************************************************

  This file was generated by a tool from the project file: ServiceConfiguration.cscfg

  Changes to this file may cause incorrect behavior and will be lost if the file is regenerated.

  **********************************************************************************************
-->
<ServiceConfiguration serviceName="WindowsAzureCompanion" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration" osFamily="1" osVersion="*">
  <Role name="AdminWebSite">
    <!-- Do not increase the instance count. Windows Azure Companion does not support more than 1 instances -->
    <Instances count="1" />
    <ConfigurationSettings>
      <!-- Application Details. Title and Welcome text -->
      <Setting name="ApplicationTitle" value="Windows Azure Companion" />
      <Setting name="ApplicationDescription" value="&lt;p&gt;The &lt;span class=&quot;textlink&quot;&gt;&lt;a href=&quot;http://www.microsoft.com/windowsazure/windowsazure&quot;&gt;Windows Azure platform&lt;/a&gt;&lt;/span&gt; is a cloud platform that allows applications to &#xD;&#xA;      be hosted and run at Microsoft datacenters. It provides a cloud operating system called Windows Azure that serves as the &#xD;&#xA;      runtime for the applications and provides a set of services that allow development, management and hosting of applications.&lt;/p&gt;&#xD;&#xA;      &#xD;&#xA;      &lt;p&gt;With its standards-based and interoperable approach, the Windows Azure platform supports multiple Internet protocols including &#xD;&#xA;      HTTP, XML, SOAP and REST. As an open platform, the Windows Azure platform allows developers to use multiples languages &#xD;&#xA;      (.NET, Java, and PHP &amp; other dynamic languages ) to build applications which run on Windows Azure and/or consume any of the &#xD;&#xA;      Windows Azure platform offerings.&lt;/p&gt;&#xD;&#xA;      &#xD;&#xA;      &lt;p&gt;The Windows Azure Companion aims to provide a seamless experience for deploying platform-level components as well as applications &#xD;&#xA;      on to Windows Azure. The first community preview of the Windows Azure Companion has been tested with PHP runtimes, frameworks, &#xD;&#xA;      and applications. However, the system may be extended to be used for deploying any component or application that has been tested &#xD;&#xA;      to run on Windows Azure.&lt;/p&gt;" />
      <!-- Windows Azure Storage Account Details. -->
      <Setting name="WindowsAzureStorageAccountName" value="ACCOUNT NAME" />
      <Setting name="WindowsAzureStorageAccountKey" value="PRIMARY ACCESS KEY" />
      <!-- Admin user name, password and e-mail. Please user strong username and password -->
      <Setting name="AdminUserName" value="USERNAME" />
      <Setting name="AdminPassword" value="PASSWORD" />
      <!-- Atom Feed for Product items to be installed. This URL should be internet addressable -->
      <Setting name="ProductListXmlFeed" value="http://wazstorage.blob.core.windows.net/azurecompanion/default/WindowsAzureCompanionFeed.xml" />
      <!-- Settings for Windows Azure Drive used for durability -->
      <Setting name="PHPApplicationsBackupContainerName" value="companionv2" />
      <Setting name="InstallationStatusConfigFileBlob" value="status.xml" />
      <Setting name="ProgressInformationFileBlob" value="progress.xml" />
      <Setting name="XDrivePageBlobName" value="backup.vhd" />
      <Setting name="XDriveSizeInMB" value="2000" />
      <!-- Max value is defined as 500 in ServiceDefinition.csdef file (XDriveLocalCache Local Resource) -->
      <!-- Windows Azure Drive size is defined in ServiceDefinition.csdef. Look for ApplicationsAndRuntimeResource -->
      <Setting name="XDriveCacheSizeInMB" value="500" />
      <!-- Diagnostics and Performance Counter Capture Frequency in minutes -->
      <Setting name="DiagnosticsAndPerformanceCounterCaptureFrequencyInMinutes" value="1" />
    </ConfigurationSettings>
  </Role>
</ServiceConfiguration>
Los valores obligatorios que debemos modificar son:
  • Windows Azure Storage Account Name: Podemos localizar este nombre en las propiedades de la storage account que vayamos a utilizar.


  • Windows Azure Storage Account Key: Se refiere a la clave primaria de la cuenta de storage, también en las propiedades en el portal.
  • AdminUserName y AdminPassword para el acceso a la parte de administración.
  • ProductListXMLFeed: Se trata de un XML donde están registrados todos los repositorios que tenemos disponibles para instalarlos a través de la plataforma. Por defecto he dejado indicado el propio del autor pero podemos añadir uno propio.

Despliegue

Para el despliegue, basta con acceder a través del portal de Windows Azure y crear un nuevo deployment en uno de los dos entornos del Hosted Service.
Una vez iniciado el servicio ¡Ya podemos comenzar a instalar CMS :D !
 

Instalación de aplicaciones

Gracias a la nueva característica FullIIS anunciada en el PDC 2010, esta aplicación está configurada poder generar diferentes sitios dentro del mismo role. 
Para poder instalar los CMS,  debemos acceder a nuestra URL a través del puerto 8080, el cual ha sido establecido para las labores de administración. Por ejemplo:
Nos logamos con las credenciales establecidas en el archivo ServiceConfiguration.cscfg para poder acceder a los apartados Platform y Applications.

Espero que sea de utilidad :)
¡Saludos!

 

Creación de un vhd para arrancarlo en modo nativo

 

 

 

Ayer me encontré con la necesidad de montar un Windows Server 2008 R2 en 64 bits. Ya que virtual PC no soporta imágenes en 64 bits y Virtual Box me daba problemas al querer instalar Hyper-V he decidio una solución con un gran rendimiento a la par que elegante: Montar un disco duro virtual (vhd) para poder arrancarlo en modo nativo  :D  

¿Qué necesitamos?

Creación del VHD

Lo primero que vamos a hacer es crear un disco duro virtual donde vamos a instalar posteriormente Windows Server 2008 R2 de 64 bits. Para ello, accedemos a la consola de comandos como administrador y ejecutamos lo siguiente: 
diskpart
create vdisk file=c:Win2K8R2x64.vhd maximum 40960
sel vdisk file=c:Win2k8R2x64.vhd
attach vdisk
list disk
 
Con el apartado anterior estamos creando un disco duro virtual de 40GB y lo montamos en el sistema. El siguiente paso será crear una partición y formatearla en NTFS para poder trabajar con el vhd. 
sel disk 2
create part primary
sel part 1
active
format fs=ntfs quick
assing

Instalación de Windows Server 2008 R2 64 Bits en el VHD

Una vez que tenemos el vhd montado y formateado ya podemos instalar el sistema operativo. Para ello necesitamos instalar The Windows® Automated Installation Kit (AIK) for Windows® 7. Accedemos a la consola de comandos de AIK a través de Inicio => Todos los programas => Microsoft Windows AIK => Deployment Tools Command Prompt como administrador. 
ImageX /apply c:en_windows2008_r2_x64sourcesinstall.wim 3 /check F:
Si desglosamos los parámetros de este comando analizamos lo siguiente:  
  • Imagex /apply nos dice la acción que vamos realizar. Este comando se utiliza para aplicar imágenes en HDs y VHDs.
  • c:en_windows2008_r2_x64sourcesinstall.wim es la ubicación del archivo .wim que es la imagen que queremos aplicar.
  • El 3 se utiliza para decir cuál de las imagenes queremos utilizar de las disponibles dentro del archivo wim. En el caso del DVD de Windows Server 2008 R2 de 64 bits disponemos de varias imágenes:
    1. Windows Server 2008 R2 Standard (Full Installation)
    2. Windows Server 2008 R2 Standard (Server Core Installation)
    3. Windows Server 2008  R2 Enterprise (Full Installation)
    4. Windows Server 2008 R2 Enterprise (Server Core Installation)
    5. Windows Server 2008 R2 Datacenter (Full Installation)
    6. Windows Server 2008 R2 Datacenter (Server Core Installation)
    7. Windows Web Server 2008 R2 (Full Installation)
    8. Windows Web Server 2008 R2 (Server Core Installation)

    En este post instalaremos la versión Enterprise, la cual ocupa el tercer lugar. 

  • /check es un parámetro opcional para comprobar la integridad del archivo install.wim.
  • F: se trata de la unidad asignada a nuestro VHD. 

Registrar el vhd en boot.ini

Como último paso debemos registrar el vhd en el boot para poder seleccionarlo en el menú durante el arranque.
En primer lugar, volvemos a la línea de comandos y desmontamos el vhd del sistema. 
 
diskpart
select vdisk file=c:Win2K8R2x64.vhd
detach vdisk
exit
Para registrar el nuevo disco, utilizamos el comando bcdedit a través de la línea de comandos: 
bcdedit /copy {current} /d "Windows Server 2008 R2 64 bits – VHD Boot"
bcdedit /set {nuevo_GUID} device vhd=[C:]Win2K8R2x64.vhd
bcdedit /set {nuevo_GUID} osdevice vhd=[C:]Win2K8R2x64.vhd
bcdedit /set {nuevo_GUID} detecthal on
Espero que sea de utilidad :D  
¡Saludos!

 

Leer archivos XML con JQuery

Desde que JQuery llegó a nuestras vidas, Javascript se ha convertido en un camino de rosas :D Tanto es así que en pocas líneas podemos conseguir el parseo completo de un archivo XML ¡Vamos a verlo!

En primer lugar hay que aclarar que podemos toparnos principalmente con tres formatos de XML diferentes:
  1. Utilizando atributos: Cada elemento guarda su información dentro de atributos. Los atributos deberían utilizarse para información predeterminada, metadatos, archivos grandes (los atributos tienden a ocupar menos bytes que los elementos).
    <?xml version="1.0" encoding="utf-8" ?>
    <Blogs>
      <blog name="WebLog Expert - Powerful log analyzer"
            description="Download and analyze Apache and IIS Web logs on your own PC."
            url=" http://www.weblogexpert.com/"/>
      <blog name="Professional drupal themes"
            description="For professional drupal themes with a unique look and feel, visit TopNotchThemes.com."
            url="http://www.topnotchthemes.com/"/>
      <blog name="Paypal Developer Community"
            description="The community for developping paypal payment processing scripts."
            url="http://www.pdncommunity.com" />"
    </Blogs>
  2. Utilizando nodos/elementos: Los elementos contienen a su vez otros elementos llamados nodos. Se suelen utilizar para almacenar datos.
    <?xml version="1.0" encoding="utf-8" ?>
    <Blogs>
      <blog>
        <name>WebLog Expert - Powerful log analyzer</name>
        <description>Download and analyze Apache and IIS Web logs on your own PC.</description>
        <url>http://www.weblogexpert.com/</url>
      </blog>
      <blog>
        <name>Professional drupal themes</name>
        <description>For professional drupal themes with a unique look and feel, visit TopNotchThemes.com.</description>
        <url>http://www.topnotchthemes.com/</url>
      </blog>
      <blog>
        <name>Paypal Developer Community</name>
        <description>The community for developping paypal payment processing scripts.</description>
        <url>http://www.pdncommunity.com</url>
      </blog>
    </Blogs>
  3. Mezcla de los dos anteriores.
Dependiendo del cómo esté almacenado utilizaremos un método u otro para recuperarlo.

Recuperación de atributos

//Read attributes

$.get("Attributes.xml", function (xml) {
    $(xml).find("blog").each(function () {

       var name = $(this).attr('name');
       var description = $(this).attr('description');
       var url = $(this).attr('url');

alert(name + " " + description + " " + url);
    });
});

Recuperación de nodos/elementos

 

//Read nodes
  $.get("Nodes.xml", function (xml) {
    $(xml).find("blog").each(function () {
       var name = $(this).find('name').text();
       var description = $(this).find('description').text();
      var url = $(this).find('url').text();

       alert(name + " " + description + " " + url);
    });
});

 

Lo que estamos haciendo en ambos casos es una iteración a través de each por cada elemento blog que tengamos en el archivo. Dependiendo del tipo que esperemos utilizaremos attr para recuperar los atributos y find para los elementos.
Espero que sea de utilidad :)

 

¡Saludos!

 

Log4net: trazando nuestras aplicaciones

 

 
En ocasiones es importante almacenar un registro con información sobre los procesos realizados, principalmente en el entorno de desarrollo. Otra de las necesidades comunes puede ser recibir un correo cuando una acción finaliza correcta y/o incorrectamente o simplemente mantener un registro de un determinado nivel de información. Log4net cubre todas estas necesidades por nosotros de una forma altamente configurable :D

 

Esta librería es fruto de su antecesora log4j (log for Java) la cual ha derivado para diferentes lenguajes como C++, PHP y por supuesto .NET.

Configuración

Toda la configuración de la librería puede estar incluida en el archivo de configuración o programáticamente. Personalmente pienso que la configuración debe estar contenida en los archivos que tienen ese fin :)
<configSections>
   <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
</configSections>
Una vez que tenemos definida la sección en configSections, personalizamos un apartado con la configuración para nuestras trazas. En este post mostraré una configuración para mostrar las mismas a través de la consola y por email.
<log4net>
    <logger name="Program">
      <level value="INFO" />
      <appender-ref ref="ConsoleAppender" />
    </logger>
    <logger name="ProductosRepository">
      <level value="WARN" />
      <appender-ref ref="SmtpAppender" />
    </logger>
    <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender" >
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />
      </layout>
    </appender>
    <appender name="SmtpAppender" type="log4net.Appender.SmtpAppender,log4net">
      <to value="destinatario@returngis.net" />
      <from value="remitente@returngis.net" />
      <subject value="Asunto del correo" />
      <smtpHost value="smtp.returngis.net" />
      <bufferSize value="512" />
      <lossy value="true" />
      <evaluator type="log4net.Core.LevelEvaluator,log4net">
        <threshold value="ERROR" />
      </evaluator>
      <layout type="log4net.Layout.PatternLayout,log4net">
        <conversionPattern value="%property{log4net:HostName} :: %level :: %message %newlineLogger: %logger%newlineThread: %thread%newlineDate: %date%newlineNDC: %newline%newline" />
      </layout>
    </appender>
</log4net>
Si nos fijamos en la configuración expuesta, vemos que tenemos en realidad dos apartados repetidos: logger y appender.

Elemento logger

Este elemento define la configuración para ser utilizada en un apartado específico de nuestro código. Me explico: Supongamos que queremos mostrar información por pantalla de los pasos que se van completando en una capa de nuestra aplicación, para saber qué parte de nuestro código se está ejecutando. Por otro lado, necesitamos recibir un email cada vez que en la lógica relacionada con la base de datos o los servicios se produzca un error. Gracias a este apartado, podemos definir diferentes configuraciones para poder usarlas conjuntamente en una misma aplicación. Como parámetros tenemos el nivel de información que queremos registrar y el tipo de salida (appender).
 El nivel de error es acumulativo, es decir, si nuestro nivel de error es de tipo INFO abarcará los niveles información, warnings, errores y errores fatales. Si por el contrario el nivel establecido es ERROR, este sólo cubrirá los errores y los errores fatales. Por lo tanto, el modo que habilita todos los niveles sería el modo DEBUG.

Elemento appender

Por otro lado, los appenders son los tipos de salida que podemos configurar. En este post voy a mostrar solamente la salida por consola y por SMTP para el envío de correos. Para más ejemplos sobre otros tipos podéis echar un vistazo a este enlace.
Como valores significativos podemos mencionar:
Pattern.Layout para definir información a mostrar a través de patrones. En el caso del ConsoleAppender que tenemos definido en el ejemplo la información sería mostrada de la siguiente forma:

 

Lossy se utiliza de forma conjunta con el elemento evaluator para evitar el consumo excesivo de recursos en producción cuando la ejecución de nuestro programa es normal. Sin embargo, si la aplicación registra un error registrará esta información con un pequeño batch de lo ocurrido momentos antes de la excepción :)

Los appenders que soportan esta opción son los siguientes:
  • log4net.Appender.AdoNetAppender
  • log4net.Appender.RemotingAppender
  • log4net.Appender.SmtpAppender
  • log4net.Appender.SmtpPickupDirAppender
  • log4net.Appender.BufferingForwardingAppender

Recuperar la configuración

Como comenté en el apartado anterior, dependiendo del cuál queramos que sea el nivel y el tipo de salida, debemos solicitar una configuración u otra a la hora de la creación de nuestro objeto del tipo ILog. A través del mismo podremos registrar las trazas necesarias. En este caso, he utilizado el nombre de la clase en la que me encuentro para dar nombre a los distintos apartados, pero podríamos utilizar, por ejemplo, el nombre del proyecto o cualquier otro nombre significativo.
private static readonly ILog Logger = LogManager.GetLogger(typeof(Program).Name);
El siguiente paso sería cargar la configuración desde el archivo de configuración, a través del siguiente método:
XmlConfigurator.Configure();
A partir de este momento estamos listos para comenzar a trazar nuestro código :D Para ello, basta con utilizar el objeto logger creado anteriormente de manera bastante similar a Trace.
Logger.Info("Retrieving files...");
Del mismo modo, podemos formatear strings:
Logger.ErrorFormat("{0}:{1}", ex.InnerException, ex.StackTrace);
Espero que sea de utilidad :)
¡Saludos!

Añadir web references en Visual Studio 2010

 

Como curiosidad, hoy me topé con la necesidad de añadir una referencia web en Visual Studio 2010 cuando mi sorpresa fue no encontrar dicha opción como esperaba, al igual que en versiones anteriores.
 

Visual Studio 2008

 

 Sin embargo en Visual Studio 2010 esta opción desaparece como tal.

Visual Studio 2010

¿Cómo agregamos referencias web entonces?

 

En esta nueva versión del IDE lo que debemos hacer es seleccionar en primer lugar Add Service Reference…  y pulsamos directamente sobre el botón Advanced…

 

En la parte inferior del nuevo cuadro de diálogo aparece un apartado llamado Compatibility donde podemos añadir las referencias web a través de la misma ventana que utilizábamos en versiones anteriores :D .
 

 

Espero que sea de utilidad :)
¡Saludos!

PowerShell para la administración de nuestras aplicaciones en Windows Azure

 

 
 
En muchos post hemos visto la forma de utilizar el portal de la plataforma para desplegar, actualizar, eliminar, etcétera los servicios que tenemos actualmente ejecutándose en la nube. Pero esta no es la única alternativa para realizar estas tareas más propias del administrador ;) . La otra opción es utilizando la API de administración que nos proporciona Windows Azure. Podemos hacer uso de la misma a través de REST o también usando Windows Azure Management CmdLets,  un snap-in para PowerShell que actúa como wrap de la API facilitándonos el trabajo. Podemos descargarlo a través del siguiente enlace.
Una vez descargado, descomprimimos el archivo y abrimos con Visual Studio la solución alojada en la carpeta code para compilar la misma. Es posible que no encuentre la librería Microsoft.WindowsAzure.StorageClient por lo que debemos eliminar la referencia existente y agregar la última versión del SDK 1.3 de esta librería. Compilamos la solución y ejecutamos el archivo startHere.cmd para instalar los componentes necesarios, además del snap-in.
Ya estamos listos para administrar nuestras aplicaciones a golpe de comando :) . Abrimos una ventana de Windows PowerShell/Windows PowerShell ISE  y ejecutamos lo siguiente:
Add-PSSnapin AzureManagementToolsSnapIn
Con este comando lo único que estamos haciendo es añadir el snap-in que acabamos de instalar a la sesión actual. Para comprobar que funcionó correctamente podemos recuperar los comandos disponibles:
Get-Command -PSSnapIn AzureManagementToolsSnapIn

Lanzar un nuevo despliegue

 Uno de los objetivos más comunes sería desplegar un paquete en uno de los entornos. Para ello utilizaremos el comando New-Deployment con los siguientes parámetros obligatorios:
New-Deployment -serviceName <LOWER_CASE_SERVICENAME>
-storageServiceName <LOWER_CASE_STORAGE_SERVICE_NAME>
-subscriptionId <YOUR_SUBSCRIPTION_ID>
-certificate (get-item cert:CurrentUserMY<CERTIFICATE_THUMPRINT>)
-slot staging –package <PACKAGE_LOCATION> -configuration <CONFIGURATION_LOCATION>
-label "From PS"
Un ejemplo del comando anterior podría ser el siguiente:
New-Deployment -serviceName returngis -storageServiceName myazurestorage
-subscriptionId 5rtegtsw-0i93d-46b0-5tre-nh64w24
-certificate (get-item cert:CurrentUserMY327BBBABFE909FDJGR9ERJGFGDDFS30E76D370F2F95)
-slot staging –package c:DeployWindowsAzurePowerShell.cspkg
-configuration C:DeployServiceConfiguration.cscfg -label "From PS"
(Los datos son ficticios ;) )
Si utilizamos un cliente para visualizar los containers en la cuenta de Storage que hemos incluido en la llamada, podemos ver que existe un nuevo container llamado mydeployments con todos los paquetes utilizados para los despliegues a través de PowerShell.
 
 
Si bien con el anterior comando hemos conseguido desplegar un nuevo paquete en el entorno de staging, la máquina virtual no ha sido arrancada :( Afortunadamente esta acción también podemos ejecutarla desde PowerShell utilizando el Set-DeploymentStatus:
Set-DeploymentStatus -status running -slot <staging/production>
-serviceName <LOWER_CASE_SERVICENAME>
-SubscriptionId <YOUR_SUBSCRIPTION_ID>
-certificate (get-item cert:CurrentUserMY<CERTIFICATE_THUMPRINT>)
Un ejemplo de lo anterior podría ser lo siguiente:
Set-DeploymentStatus -status running -slot staging
-serviceName returngis -SubscriptionId 5rtegtsw-0i93d-46b0-5tre-nh64w24
-certificate (get-item cert:CurrentUserMY327BBBABFE909FDJGR9ERJGFGDDFS30E76D370F2F95)

Eliminiar despliegue

Para eliminar un despliegue los parámetros son prácticamente los mismos que al realizar la subida, a excepción de especificar los paquetes.
Remove-deployment -slot staging -serviceName <LOWER_CASE_SERVICENAME>
 -subscriptionId <YOUR_SUBSCRIPTION_ID>
 -certificate (get-item cert:CurrentUserMY<CERTIFICATE_THUMPRINT>)
Si nos fijamos en la consola, podemos pensar que el despliegue se ha eliminado correctamente… Lo cual no es cierto.
 
 
El mensaje de error nos avisa que Windows Azure no puede eliminar un despliegue cuando el mismo se está ejecutando, por lo que tendremos que utilizar el comando anterior, Set-DeploymentStatus, pero esta vez para suspender la ejecución de la máquina virtual.
Set-DeploymentStatus -status suspended -slot <staging/production>
-serviceName <LOWER_CASE_SERVICENAME>
-SubscriptionId <YOUR_SUBSCRIPTION_ID>
-certificate (get-item cert:CurrentUserMY<CERTIFICATE_THUMPRINT>)

¿Cómo podemos saber cuándo ha finalizado un proceso?

Debido a que las acciones que lanzamos se llevan a cabo en segundo plano no podemos saber cuándo ha finalizado el proceso. Por ello, podemos asignarle un comando adicional separado por un pipeline para permanecer en espera hasta que la ejecución finalice. Este comando es Get-OperationStatus -WaitToComplete
Set-DeploymentStatus -status running -slot staging -serviceName <LOWER_CASE_STORAGE_SERVICE_NAME>
-SubscriptionId <YOUR_SUBSCRIPTION_ID>
-certificate (get-item cert:CurrentUserMY<CERTIFICATE_THUMPRINT>) | Get-OperationStatus -WaitToComplete
Si estamos utilizando Windows PowerShell ISE aparecerá una nueva ventana con el progreso:
 
 
Espero que sea de utilidad :)
¡Saludos!

 

Cloudes: Computación en Windows Azure

 

 
El 26 de Enero comenzamos con los eventos online en Cloudes, grupo de usuarios de cloud computing en España :D
Si bien ya habíamos anunciado nuestra existencia en LinkedIn, empezamos apostando fuerte por los eventos online con el objetivo de llegar al mayor número de personas posible.
 
En esta ocasión daremos una charla sobre la computación en Windows Azure. Veremos cuáles son las posibilidades hasta ahora, la forma de desarrollar, el nuevo portal de la plataforma, etcétera.
 

 

Para registraros podéis hacerlo a través del siguiente enlace.

 

¡Os esperamos!

Crear e instalar un servicio Windows

 

Estos días he estado trabajando con servicios Windows, los cuales se utilizan para realizar operaciones en background. Una de las principales ventajas de los mismos es que tenemos la posibilidad de ejecutarlos sin necesidad de tener un usuario logado en el sistema. 

En este post voy a mostrar la forma de crear un proyecto con una pequeña demo y varias formas para instalarlo. 

Creación del proyecto

Creamos un proyecto del tipo de Windows Service. En este post lo llamaré CheckerWindowsService
 

 

Cuando carga la plantilla de Visual Studio, vemos que tenemos un nuevo archivo llamado Service1.cs y el típico Program.cs que acompaña a las aplicaciones de consola. Si accedemos al primero de ellos, nos mostrará la parte de diseño que básicamente es un fondo gris :D Si pulsamos sobre ella con el botón derecho y hacemos clic en View Code veremos la siguiente estructura: 

 

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text; 

namespace CheckerWindowsService
{
    public partial class Service1 : ServiceBase
    {
        public Service1()
        {
            InitializeComponent();
        } 

        protected override void OnStart(string[] args)
        {
        } 

        protected override void OnStop()
        {
        }
    }
} 
La clase Service1 debe heredar de ServiceBase para obtener todos los métodos y propiedades relacionadas con los servicios Windows. En esta plantilla se muestran los métodos que normalmente se sobrescriben: OnStart donde insertaremos el código que queremos que se ejecute nada más iniciar el servicio y OnStop en el caso de que el mismo sea parado. Para este ejemplo, voy a renombrar la clase Service1.cs a CheckerService.cs

Implementación

Lo primero que debemos modificar son las propiedades del archivo CheckerService.cs 
 
  

 
Por nombrar algunas de las propiedades más utilizadas, AutoLog se utiliza para permitir que el servicio registre o no los inicios, paradas, etcétera en el visor de eventos del sistema (opción por defecto). CanPauseAndContinue nos da la posibilidad de pausar y continuar con la ejecución del servicio, CanStop pararlo (al hacer esta opción pasará por el método OnStop) y ServiceName donde asignamos el nombre del servicio. 

En esta demo lo que vamos a hacer es que cada minuto vamos a comprobar si existe algún proceso del explorador Firefox abierto y, de ser así, vamos a finalizarlo (y si se resiste matarlo :D ).  Sobre la vista de diseño pulsamos con el botón derecho y seleccionamos View Code para agregar lo siguiente: 
protected override void OnStart(string[] args)
{

   var timer = new Timer { AutoReset = true, Interval = 60000 };
   timer.Elapsed += timer_Elapsed;
   timer.Start();
}

private void timer_Elapsed(object sender, ElapsedEventArgs e)
{
    var processes = Process.GetProcessesByName("firefox");

    foreach (var process in processes)
    {
       process.CloseMainWindow();

       if (!process.HasExited)
       {
            process.Kill();
            process.Close();
       }
    }
}

Añadir Instalador

Una vez que tengamos nuestro código implementado, es necesario añadir un instalador a la solución. Para ello nos ponemos en el modo diseño y con el botón derecho seleccionamos Add Installer

 

 
 

En este momento se agregará una nueva clase llamado ProjectInstaller.cs con dos componentes. Hacemos clic sobre serviceInstaller1 donde indicaremos el nombre del servicio (el mismo ServiceName que en CheckerService.cs) y el tipo de inicio (Manual, Automático o Deshabilitado). Además podemos indicar el nombre a mostrar y una descripción de lo que hace la aplicación. Estos dos últimos datos serán los que se mostrarán en el listado de servicios de Windows. 
 
 
 
Por último, si seleccionamos el otro elemento, serviceProjectInstaller1, podemos seleccionar el tipo de cuenta con la que queremos que se ejecute el servicio. En este caso, utilizaré LocalSystem para tener los permisos suficientes para matar procesos. 
 

 

Instalación/Desinstalación

Para la instalación del servicio Windows tenemos varias alternativas: 
  • Utilizar la plantilla para el proyecto del tipo Setup Project
  • Crear instaladores con InstallShield.
  • El comando InstallUtil.exe desde la línea de comandos. 

Setup Project

Adjuntamos a nuestra solución un nuevo proyecto del tipo Setup Project.
 
 
Seleccionamos el proyecto que acabamos de crear y añadimos la salida del proyecto del servicio web. 
 
 

Aparecerá un cuadro de diálogo con el proyecto Windows Service por defecto donde dejaremos seleccionado Primary output y confirmamos.
 

 
En el Explorer Solution hacemos click en el botón Custom Actions Editor.

 

 

 

Y aparecerá una nueva ventana donde se alojan las acciones personalizadas. Con el botón derecho añadimos una nueva como se muestra en la imagen:
 

Seleccionamos Application Folder.
 

Y por último aparecerá únicamente Primary output from CheckerWindowsService que es la que añadimos anteriormente. Pulsamos en OK y ¡Listo!
 

 
Para instalarlo, compilamos nuestra solución y posteriormente el proyecto Setup Project. Seleccionamos el proyecto de instalación con el botón derecho y hacemos click en Install.

 

 
Para desinstalar la operación sería la misma pero con Unistall :D

InstallShield
De lo poco que pude trastear con InstallShield, pude ver que las opciones de configuración son mayores que con Setup Project y te permite una mejor personalización de las instalaciones.
 Si bien con Visual Studio 2010 puedes hacer uso de InstallShield Limited Edition.
 
 

Para poder crear un installer para un windows service necesitas tener al menos una versión Premium, obviamente de pago.

InstallUtil.exe 

Desde la línea de comandos también podemos instalar/desinstalar un servicio Windows. El comando InstallUtil.exe se instala automáticamente con Visual Studio y podemos ejecutarlo a través de Visual Studio Command Prompt.
 
Instalación

InstallUtil.exe miEjecutable.exe
Desinstalación
InstallUtil.exe /u miEjecutable.exe
Espero que os sea de utilidad :D  

¡Saludos!
 

Eliminar los snapshots de un blob de Windows Azure Storage

 

Si habéis tenido que trabajar alguna vez con los blobs de Windows Azure Storage, y más aún si habéis hecho uso de los snapshot, ya habréis tenido el placer de encontraros con un error como este:
 
 
Si bien teníamos una forma de eliminar un blob con todos sus snapshots
blob.Delete(new BlobRequestOptions()
{
    DeleteSnapshotsOption = DeleteSnapshotsOption.IncludeSnapshots
});
Cuando queríamos eliminar uno solo la cosa se complica :(
Hasta ahora para recuperar un snapshot la única solución era llamar al método GetBlobReference con la siguiente estructura:
blob.GetBlobReference("filename?snapshot=<DateTime>");
Además la fecha debía tener el siguiente formato: 2010-03-07T00%3A12%3A14.1496934Z 
 
La otra opción era dejar de lado el Storage Client y crear nuestra propia llamada REST con los parámetros oportunos.
En cualquiera de los dos casos no era una forma muy simplificada de llevar a cabo una acción tan sencilla como recuperar un snapshot hasta la versión 1.3 :D
A partir de ahora disponemos de una sobrecarga de CloudBlobClient.GetBlobReference (aunque yo lo habría puesto dentro de CloudBlobContainer o al menos poner la sobrecarga en ambos métodos GetBlobReference) a la cual podemos pasar como parámetros tanto la URI del blob como el DateTime del snapshot que queremos recuperar.
Un dato importante a tener en cuenta es que la fecha debe ser UTC y obviamente debe ser exacta a la almacenada. Digo esto por el incidente que tuve al intentar recuperar el DateTime desde un ActionLink de ASP.NET MVC:
<%: Html.ActionLink("Delete", "Delete", new { item.Uri, item.ContainerName, item.SnapShotTime})%>
Al hacer esto, lo que está ocurriendo es que realiza un ToString() del DateTime dejando fuera los milisegundos que tenía asociados el snapshotTime, por lo que la fecha exacta ya no coincide. En mi caso, en vez de permitir esta conversión, paso como parámetro directamente la propiedad Ticks la cual devuelve un long:
<%: Html.ActionLink("Delete", "Delete", new { item.Uri, item.ContainerName,
                          item.SnapShotTime.Ticks })%>
De esta manera mantengo la fecha exacta y sólo necesitamos realizar la conversión a DateTime UTC en el servidor:
public void DeleteBlob(string uri, string containerName, long snapShotTimeTicks)
{

    var container = GetContainer(containerName);
    CloudBlob blob;

    if (snapShotTimeTicks != 0)
    {
        var utcSnapshotTime = new DateTime(snapShotTimeTicks, DateTimeKind.Utc);
        blob = _blobClient.GetBlobReference(uri, utcSnapshotTime);
    }
    else
       blob = container.GetBlobReference(uri);

   blob.Delete();
}
Espero que sea de utilidad :)
¡Saludos!