Administrando servicios de Windows

Recientemente tuve que programar una pequeña utilidad para administrar un servicio de Windows, así que pensé recoger aquí algunas informaciones que se encuentran dispersas en varias fuentes para facilitar la tarea.

La manera más fácil de administrar un servicio en .NET es mediante la clase ServiceController del assembly System.ServiceProcess.dll. El siguiente ejemplo muestra cómo reiniciar el servicio de impresión (Spooler):

using System;
using System.ServiceProcess;

namespace ServiceControllerExample
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                Console.WriteLine("Reiniciando servicio Spooler");

                ServiceController serviceController = new ServiceController("Spooler");

                serviceController.Stop();
                serviceController.WaitForStatus(ServiceControllerStatus.Stopped);

                serviceController.Start();
                serviceController.WaitForStatus(ServiceControllerStatus.Running);

                Console.WriteLine("Servicio reiniciado, presione Enter para finalizar.");
                Console.ReadLine();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

            Console.ReadLine();
        }
    }
}

Para instanciar ServiceController simplemente debemos usar el nombre del servicio que lo podemos obtener de la consola de administración (services.msc). Si nos equivocamos con el nombre el objeto será creado igual, y los métodos para cambiar el estado pueden fallar por varias razones, así que es una buena idea poner un try…catch alrededor de todo.

Para reiniciar hay que llamar a Stop() y luego a Start(). El servicio debe estar en un estado previo que tenga sentido para la operación. Por ejemplo si llamamos a Start() en un servicio que ya está iniciado se producirá una excepción.

Otra cosa que hay que notar son las llamadas a WaitForStatus(). Los métodos Start() y Stop() son no bloqueantes, así que debemos hacer esto para sincronizar nuestro código.

Obteniendo el directorio de un servicio

Si desde tu servicio accedes a archivos, la primera vez puede llegarte a sorprender que todas las rutas relativas apuntan a c:\windows\system32. Así que para obtener el working directory del servicio hay que usar AppDomain.CurrentDomain.BaseDirectory.

Para obtener el working directory desde otro proceso hay un par de maneras. Mediante Windows Management Instrumentation (WMI), o directamente desde la clave HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\NombreDelServicio del registro.

RegistryKey key = Registry.LocalMachine.OpenSubKey(@"System\CurrentControlSet\Services\Spooler");
string servicePath = (string)key.GetValue("ImagePath");