c# - llamar - instalar servicios de windows
Manera más fácil de depurar un servicio de Windows (29)
¿Existe una forma más sencilla de pasar por el código que iniciar el servicio a través del Administrador de control de servicios de Windows y luego adjuntar el depurador al hilo? Es un poco engorroso y me pregunto si hay un enfoque más directo.
¿Qué tal Debugger.Break () en la primera línea?
A veces es importante analizar lo que sucede 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 se inicia el servicio.
La respuesta corta es que 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 puedes atascarte fácilmente. Las siguientes sugerencias se refieren a Windows 7x64 y Visual Studio 2010 Team Edition , pero también deben ser válidas para otros entornos.
Importante: implemente el servicio en modo "manual" (use la utilidad InstallUtil
desde el indicador de comandos 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) y luego inicie el servicio a través del Panel de control de servicios.
Debido al código Debugger.Launch
, esto generará un cuadro de diálogo "Se produjo una excepción de Microsoft .NET Framework no controlada en Servicename.exe ". a aparecer. Hacer clic Sí, depure Servicename.exe como se muestra en la captura de pantalla:
Después, especialmente en Windows 7 UAC puede pedirle que ingrese las credenciales de administrador. Ingrese ellos y proceda con Sí :
Después de eso, aparece la conocida ventana de depurador de Just Studio In Visual Time. Le pregunta si desea depurar utilizando el depurador eliminado. Antes de hacer clic en Sí , seleccione que no desea abrir una nueva instancia (segunda opción); una nueva instancia no sería útil aquí porque no se mostraría el código fuente. Así que seleccionas la instancia de Visual Studio que has abierto anteriormente en su lugar:
Después de hacer clic en Sí , después de un tiempo, Visual Studio mostrará la flecha amarilla en la línea donde está la declaración Debugger.Launch
y podrá depurar su código (método MyInitOnStart
, que contiene su inicialización).
Presionando F5 continúa la ejecución inmediatamente, hasta que se alcanza el siguiente punto de interrupción que ha preparado.
Sugerencia: para mantener el servicio en ejecución, seleccione Depurar -> Separar todo . Esto le permite ejecutar un cliente que se comunica con el servicio después de que se haya iniciado correctamente y haya terminado de depurar el código de inicio. Si presiona Shift + F5 (detener la depuración), esto terminará el servicio. En lugar de hacer esto, debe usar el Panel de control de servicios para detenerlo.
Nota que
Si crea una versión, 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 aún 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í, pero continuará de inmediato con la declaraciónDebugger.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 abase.Onstart(args)
lo suficientemente rápido desde el depurador. En la práctica, un tiempo de espera de 10 minutos evita que vea el mensaje " el servicio no respondió ..." inmediatamente después de iniciar el depurador.Una vez que te acostumbras a él, este método es muy fácil porque solo requiere que agregues 4 líneas a un código de servicio existente, lo que te permite ganar rápidamente el control y la depuración.
Al desarrollar y depurar un servicio de Windows, normalmente lo ejecuto como una aplicación de consola agregando un parámetro de inicio / consola 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.
}
}
}
Creo que depende de qué sistema operativo esté utilizando, 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 necesitarás modificar la configuración del servicio para habilitar "Interactuar con el escritorio". Utilizo esto para todo tipo de depuración, no solo servicios.
- La otra opción, es separar el código un poco, de modo que la parte de servicio sea intercambiable con un inicio de aplicación normal. De esa manera, puede usar un indicador de línea de comando simple, y ejecutarlo como un proceso (en lugar de un Servicio), lo que facilita mucho la depuración.
Espero que esto ayude.
Cuando configuré un nuevo proyecto de servicio hace unas semanas, encontré esta publicación. Si bien hay muchas sugerencias excelentes, aún no encontré la solución que buscaba: 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
Inactiva el modo de ejecución seleccionado, 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 ayudante RunInteractive
utiliza 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í walkthrough con explicaciones.
Cuando escribo un servicio, pongo toda la lógica de 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 depurar y adjunto el depurador al servicio real solo para 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.
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 de depuración ...
namespace YourNamespace
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
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()
{
}
Cómo funciona
Básicamente, tiene que crear un public void OnDebug()
que llama a OnStart(string[] args)
ya que está protegido y no es accesible desde el exterior. El programa void Main()
se agrega con el preprocesador #DEBUG
con #DEBUG
.
Visual Studio define DEBUG
si el proyecto se compila en modo de depuración. Esto permitirá que la sección de depuración (a continuación) se ejecute cuando la condición sea verdadera
Service1 myService = new Service1();
myService.OnDebug();
System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);
Y se ejecutará como una aplicación de consola, una vez que todo salga bien, puede cambiar el modo Release
y la else
sección normal activará la lógica.
Este es el método simple que utilicé para probar el servicio, sin ningún método adicional de "Depuración" y con las Pruebas unitarias VS integradas.
[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<string> 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 });
}
Esto es bastante después de la pregunta, pero podría ser útil para futuras referencias.
Si puedes, te sugiero usar el excelente proyecto TopShelf . Facilita mucho más el desarrollo y la depuración de un servicio de Windows y añade que la implementación también es algo más fácil.
Échale un vistazo: TopShelf
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 liberación, como se muestra a continuación, para cambiar 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
Lo que normalmente hago es encapsular la lógica del servicio en una clase separada y comenzar 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
/....
Lo que solía hacer era tener un interruptor de línea de comandos que iniciara el programa como un servicio o como una aplicación regular. Luego, en mi IDE, configuraría el interruptor para que pudiera revisar 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?
Me gusta poder depurar todos los aspectos de mi servicio, incluida cualquier inicialización en OnStart (), mientras se ejecuta con un comportamiento de servicio completo en el marco del SCM ... no hay modo de "consola" o "aplicación".
Lo hago creando un segundo servicio, en el mismo proyecto, para usar para la depuración. El servicio de depuración, cuando se inicia como es habitual (es decir, en los servicios MMC plugin), crea el proceso de host de servicio. Esto le brinda un proceso para adjuntar el depurador a pesar de que aún no haya iniciado su servicio real. Después de adjuntar el depurador al proceso, inicie su servicio real y podrá acceder a él en cualquier parte del ciclo de vida del servicio, incluido OnStart ().
Debido a que requiere una intrusión de código mínima, el servicio de depuración se puede incluir fácilmente en su proyecto de configuración del servicio y se elimina fácilmente de su lanzamiento de producción al comentar una sola línea de código y eliminar un solo instalador de proyectos.
Detalles:
1) Suponiendo que está implementando MyService
, también cree MyServiceDebug
. Agregue ambos a la matriz ServiceBase
en Program.cs
así:
/// <summary>
/// The main entry point for the application.
/// </summary>
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:
Ambos servicios (real y depuración) se incluyen cuando agrega la salida del proyecto de servicio al proyecto de configuración para el servicio. Después de la instalación, ambos servicios aparecerán en el complemento MMC service.msc.
3) Iniciar el servicio de depuración en MMC.
4) En Visual Studio, adjunte el depurador al proceso iniciado por el servicio de depuración.
5) Iniciar el servicio real y disfrutar de la depuración.
Para depurar los servicios de Windows, combino GFlags y un archivo .reg creado por regedit.
- Ejecute GFlags, especificando el nombre exe y vsjitdebugger
- Ejecute regedit y vaya a la ubicación donde GFlags establece sus opciones
- Elija "Exportar clave" en el menú de archivo
- Guarda ese archivo en algún lugar con la extensión .reg
- Cada vez que quiera depurar el servicio: haga doble clic en el archivo .reg
- Si desea detener la depuración, haga doble clic en el segundo archivo .reg
O guarde los siguientes fragmentos de código y reemplace servicename.exe con el nombre del ejecutable deseado.
debugon.reg:
Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows NT/CurrentVersion/Image File Execution Options/servicename.exe] "GlobalFlag"="0x00000000" "Debugger"="vsjitdebugger.exe"
debugoff.reg:
Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows NT/CurrentVersion/Image File Execution Options/servicename.exe] "GlobalFlag"="0x00000000"
Para la programación rutinaria de cosas pequeñas, he hecho un truco muy simple para depurar fácilmente mi servicio:
Al inicio del servicio, compruebo un parámetro de línea de comando "/ debug". Si se llama al servicio con este parámetro, no hago el inicio del servicio habitual, sino que inicio todos los escuchas y solo se muestra el mensaje "Depuración en curso, presione Aceptar para finalizar".
Por lo tanto, si mi servicio se inicia de la forma habitual, se iniciará como servicio, si se inicia con el parámetro / depuración de la línea de comandos, actuará como un programa normal.
En VS solo agregaré / depuraré como parámetro de depuración e iniciaré el programa de servicio directamente.
De esta manera puedo depurar fácilmente para la mayoría de los pequeños problemas de tipo. Por supuesto, algunas cosas todavía tendrán que ser depuradas como servicio, pero para el 99% esto es suficiente.
Para solucionar problemas en el programa de servicio de Windows existente, use ''Debugger.Break ()'' como sugirieron otros tipos.
Para el nuevo programa 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 del depurador 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 usuario distinto pueda depurar un proceso (como se explica here ):
reg add "HKCR/AppID{E62A7A31-6025-408E-87F6-81AEB0DC9347}" /v AppIDFlags /t REG_DWORD /d 8 /f
Solo pegar
Debugger.Break();
En cualquier lugar de tu código.
Por ejemplo ,
internal static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
private static void Main()
{
Debugger.Break();
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new Service1()
};
ServiceBase.Run(ServicesToRun);
}
}
Debugger.Break();
cuando ejecutas tu programa
También creo que tener una "versión" separada para la ejecución normal y como servicio es el camino a seguir, pero ¿es realmente necesario dedicar un interruptor de línea de comando separado para ese propósito?
No podrías simplemente hacer:
public static int Main(string[] args)
{
if (!Environment.UserInteractive)
{
// Startup as service.
}
else
{
// Startup as application
}
}
Eso tendría el "beneficio", de que simplemente puede iniciar su aplicación a través de un doble clic (OK, si realmente lo necesita) y que solo puede presionar F5 en Visual Studio (sin la necesidad de modificar la configuración del proyecto para incluir esa /console
Opción).
Técnicamente, el Environment.UserInteractive
comprueba si el indicador WSF_VISIBLE
está establecido para la estación de ventana actual, pero ¿hay alguna otra razón por la que devolvería el valor false
, además de ejecutarse como un servicio (no interactivo)?
También puede iniciar el servicio a través del símbolo del sistema (sc.exe).
Personalmente, ejecuté el código como un programa independiente en la fase de depuración, y cuando se solucionan la mayoría de los errores, cambiar a ejecución como servicio.
Tienes dos opciones para hacer la depuración.
- cree un archivo de registro: Personalmente prefiero un archivo de registro separado como el archivo de texto en lugar de usar el registro de la aplicación o el registro de eventos.
- Convierta la aplicación en una aplicación de consola: esto le permitirá a usted, todas las herramientas de depuración que podemos usar en VS.
Por favor, consulte THIS entrada de blog que he creado para el tema.
Use el proyecto C # de la plantilla de servicio de Windows para crear una nueva aplicación de servicio https://github.com/HarpyWar/windows-service-template
Se detectan automáticamente los modos de consola / servicio, instalador / desinstalador automático de su servicio y se incluyen varias de las funciones más utilizadas.
Uso una variación en la respuesta de JOP. Al usar 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 mediante el administrador de servicios de Windows.
protected override void OnStart(string[] args)
{
if (args.Contains<string>("DEBUG_SERVICE"))
{
Debugger.Break();
}
...
}
Utilice la biblioteca TopShelf .
Cree una aplicación de consola y luego configure la configuración en su Principal
class Program
{
static void Main(string[] args)
{
HostFactory.Run(x =>
{
// setup service start and stop.
x.Service<Controller>(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, simplemente presione F5 en Visual Studio.
Para instalar el servicio, escriba cmd "console.exe install"
A continuación, puede iniciar y detener el servicio en el administrador de servicios de Windows.
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.
Creamos una nueva 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 colateral agradable de este diseño es que los argumentos con los que configuró el temporizador se pueden usar para tener un retraso antes de que el servicio realmente comience a funcionar, de modo que tenga tiempo de adjuntar un depurador manualmente.
ps ¿Cómo adjuntar 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 las respuestas anteriores, pero con los indicadores del compilador condicional [#if] para ayudar a evitar que se active en una versión de lanzamiento).
Dejé de hacerlo de esta manera porque a veces nos olvidábamos de compilar Release y tener una interrupción del depurador en una aplicación que se ejecutaba en una demostración de un cliente (¡embarazoso!).
#if DEBUG
if (!System.Diagnostics.Debugger.IsAttached)
{
System.Diagnostics.Debugger.Break();
}
#endif
Si quiero depurar rápidamente el servicio, simplemente coloco un Debugger.Break()
allí. Cuando se alcanza esa línea, me devolverá a VS. No te olvides de eliminar esa línea cuando hayas terminado.
ACTUALIZACIÓN: Como alternativa a los #if DEBUG
#if, 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 versiones de depuración. Mientras esté en ello, puede ser útil crear una configuración de compilación separada para la depuración del servicio.
#if DEBUG
System.Diagnostics.Debugger.Break();
#endif
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
}
}
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
}