Manera más fácil de depurar un servicio de Windows

¿Hay una manera más fácil de recorrer el código que iniciar el servicio a través del Administrador de control de servicios de Windows y luego adjuntar el depurador a la secuencia? Es algo engorroso y me pregunto si hay un enfoque más directo.

Si quiero depurar rápidamente el servicio, simplemente coloco un Debugger.Break() allí. Cuando se llegue a esa línea, me devolverá a VS. No olvides quitar esa línea cuando termines.

ACTUALIZACIÓN: como alternativa a #if DEBUG , también puede usar el atributo Conditional("DEBUG_SERVICE") .

 [Conditional("DEBUG_SERVICE")] private static void DebugMode() { Debugger.Break(); } 

En su OnStart , simplemente llame a este método:

 public override void OnStart() { DebugMode(); /* ... do the rest */ } 

Allí, el código solo se habilitará durante las comstackciones de depuración. Mientras lo hace, puede ser útil crear una Configuración de comstackción separada para la depuración del servicio.

También creo que tener una “versión” separada para la ejecución normal y como servicio es el camino a seguir, pero ¿realmente se requiere dedicar un cambio de línea de comando para ese fin?

¿No podrías hacer lo siguiente?

 public static int Main(string[] args) { if (!Environment.UserInteractive) { // Startup as service. } else { // Startup as application } } 

Eso tendría el “beneficio”, que puede simplemente iniciar su aplicación mediante doble clic (OK, si realmente lo necesita) y que puede presionar F5 en Visual Studio (sin la necesidad de modificar la configuración del proyecto para incluir esa /console Opción).

Técnicamente, Environment.UserInteractive comprueba si la bandera WSF_VISIBLE está configurada para la estación de ventana actual, pero ¿hay alguna otra razón por la que devuelva false , además de ser ejecutada como un servicio (no interactivo)?

Cuando configuré un nuevo proyecto de servicio hace unas semanas, encontré esta publicación. Si bien hay muchas sugerencias excelentes, todavía no encontré la solución que quería: la posibilidad de llamar a los métodos OnStart y OnStop de las clases de servicio sin ninguna modificación a las clases de servicio.

La solución que se me ocurrió utiliza el Environment.Interactive Interactivo el modo de ejecución de selección, como lo sugieren otras respuestas a esta publicación.

 static void Main() { ServiceBase[] servicesToRun; servicesToRun = new ServiceBase[] { new MyService() }; if (Environment.UserInteractive) { RunInteractive(servicesToRun); } else { ServiceBase.Run(servicesToRun); } } 

El asistente RunInteractive usa la reflexión para llamar a los métodos OnStart y OnStop protegidos:

 static void RunInteractive(ServiceBase[] servicesToRun) { Console.WriteLine("Services running in interactive mode."); Console.WriteLine(); MethodInfo onStartMethod = typeof(ServiceBase).GetMethod("OnStart", BindingFlags.Instance | BindingFlags.NonPublic); foreach (ServiceBase service in servicesToRun) { Console.Write("Starting {0}...", service.ServiceName); onStartMethod.Invoke(service, new object[] { new string[] { } }); Console.Write("Started"); } Console.WriteLine(); Console.WriteLine(); Console.WriteLine( "Press any key to stop the services and end the process..."); Console.ReadKey(); Console.WriteLine(); MethodInfo onStopMethod = typeof(ServiceBase).GetMethod("OnStop", BindingFlags.Instance | BindingFlags.NonPublic); foreach (ServiceBase service in servicesToRun) { Console.Write("Stopping {0}...", service.ServiceName); onStopMethod.Invoke(service, null); Console.WriteLine("Stopped"); } Console.WriteLine("All services stopped."); // Keep the console alive for a second to allow the user to see the message. Thread.Sleep(1000); } 

Este es todo el código requerido, pero también escribí tutorial con explicaciones.

Lo que suelo hacer es encapsular la lógica del servicio en una clase separada e iniciarlo desde una clase ‘corredor’. Esta clase de corredor puede ser el servicio real o simplemente una aplicación de consola. Entonces su solución tiene (al menos) 3 proyectos:

 /ConsoleRunner /.... /ServiceRunner /.... /ApplicationLogic /.... 

A veces es importante analizar lo que está sucediendo durante la puesta en marcha del servicio. Adjuntar al proceso no ayuda aquí, porque no es lo suficientemente rápido para adjuntar el depurador mientras el servicio se está iniciando.

La respuesta corta es: estoy usando las siguientes 4 líneas de código para hacer esto:

 #if DEBUG base.RequestAdditionalTime(600000); // 600*1000ms = 10 minutes timeout Debugger.Launch(); // launch and attach debugger #endif 

Estos se insertan en el método OnStart del servicio de la siguiente manera:

 protected override void OnStart(string[] args) { #if DEBUG base.RequestAdditionalTime(600000); // 10 minutes timeout for startup Debugger.Launch(); // launch and attach debugger #endif MyInitOnstart(); // my individual initialization code for the service // allow the base class to perform any work it needs to do base.OnStart(args); } 

Para aquellos que no lo han hecho antes, he incluido consejos detallados a continuación , porque pueden atascarse fácilmente. Los siguientes consejos hacen referencia a Windows 7×64 y Visual Studio 2010 Team Edition , pero también deberían ser válidos para otros entornos.


Importante: Despliegue el servicio en modo “manual” (utilizando la utilidad InstallUtil desde el símbolo del sistema de VS o ejecute un proyecto de instalador de servicio que haya preparado). Abra Visual Studio antes de iniciar el servicio y cargue la solución que contiene el código fuente del servicio: configure puntos de interrupción adicionales según lo requiera en Visual Studio, luego inicie el servicio a través del Panel de control del servicio.

Debido al código Debugger.Launch , esto provocará un diálogo “Se produjo una excepción no controlada de Microsoft .NET Framework en Servicename.exe “. a aparecer. Hacer clic Elevar Sí, depurar Servicename.exe como se muestra en la captura de pantalla:
FrameworkException

Después, especialmente en Windows 7 UAC, puede pedirte que ingreses credenciales de administrador. Introdúzcalos y proceda con :

UACPrompt

Después de eso, aparece la bien conocida ventana del depurador Just-In-Time de Visual Studio. Le pregunta si desea depurar usando el depurador eliminado. Antes de hacer clic en , seleccione que no desea abrir una nueva instancia (segunda opción): una nueva instancia no sería útil aquí, porque el código fuente no se mostraría. Entonces, en su lugar, selecciona la instancia de Visual Studio que abrió anteriormente: VSDebuggerPrompt

Después de hacer clic en , después de un tiempo Visual Studio mostrará la flecha amarilla en la línea donde está la instrucción Debugger.Launch y podrá depurar su código (método MyInitOnStart , que contiene su inicialización). VSDebuggerBreakpoint

Presionando F5 continúa la ejecución inmediatamente, hasta que se alcanza el próximo punto de interrupción que ha preparado.

Sugerencia: para mantener el servicio en ejecución, seleccione Depurar -> Desconectar todo . Esto le permite ejecutar un cliente que se comunica con el servicio después de que se inició correctamente y ha terminado de depurar el código de inicio. Si presiona Mayús + F5 (detener la depuración), esto terminará el servicio. En lugar de hacer esto, debe usar el Panel de control de servicio para detenerlo.

Tenga en cuenta que

  • Si crea un Release, entonces el código de depuración se elimina automáticamente y el servicio se ejecuta normalmente.

  • Estoy usando Debugger.Launch() , que inicia y adjunta un depurador . También probé Debugger.Break() , que no funcionó , porque todavía no hay un depurador conectado al inicio del servicio (lo que provoca el “Error 1067: el proceso finalizó inesperadamente” ).

  • RequestAdditionalTime establece un tiempo de espera más largo para el inicio del servicio (no está retrasando el código en sí, sino que continuará de inmediato con la instrucción Debugger.Launch ). De lo contrario, el tiempo de espera predeterminado para iniciar el servicio es demasiado corto y el inicio del servicio falla si no llama a base.Onstart(args) lo suficientemente rápido desde el depurador. Prácticamente, un tiempo de espera de 10 minutos evita que vea el mensaje ” el servicio no respondió …” inmediatamente después de que se inicia el depurador.

  • Una vez que se acostumbre, este método es muy fácil porque solo requiere que agregue 4 líneas a un código de servicio existente, lo que le permite obtener control y depuración rápidamente.

Este video de YouTube de Fabio Scopel explica cómo depurar un servicio de Windows bastante bien … el método real de hacerlo comienza a las 4:45 en el video …

Aquí está el código explicado en el video … en su archivo Program.cs, agregue las cosas para la sección Debug …

 namespace YourNamespace { static class Program { ///  /// The main entry point for the application. ///  static void Main() { #if DEBUG Service1 myService = new Service1(); myService.OnDebug(); System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite); #else ServiceBase[] ServicesToRun; ServicesToRun = new ServiceBase[] { new Service1() }; ServiceBase.Run(ServicesToRun); #endif } } } 

En su archivo Service1.cs, agregue el método OnDebug () …

  public Service1() { InitializeComponent(); } public void OnDebug() { OnStart(null); } protected override void OnStart(string[] args) { // your code to do something } protected override void OnStop() { } 

ACTUALIZAR

Este enfoque es, con mucho, el más fácil:

http://www.codeproject.com/KB/dotnet/DebugWinServices.aspx

Dejo mi respuesta original a continuación para la posteridad.


Mis servicios tienden a tener una clase que encapsula un temporizador ya que quiero que el servicio verifique a intervalos regulares si hay algún trabajo que hacer.

Actualizamos la clase y llamamos a StartEventLoop () durante el inicio del servicio. (Esta clase también podría usarse fácilmente desde una aplicación de consola).

El efecto secundario agradable de este diseño es que los argumentos con los que configura el temporizador pueden utilizarse para tener un retraso antes de que el servicio realmente empiece a funcionar, de modo que tenga tiempo para adjuntar un depurador manualmente.

ps ¿Cómo conectar el depurador manualmente a un proceso en ejecución …?

 using System; using System.Threading; using System.Configuration; public class ServiceEventHandler { Timer _timer; public ServiceEventHandler() { // get configuration etc. _timer = new Timer( new TimerCallback(EventTimerCallback) , null , Timeout.Infinite , Timeout.Infinite); } private void EventTimerCallback(object state) { // do something } public void StartEventLoop() { // wait a minute, then run every 30 minutes _timer.Change(TimeSpan.Parse("00:01:00"), TimeSpan.Parse("00:30:00"); } } 

También solía hacer lo siguiente (ya mencionado en respuestas anteriores pero con los indicadores del comstackdor condicional [#if] para ayudar a evitar que se active en una versión de Release).

Dejé de hacerlo de esta manera porque a veces nos olvidaríamos de comstackr en Release y de tener un depurador en una aplicación que se ejecuta en una demostración de cliente (¡embarazoso!).

 #if DEBUG if (!System.Diagnostics.Debugger.IsAttached) { System.Diagnostics.Debugger.Break(); } #endif 

 static void Main() { #if DEBUG // Run as interactive exe in debug mode to allow easy // debugging. var service = new MyService(); service.OnStart(null); // Sleep the main thread indefinitely while the service code // runs in .OnStart Thread.Sleep(Timeout.Infinite); #else // Run normally as service in release mode. ServiceBase[] ServicesToRun; ServicesToRun = new ServiceBase[]{ new MyService() }; ServiceBase.Run(ServicesToRun); #endif } 

También puede iniciar el servicio a través del símbolo del sistema (sc.exe).

Personalmente, ejecutaba el código como un progtwig independiente en la fase de depuración, y cuando la mayoría de los errores se resuelven, pasa a funcionar como servicio.

Lo que solía hacer era tener un interruptor de línea de comando que iniciara el progtwig como servicio o como una aplicación regular. Luego, en mi IDE, configuraría el interruptor para poder pasar por mi código.

Con algunos idiomas, puede detectar si se está ejecutando en un IDE y realizar este cambio automáticamente.

Qué idioma estás usando?

Creo que depende del sistema operativo que esté usando, Vista es mucho más difícil de adjuntar a los Servicios, debido a la separación entre sesiones.

Las dos opciones que he usado en el pasado son:

  • Use GFlags (en las herramientas de depuración para Windows) para configurar un depurador permanente para un proceso. Esto existe en la clave de registro “Opciones de ejecución de archivo de imagen” y es increíblemente útil. Creo que tendrá que modificar la configuración del Servicio para habilitar “Interactuar con el Escritorio”. Lo uso para todos los tipos de depuración, no solo servicios.
  • La otra opción es separar el código un poco, para que la parte de servicio se pueda intercambiar con un inicio de aplicación normal. De esta forma, puede usar un indicador de línea de comando simple e iniciar como un proceso (en lugar de un Servicio), lo que hace que sea mucho más fácil de depurar.

Espero que esto ayude.

Use la biblioteca TopShelf .

Crea una aplicación de consola y luego configura en tu Principal

 class Program { static void Main(string[] args) { HostFactory.Run(x => { // setup service start and stop. x.Service(s => { s.ConstructUsing(name => new Controller()); s.WhenStarted(controller => controller.Start()); s.WhenStopped(controller => controller.Stop()); }); // setup recovery here x.EnableServiceRecovery(rc => { rc.RestartService(delayInMinutes: 0); rc.SetResetPeriod(days: 0); }); x.RunAsLocalSystem(); }); } } public class Controller { public void Start() { } public void Stop() { } } 

Para depurar su servicio, solo presione F5 en Visual Studio.

Para instalar el servicio, escriba cmd “console.exe install”

Luego puede iniciar y detener el servicio en el administrador de servicios de Windows.

Cuando escribo un servicio pongo toda la lógica del servicio en un proyecto dll y creo dos “hosts” que llaman a este dll, uno es un servicio de Windows y el otro es una aplicación de línea de comandos.

Utilizo la aplicación de línea de comandos para la depuración y adjunto el depurador al servicio real solo para los errores que no puedo reproducir en la aplicación de línea de comandos.

Si utiliza este enfoque, recuerde que debe probar todo el código mientras se ejecuta en un servicio real, mientras que la herramienta de línea de comandos es una buena herramienta de depuración, es un entorno diferente y no se comporta exactamente como un servicio real.

Me gusta poder depurar todos los aspectos de mi servicio, incluida cualquier inicialización en OnStart (), mientras sigo ejecutándola con un comportamiento de servicio completo en el marco del SCM … sin modo de “consola” o “aplicación”.

Lo hago creando un segundo servicio, en el mismo proyecto, para usar en la depuración. El servicio de depuración, cuando se inicia de la forma habitual (es decir, en el complemento MMC de servicios), crea el proceso de host de servicio. Esto le proporciona un proceso para adjuntar el depurador aunque aún no haya comenzado su servicio real. Después de conectar el depurador al proceso, inicie su servicio real y puede acceder a él en cualquier parte del ciclo de vida del servicio, incluido OnStart ().

Debido a que requiere una intrusión de código muy mínima, el servicio de depuración se puede incluir fácilmente en su proyecto de configuración de servicio, y se elimina fácilmente de su versión de producción comentando una sola línea de código y eliminando un único instalador de proyecto.

Detalles:

1) Suponiendo que está implementando MyService , también cree MyServiceDebug . Agregue ambos a la matriz ServiceBase en Program.cs forma:

  ///  /// The main entry point for the application. ///  static void Main() { ServiceBase[] ServicesToRun; ServicesToRun = new ServiceBase[] { new MyService(), new MyServiceDebug() }; ServiceBase.Run(ServicesToRun); } 

2) Agregue el servicio real Y el servicio de depuración al instalador del proyecto para el proyecto de servicio:

enter image description here

Ambos servicios (real y depuración) se incluyen cuando agrega la salida del proyecto de servicio al proyecto de instalación para el servicio. Después de la instalación, ambos servicios aparecerán en el complemento service.msc MMC.

3) Inicie el servicio de depuración en MMC.

4) En Visual Studio, conecte el depurador al proceso iniciado por el servicio de depuración.

5) Comience el servicio real y disfrute de la depuración.

Al desarrollar y depurar un servicio de Windows, generalmente lo ejecuto como una aplicación de consola agregando un parámetro de inicio / console y comprobando esto. Hace la vida mucho más fácil.

 static void Main(string[] args) { if (Console.In != StreamReader.Null) { if (args.Length > 0 && args[0] == "/console") { // Start your service work. } } } 

¿Qué hay de Debugger.Break () en la primera línea?

Para depurar los servicios de Windows, combino GFlags y un archivo .reg creado por regedit.

  1. Ejecute GFlags, especificando el nombre-exe y vsjitdebugger
  2. Ejecute regedit e ir a la ubicación donde GFlags establece sus opciones
  3. Elija “Exportar clave” en el menú de archivo
  4. Guarde ese archivo en algún lugar con la extensión .reg
  5. Cada vez que desee depurar el servicio: haga doble clic en el archivo .reg
  6. Si desea detener la depuración, haga doble clic en el segundo archivo .reg

O guarde los siguientes fragmentos y reemplace servicename.exe con el nombre del archivo ejecutable deseado.


debugon.reg:

  Editor de registro de Windows versión 5.00

 [HKEY_LOCAL_MACHINE \ SOFTWARE \ Microsoft \ Windows NT \ CurrentVersion \ Image File Execution Options \ servicename.exe]
 "GlobalFlag" = "0x00000000"
 "Depurador" = "vsjitdebugger.exe" 

debugoff.reg:

  Editor de registro de Windows versión 5.00

 [HKEY_LOCAL_MACHINE \ SOFTWARE \ Microsoft \ Windows NT \ CurrentVersion \ Image File Execution Options \ servicename.exe]
 "GlobalFlag" = "0x00000000" 

Para la rutina de progtwigción de cosas pequeñas he hecho un truco muy simple para depurar fácilmente mi servicio:

Al inicio del servicio, busco un parámetro de línea de comando “/ depurar”. Si se llama al servicio con este parámetro, no realizo el inicio habitual del servicio, sino que comienzo todos los oyentes y solo muestro un mensaje “Debug in progress, presione ok to end”.

Entonces, si mi servicio se inicia de la manera habitual, comenzará como servicio, si se inicia con el parámetro / depuración de la línea de comando funcionará como un progtwig normal.

En VS voy a agregar / depurar como parámetro de depuración e iniciar el progtwig de servicio directamente.

De esta forma puedo depurar fácilmente la mayoría de los problemas de tipo pequeño. Por supuesto, algunas cosas aún deberán ser depuradas como servicio, pero para el 99% esto es suficiente.

 #if DEBUG System.Diagnostics.Debugger.Break(); #endif 

Utilizo una variación en la respuesta de JOP. Con los parámetros de la línea de comandos, puede establecer el modo de depuración en el IDE con las propiedades del proyecto o a través del administrador de servicios de Windows.

 protected override void OnStart(string[] args) { if (args.Contains("DEBUG_SERVICE")) { Debugger.Break(); } ... } 

Esto es bastante posterior a la pregunta, pero podría ser útil para futuras referencias.

Si puedes, te sugiero usar el excelente proyecto TopShelf . Hace que el desarrollo y la depuración de un servicio de Windows sean mucho más fáciles y agrega que la implementación también es más fácil.

Échale un vistazo: http://topshelf-project.com/

Para solucionar problemas en el progtwig de servicio de Windows existente, use ‘Debugger.Break ()’ como sugirieron otros tipos.

Para el nuevo progtwig de servicio de Windows, sugeriría usar el método de James Michael Hare http://geekswithblogs.net/BlackRabbitCoder/archive/2011/03/01/c-toolbox-debug-able-self-installable-windows-service-template- redux.aspx

Simplemente ponga su almuerzo de depuración en cualquier lugar y adjunte Visualstudio al inicio

 #if DEBUG Debugger.Launch(); #endif 

También debe iniciar VS como Administatrator y debe permitir que un proceso pueda ser depurado automáticamente por un usuario diferente (como se explica aquí ):

 reg add "HKCR\AppID{E62A7A31-6025-408E-87F6-81AEB0DC9347}" /v AppIDFlags /t REG_DWORD /d 8 /f 

Utilice el proyecto de Windows Service Template C # para crear una nueva aplicación de servicio https://github.com/HarpyWar/windows-service-template

Hay un modo de consola / servicio detectado automáticamente, un instalador / desinstalador automático de su servicio y varias características más utilizadas.

Este es el método simple que usé para probar el servicio, sin ningún método adicional de “depuración” y con pruebas integradas de unidad VS.

 [TestMethod] public void TestMyService() { MyService fs = new MyService(); var OnStart = fs.GetType().BaseType.GetMethod("OnStart", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); OnStart.Invoke(fs, new object[] { null }); } // As an extension method public static void Start(this ServiceBase service, List parameters) { string[] par = parameters == null ? null : parameters.ToArray(); var OnStart = service.GetType().GetMethod("OnStart", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); OnStart.Invoke(service, new object[] { par }); } 
 static class Program { static void Main() { #if DEBUG // TODO: Add code to start application here // //If the mode is in debugging // //create a new service instance Service1 myService = new Service1(); // //call the start method - this will start the Timer. myService.Start(); // //Set the Thread to sleep Thread.Sleep(300000); // //Call the Stop method-this will stop the Timer. myService.Stop(); #else ServiceBase[] ServicesToRun; ServicesToRun = new ServiceBase[] { new Service1() }; ServiceBase.Run(ServicesToRun); #endif } } 

Tienes dos opciones para hacer la depuración.

  1. crear un archivo de registro: Personalmente prefiero un archivo de registro separado como archivo de texto en lugar de utilizar el registro de la aplicación o el registro de eventos. Pero esto le costará mucho en nombre del tiempo, porque sigue siendo difícil encontrar dónde está la ubicación exacta del error
  2. Convierta la aplicación a la aplicación de consola: esto le permitirá a usted, todas las herramientas de depuración que podemos usar en VS.

Consulte esta publicación de blog que creé para el tema.

Solo pega

 Debugger.Break(); 

cualquier parte en tu código.

Por ejemplo ,

 internal static class Program { ///  /// The main entry point for the application. ///  private static void Main() { Debugger.Break(); ServiceBase[] ServicesToRun; ServicesToRun = new ServiceBase[] { new Service1() }; ServiceBase.Run(ServicesToRun); } } 

Debugger.Break(); a Debugger.Break(); cuando ejecutas tu progtwig

La mejor opción es usar el espacio de nombres ‘ System.Diagnostics ‘.

Incluya su código en el bloque if else para el modo de depuración y el modo de lanzamiento como se muestra a continuación para alternar entre el modo de depuración y el de liberación en Visual Studio,

 #if DEBUG // for debug mode **Debugger.Launch();** //debugger will hit here foreach (var job in JobFactory.GetJobs()) { //do something } #else // for release mode **Debugger.Launch();** //debugger will hit here // write code here to do something in Release mode. #endif