Un moyen plus facile de déboguer un service Windows

Existe-t-il un moyen plus simple de parcourir le code que de démarrer le service via le Gestionnaire de contrôle des services Windows, puis de connecter le débogueur au thread? C’est un peu lourd et je me demande s’il existe une approche plus simple.

Si je veux rapidement déboguer le service, je viens de déposer un Debugger.Break() dans celui-ci. Lorsque cette ligne est atteinte, elle me ramène à VS. N’oubliez pas de supprimer cette ligne lorsque vous avez terminé.

UPDATE: Au lieu de #if DEBUG , vous pouvez également utiliser l’atsortingbut Conditional("DEBUG_SERVICE") .

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

Sur votre OnStart , appelez simplement cette méthode:

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

Là, le code ne sera activé que lors des versions Debug. Pendant que vous y êtes, il peut être utile de créer une configuration de construction distincte pour le débogage du service.

Je pense également qu’il est préférable d’avoir une «version» séparée pour l’exécution normale et en tant que service, mais est-ce vraiment nécessaire de dédier un commutateur de ligne de commande distinct à cette fin?

Tu ne peux pas juste faire:

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

Cela aurait “l’avantage”, que vous pouvez simplement démarrer votre application via doubleclick (OK, si vous en avez vraiment besoin) et que vous pouvez simplement appuyer sur F5 dans Visual Studio (sans avoir à modifier les parameters du projet pour inclure cette /console Option).

Techniquement, Environment.UserInteractive vérifie si l’ WSF_VISIBLE est défini pour la station de fenêtre en cours, mais existe-t-il une autre raison pour laquelle il renvoie false , en plus d’être exécuté en tant que service (non interactif)?

Lorsque j’ai mis en place un nouveau projet de service il y a quelques semaines, j’ai trouvé cet article. Bien qu’il y ait beaucoup de bonnes suggestions, je n’ai toujours pas trouvé la solution que je voulais: la possibilité d’appeler les méthodes OnStart et OnStop des classes de service sans aucune modification des classes de service.

La solution que j’ai proposée utilise l’ Environment.Interactive le mode en cours d’exécution, comme suggéré par d’autres réponses à cet article.

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

L’assistant RunInteractive utilise la reflection pour appeler les méthodes OnStart et OnStop protégées:

 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 ssortingng[] { } }); 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); } 

C’est tout le code requirejs, mais j’ai aussi écrit un guide avec des explications.

Ce que je fais habituellement, c’est encapsuler la logique du service dans une classe distincte et commencer à partir d’une classe “runner”. Cette classe de coureur peut être le service réel ou simplement une application console. Donc, votre solution a au moins 3 projets:

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

Parfois, il est important d’parsingr ce qui se passe pendant le démarrage du service. La connexion au processus n’aide pas ici, car vous n’êtes pas assez rapide pour attacher le débogueur pendant le démarrage du service.

La réponse courte est que j’utilise les 4 lignes de code suivantes pour faire ceci:

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

Celles-ci sont insérées dans la méthode OnStart du service comme suit:

 protected override void OnStart(ssortingng[] 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); } 

Pour ceux qui ne l’ont pas encore fait, j’ai inclus des conseils détaillés cidessous , car vous pouvez facilement restr coincé. Les conseils suivants concernent Windows 7×64 et Visual Studio 2010 Team Edition , mais doivent également être valides pour d’autres environnements.


Important: déployez le service en mode “manuel” (en utilisant soit l’utilitaire InstallUtil partir de l’invite de commande VS, soit un projet d’installation de service que vous avez préparé). Ouvrez Visual Studio avant de démarrer le service et chargez la solution contenant le code source du service – configurez des points d’arrêt supplémentaires selon vos besoins dans Visual Studio – puis démarrez le service via le Panneau de configuration du service.

En raison du code Debugger.Launch , cela provoquera une boîte de dialog “Une exception Microsoft .NET Framework non gérée s’est produite dans Servicename.exe .” apparaître. Cliquez sur Élever Oui, déboguez Servicename.exe comme indiqué dans la capture d’écran:
FrameworkException

Par la suite, sous Windows 7 UAC, vous pourriez être invité à saisir des informations d’identification d’administrateur. Entrez-les et continuez avec Oui :

L'UACPrompt

Après cela, la fenêtre bien connue du débogueur Just-In-Time de Visual Studio apparaît. Il vous demande si vous souhaitez déboguer à l’aide du débogueur supprimé. Avant de cliquer sur Oui , sélectionnez que vous ne voulez pas ouvrir une nouvelle instance (2ème option) – une nouvelle instance ne serait pas utile ici, car le code source ne serait pas affiché. Donc, vous sélectionnez l’instance Visual Studio que vous avez ouverte plus tôt à la place: VSDebuggerPrompt

Après avoir cliqué sur Oui , Visual Studio affichera la flèche jaune juste après la ligne où se trouve l’instruction Debugger.Launch et vous pourrez déboguer votre code (méthode MyInitOnStart , qui contient votre initialisation). VSDebuggerBreakpoint

En appuyant sur F5, l' exécution continue immédiatement jusqu’à ce que le prochain point d’arrêt que vous avez préparé soit atteint.

Conseil: Pour que le service continue à fonctionner, sélectionnez Déboguer -> Détacher tout . Cela vous permet d’exécuter un client communiquant avec le service après son démarrage correct et vous avez fini de déboguer le code de démarrage. Si vous appuyez sur Maj + F5 (arrêtez le débogage), cela mettra fin au service. Au lieu de cela, vous devez utiliser le panneau de commande du service pour l’arrêter.

Notez que

  • Si vous générez une version, le code de débogage est automatiquement supprimé et le service s’exécute normalement.

  • J’utilise Debugger.Launch() , qui démarre et attache un débogueur . J’ai également testé Debugger.Break() , qui ne fonctionnait pas , car aucun débogueur n’est encore connecté au démarrage du service (à l’origine de “l’erreur 1067: le processus s’est terminé de manière inattendue” ).

  • RequestAdditionalTime définit un délai plus long pour le démarrage du service (il ne retarde pas le code lui-même, mais continue immédiatement avec l’instruction Debugger.Launch ). Sinon, le délai d’expiration par défaut pour le démarrage du service est trop court et le démarrage du service échoue si vous n’appelez pas assez rapidement base.Onstart(args) partir du débogueur. Pratiquement, un délai de 10 minutes évite que le message ” le service n’a pas répondu …” s’affiche immédiatement après le démarrage du débogueur.

  • Une fois que vous vous y êtes habitué, cette méthode est très simple car il vous suffit d’ append 4 lignes à un code de service existant, ce qui vous permet de prendre rapidement le contrôle et de déboguer.

Cette vidéo YouTube de Fabio Scopel explique comment déboguer un service Windows plutôt bien … la méthode proprement dite commence à 4:45 dans la vidéo …

Voici le code expliqué dans la vidéo … dans votre fichier Program.cs, ajoutez le contenu de la section 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 } } } 

Dans votre fichier Service1.cs, ajoutez la méthode OnDebug () …

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

METTRE À JOUR

Cette approche est de loin la plus facile:

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

Je laisse ma réponse originale ci-dessous pour la postérité.


Mes services ont tendance à avoir une classe qui encapsule un minuteur, car je veux que le service vérifie régulièrement s’il y a du travail à faire.

Nous relevons la classe et appelons StartEventLoop () lors du démarrage du service. (Cette classe pourrait facilement être utilisée depuis une application de console.)

Le bon effet secondaire de cette conception est que les arguments avec lesquels vous configurez le temporisateur peuvent être utilisés pour avoir un délai avant que le service ne commence réellement à fonctionner, de sorte que vous ayez le temps de joindre un débogueur manuellement.

ps Comment attacher le débogueur manuellement à un processus en cours d’exécution?

 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"); } } 

J’avais l’habitude de faire ce qui suit (déjà mentionné dans les réponses précédentes, mais avec les drapeaux conditionnels du compilateur [#if] pour éviter de le déclencher dans une version Release).

J’ai arrêté de le faire de cette façon car parfois nous oublions de construire dans Release et de faire une pause de débogueur dans une application s’exécutant sur une démo client (embarrassante!).

 #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 } 

Vous pouvez également démarrer le service via l’invite de commande (sc.exe).

Personnellement, j’exécutais le code en tant que programme autonome dans la phase de débogage et, lorsque la plupart des bogues sont corrigés, optez pour l’exécution en tant que service.

Ce que j’avais l’habitude de faire était d’avoir un commutateur de ligne de commande qui lancerait le programme en tant que service ou en tant qu’application normale. Ensuite, dans mon IDE, je définirais le commutateur pour que je puisse parcourir mon code.

Avec certaines langues, vous pouvez réellement détecter si elle s’exécute dans un IDE et exécuter ce commutateur automatiquement.

Quelle langue utilisez-vous?

Je pense que cela dépend du système d’exploitation que vous utilisez, Vista est beaucoup plus difficile à joindre aux services, en raison de la séparation entre les sessions.

Les deux options que j’ai utilisées par le passé sont les suivantes:

  • Utilisez GFlags (dans les outils de débogage pour Windows) pour configurer un débogueur permanent pour un processus. Cela existe dans la clé de Registre “Image File Execution Options” et est extrêmement utile. Je pense que vous devrez modifier les parameters du service pour activer “Interact with Desktop”. Je l’utilise pour tous les types de débogage, pas seulement pour les services.
  • L’autre option consiste à séparer un peu le code afin que la partie service soit interchangeable avec un démarrage d’application normal. De cette façon, vous pouvez utiliser un simple indicateur de ligne de commande et le lancer en tant que processus (plutôt que service), ce qui facilite grandement le débogage.

J’espère que cela t’aides.

Utilisez la bibliothèque TopShelf .

Créez une application console, puis configurez la configuration dans votre main

 class Program { static void Main(ssortingng[] 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() { } } 

Pour déboguer votre service, appuyez simplement sur F5 dans Visual Studio.

Pour installer le service, tapez cmd “console.exe install”

Vous pouvez ensuite démarrer et arrêter le service dans le gestionnaire de services Windows.

Lorsque j’écris un service, je mets toute la logique de service dans un projet dll et crée deux “hôtes” qui appellent cette DLL, l’un étant un service Windows et l’autre une application en ligne de commande.

J’utilise l’application de ligne de commande pour le débogage et attache le débogueur au service réel uniquement pour les bogues que je ne peux pas reproduire dans l’application de ligne de commande.

Si vous utilisez cette approche, rappelez-vous simplement que vous devez tester tout le code pendant que vous exécutez un service réel, alors que l’outil de ligne de commande est un bon outil de débogage. Il s’agit d’un environnement différent.

J’aime pouvoir déboguer tous les aspects de mon service, y compris toute initialisation dans OnStart (), tout en l’exécutant avec un comportement de service complet dans le cadre du SCM … pas de mode “console” ou “app”.

Je le fais en créant un deuxième service, dans le même projet, à utiliser pour le débogage. Le service de débogage, lorsqu’il est démarré normalement (c’est-à-dire dans le plug-in MMC des services), crée le processus hôte du service. Cela vous donne un processus pour attacher le débogueur même si vous n’avez pas encore démarré votre service réel. Après avoir connecté le débogueur au processus, démarrez votre service réel et vous pouvez y accéder n’importe où dans le cycle de vie du service, y compris OnStart ().

Comme il nécessite très peu d’intrusion de code, le service de débogage peut facilement être inclus dans votre projet d’installation de service et peut être facilement retiré de votre version de production en commentant une seule ligne de code et en supprimant un seul installateur de projet.

Détails:

1) En supposant que vous implémentez MyService , créez également MyServiceDebug . Ajoutez les deux au tableau ServiceBase dans Program.cs comme ceci:

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

2) Ajoutez le service réel ET le service de débogage à l’installateur de projet pour le projet de service:

entrer la description de l'image ici

Les deux services (réel et de débogage) sont inclus lorsque vous ajoutez la sortie du projet de service au projet d’installation du service. Après l’installation, les deux services apparaîtront dans le plug-in service.msc MMC.

3) Démarrez le service de débogage dans MMC.

4) Dans Visual Studio, attachez le débogueur au processus démarré par le service de débogage.

5) Démarrez le service réel et profitez du débogage.

Lors du développement et du débogage d’un service Windows, je l’exécute généralement en tant qu’application de console en ajoutant un paramètre de démarrage / console et en le vérifiant. Facilite la vie

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

Qu’en est-il de Debugger.Break () dans la première ligne?

Pour déboguer Windows Services, je combine des GFlags et un fichier .reg créé par regedit.

  1. Exécuter GFlags, en spécifiant le nom exe et vsjitdebugger
  2. Exécutez regedit et allez à l’emplacement où GFlags définit ses options
  3. Choisissez “Exporter la clé” dans le menu-fichier
  4. Enregistrez ce fichier quelque part avec l’extension .reg
  5. Chaque fois que vous souhaitez déboguer le service: double-cliquez sur le fichier .reg
  6. Si vous souhaitez arrêter le débogage, double-cliquez sur le second fichier .reg.

Vous pouvez également enregistrer les extraits de code suivants et remplacer servicename.exe par le nom de l’exécutable souhaité.


debugon.reg:

  Windows Registry Editor Version 5.00

 [HKEY_LOCAL_MACHINE \ SOFTWARE \ Microsoft \ Windows NT \ CurrentVersion \ Options d'exécution du fichier image \ NomService.exe]
 "GlobalFlag" = "0x00000000"
 "Debugger" = "vsjitdebugger.exe" 

debugoff.reg:

  Windows Registry Editor Version 5.00

 [HKEY_LOCAL_MACHINE \ SOFTWARE \ Microsoft \ Windows NT \ CurrentVersion \ Options d'exécution du fichier image \ NomService.exe]
 "GlobalFlag" = "0x00000000" 

Pour la programmation de routine des petites choses, j’ai fait un truc très simple pour déboguer facilement mon service:

Au démarrage du service, je recherche un paramètre de ligne de commande “/ debug”. Si le service est appelé avec ce paramètre, je ne fais pas le démarrage du service habituel, mais lancez tous les écouteurs et affichez simplement un message “Débogage en cours, appuyez sur ok pour terminer”.

Donc, si mon service est démarré de la manière habituelle, il démarrera en tant que service, s’il est démarré avec le paramètre / debug de la ligne de commande, il agira comme un programme normal.

Dans VS, je vais simplement append / déboguer en tant que paramètre de débogage et démarrer directement le programme de service.

De cette façon, je peux facilement déboguer la plupart des petits problèmes. Bien sûr, certains éléments devront encore être débogués en tant que service, mais pour 99%, cela suffit.

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

J’utilise une variante de la réponse de JOP. À l’aide des parameters de ligne de commande, vous pouvez définir le mode de débogage dans l’EDI avec les propriétés du projet ou via le gestionnaire de services Windows.

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

C’est un peu après la question, mais pourrait être utile pour référence future.

Si vous le pouvez, je vous suggère d’utiliser l’excellent projet TopShelf . Cela simplifie considérablement le développement et le débogage d’un service Windows, et facilite également le déploiement.

Check it out: http://topshelf-project.com/

Pour dépanner le programme de service Windows existant, utilisez “Debugger.Break ()” comme suggéré par les autres utilisateurs.

Pour le nouveau programme de service Windows, je suggère d’utiliser la méthode de James Michael Hare http://geekswithblogs.net/BlackRabbitCoder/archive/2011/03/01/c-toolbox-debug-able-self-installable-windows-service-template- redux.aspx

Il suffit de placer votre déjeuner de débogueur n’importe où et d’attacher Visualstudio au démarrage

 #if DEBUG Debugger.Launch(); #endif 

De plus, vous devez lancer VS en tant qu’Administrateur et vous devez autoriser le débogage automatique d’un processus par un utilisateur différent (comme expliqué ici ):

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

Utilisez le projet de modèle de service Windows C # pour créer une nouvelle application de service https://github.com/HarpyWar/windows-service-template

Il existe un mode console / service automatiquement détecté, un programme d’installation / désinstallation automatique de votre service et plusieurs fonctionnalités les plus utilisées sont incluses.

Voici la méthode simple que j’ai utilisée pour tester le service, sans aucune méthode de “débogage” supplémentaire et avec des tests unitaires VS intégrés.

 [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) { ssortingng[] 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 } } 

Vous avez deux options pour effectuer le débogage.

  1. créer un fichier journal: Personnellement, je préfère un fichier journal distinct, comme un fichier texte, plutôt que d’utiliser le journal des applications ou le journal des événements.
  2. Convertir l’application en application console: cela vous permettra, tous les outils de débogage que nous pouvons utiliser dans VS.

S’il vous plaît se référer à cet article de blog que j’ai créé pour le sujet.

Il suffit de coller

 Debugger.Break(); 

n’importe où dans votre code.

Par exemple ,

 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); } } 

Il va bash Debugger.Break(); lorsque vous exécutez votre programme.

La meilleure option est d’utiliser l’espace de noms ‘ System.Diagnostics ‘.

Joignez votre code dans if else, bloquez pour le mode debug et le mode release, comme indiqué ci-dessous, pour basculer entre le mode debug et le mode release dans 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