[How To] Depurar un servicio Windows con C#

febrero 02, 2016 Christian Amado 0 Comentarios

Introducción

Un servicio Windows resulta muy útil a la hora de realizar determinadas tareas dentro del ambiente Windows (sobre todo si es Windows Server) porque nos permite realizar tareas cada X tiempo sin ninguna interacción con el usuario.

En esta entrada quiero mostrarles una forma muy práctica de Depurar un servicio de Windows de manera ágil sin tener que utilizar herramientas externas al Visual Studio, como ser installUtil.exe u otras. Tampoco necesitaremos crear un proyecto de instlación ni adjuntar un proceso a nuestro servicio para realizar dicha depuración.

Con todo esto logramos paos extras que desvían nuestra atención del objetivo real "Depurar un servicio de Windows".

Utilizando el código

Creamos un proyecto nuevo del tipo "Servicio de Windows", tal como se muestra aquí:

Una vez creado el proyecto, el explorador de soluciones se verá así:

Sin realizar otra opción, ejecutamos el servicio (F5) y recibiremos una advertencia (disculpen, mi VS está en inglés):

Es entendible, pues el servicio Windows no se puede depurar porque necesita estar presente en el sistema para que pueda ejecutarse. Obviamente, este no se encuentra en el sistema porque lo ejecutamos desde el Visual Studio que no puede instalar directamente un servicio sin la intervención de alguna herramienta citada en la introducción de esta entrada.

Hacemos clic derecho sobre el archivo Service1.cs y seleccionamos la opción "Ver código":

En el código del servicio, en C# para este caso, ingresamos este código:
using System;
using System.Diagnostics;
using System.ServiceProcess;
//Agregamos los espacios de nombres requeridos
using System.Timers;

namespace WindowsServiceDebug
{
    public partial class Service1 : ServiceBase
    {
        //Creamos un atributo para el temporizador
        private Timer temporizador;
        //Creamos el proceso aquí para poder terminarlo después.
        private Process proceso;

        ///         /// Iniciar el servicio con los argumentos necesarios        /// Lista de argumentos. Puede ser null
        internal void IniciarServicio(string[] args)
        {
            this.OnStart(args);
        }

        ///         /// Terminar el servicio
        ///         internal void TerminarServicio()
        {
            this.OnStop();
        }

        public Service1()
        {
            InitializeComponent();

            //Creamos la definición del temporizador
            temporizador = new Timer();

            //El intervalo lo establecemos mediante la Estructura TimeSpan y la propiedad TotalSeconds, 
            //que retornará 30 segundos.
            temporizador.Interval = new TimeSpan(0, 0, 30).TotalMilliseconds;

            temporizador.Elapsed += Temporizador_Elapsed;
        }

        private void Temporizador_Elapsed(object sender, ElapsedEventArgs e)
        {
            if (proceso != null)
            {
                proceso = null;
            }
                   
            proceso = new Process();
            var procesoInfo = new ProcessStartInfo("calc.exe");
            proceso.StartInfo = procesoInfo;
            proceso.Start();
        }

        protected override void OnStart(string[] args)
        {
            //Registramos el evento de inicio en el Visor de Eventos del Windows
            EventLog.WriteEntry("Service1 ha iniciado");

            //Iniciamos el temporizador
            temporizador.Start();
        }

        protected override void OnStop()
        {
            //Terminamos el temporizador
            temporizador.Stop();

            //REgistramos el evento de finalización en el Visor de Eventos del Windows
            EventLog.WriteEntry("Service1 ha terminado");

        }
    }
}

¿Qué hace este código?

El Timer se utiliza para ir contando el tiempo que va transcurrir para realizar una tarea en específico y el Process permite abrir un ejecutable cualquiera desde nuestro servicio Windows. El servicio tiene un Comienzo y una Parada que se registran en el Visor de Eventos del Windows. Cada 30 segundos, se abirá una calculadora hasta que el servicio sea detenido.

En el archivo Program.cs realizaremos un cambio muy importante:
static void Main(string[] args)
{
    //Verificamos si es una aplicación interactiva
    if (Environment.UserInteractive)
    {
        //Creamos una instancia del servicio
        Service1 service1 = new Service1();
        //Iniciamos el método inicial del servicio
        service1.IniciarServicio(args);

        //Creamos una condición de parada para el servicio
        Console.WriteLine("Escriba STOP para terminar el servicio");
        string ingreso = Console.ReadLine();

        if (ingreso.Equals("STOP", StringComparison.OrdinalIgnoreCase))
            service1.TerminarServicio();
    }
    else
    {
        //Flujo Normal de un servicio Windows
        ServiceBase[] ServicesToRun;
        ServicesToRun = new ServiceBase[]
        {
            new Service1()
        };
        ServiceBase.Run(ServicesToRun);
    }    
}

¿Qué hace este código?

Si estamos depurando (desde la consola, que permite Interacción de usuario) ejecutará el servicio directamente desde la consola (simulando estar instalado en nuestro Sistema Operativo) pues los métodos OnStart y OnStop son llamados explícitamente desde la consola. Pero, si el servicio es instalado (se crea el instalador al terminar el desarrollo) el servicio será ejecutado normalmente.

En la propiedades del proyecto debemos cambiar el Tipo de Salida a Aplicación de Consola:

Colocamos los puntos de interrupción correspondients y luego hacemos clic derecho sobre el proyecto. ¡A compilar!:

Vemos el Visor de Eventos y encontraremos nuestro mensaje (introducido en el método OnStart del servicio):

Nuestro servicio está depurando y mostrando la consola:
 

Conclusión

De esta manera logramos depurar nuestro servicio mediante una aplicación de consola que emula la instalación del servicio Windows. Nos evitamos los pasos extras expuestos en la documentación de MSDN.