Application console .NET en tant que service Windows

145

J'ai une application console et je souhaite l'exécuter en tant que service Windows. VS2010 a un modèle de projet qui permet d'attacher un projet de console et de créer un service Windows. Je voudrais ne pas ajouter de projet de service séparé et si possible intégrer le code de service dans l'application console pour conserver l'application console en tant que projet unique qui pourrait fonctionner en tant qu'application console ou en tant que service Windows si elle est exécutée par exemple à partir de la ligne de commande à l'aide de commutateurs.

Peut-être que quelqu'un pourrait suggérer une bibliothèque de classes ou un extrait de code qui pourrait rapidement et facilement transformer une application console c # en service?

Tomas
la source
Pourquoi ne créez-vous pas simplement un projet de service temporaire et ne copiez pas les éléments qui en font un service?
Gabe
4
Vous pouvez essayer Topshelf topshelf-project.com
Artem Koshelev
Vous pouvez essayer la technique décrite ici: einaregilsson.com/2007/08/15/…
Joe
hein? Je ne suis pas sûr. à propos de ça.
2
Une alternative très simple à l'étagère supérieure: runasservice.com
Luis Perez

Réponses:

185

J'utilise généralement la technologie suivante pour exécuter la même application en tant qu'application console ou en tant que service:

public static class Program
{
    #region Nested classes to support running as service
    public const string ServiceName = "MyService";

    public class Service : ServiceBase
    {
        public Service()
        {
            ServiceName = Program.ServiceName;
        }

        protected override void OnStart(string[] args)
        {
            Program.Start(args);
        }

        protected override void OnStop()
        {
            Program.Stop();
        }
    }
    #endregion

    static void Main(string[] args)
    {
        if (!Environment.UserInteractive)
            // running as service
            using (var service = new Service())
                ServiceBase.Run(service);
        else
        {
            // running as console app
            Start(args);

            Console.WriteLine("Press any key to stop...");
            Console.ReadKey(true);

            Stop();
        }
    }

    private static void Start(string[] args)
    {
        // onstart code here
    }

    private static void Stop()
    {
        // onstop code here
    }
}

Environment.UserInteractiveest normalement vrai pour l'application console et faux pour un service. Techniquement, il est possible d'exécuter un service en mode interactif avec l'utilisateur, vous pouvez donc vérifier un commutateur de ligne de commande à la place.

VladV
la source
3
Vous utilisez la classe ServiceInstaller, voir msdn.microsoft.com/en-us/library/… .
VladV
2
C'est attendu - votre service s'exécuterait comme un processus séparé (il serait donc affiché dans le gestionnaire de tâches), mais ce processus serait contrôlé par le système (par exemple démarré, arrêté, redémarré selon les paramètres du service).
VladV
2
Si vous l'exécutez en tant qu'application console, vous ne verrez pas de service. Le but de ce code est de vous permettre de l'exécuter soit en tant qu'application console, soit en tant que service. Pour exécuter en tant que service, vous devez d'abord l'installer (à l'aide de la classe ServiceInstaller - voir le lien MSDN ci-dessus - ou installuitil.exe), puis exécuter le service à partir du panneau de configuration.
VladV
2
ServiceInstaller est juste une classe d'utilitaires pour gérer les services Windows (un peu comme les utilitaires installutil.exe ou sc.exe). Vous pouvez l'utiliser pour installer tout ce que vous voulez en tant que service, le système d'exploitation ne se soucie pas du type de projet que vous utilisez.
VladV
5
Ajoutez simplement une référence dans votre projet à System.ServiceProcess et vous pourrez utiliser le code ci
danimal
59

J'ai eu beaucoup de succès avec TopShelf .

TopShelf est un package Nuget conçu pour faciliter la création d'applications Windows .NET pouvant s'exécuter en tant qu'applications console ou services Windows. Vous pouvez connecter rapidement des événements tels que les événements de démarrage et d'arrêt de votre service, configurer à l'aide du code, par exemple pour définir le compte sous lequel il s'exécute, configurer les dépendances sur d'autres services et configurer la manière dont il récupère les erreurs.

Depuis la console du gestionnaire de packages (Nuget):

Install-Package Topshelf

Reportez-vous aux exemples de code pour commencer.

Exemple:

HostFactory.Run(x =>                                 
{
    x.Service<TownCrier>(s =>                        
    {
       s.ConstructUsing(name=> new TownCrier());     
       s.WhenStarted(tc => tc.Start());              
       s.WhenStopped(tc => tc.Stop());               
    });
    x.RunAsLocalSystem();                            

    x.SetDescription("Sample Topshelf Host");        
    x.SetDisplayName("Stuff");                       
    x.SetServiceName("stuff");                       
}); 

TopShelf s'occupe également de l'installation du service, ce qui peut gagner beaucoup de temps et supprime le code standard de votre solution. Pour installer votre .exe en tant que service, exécutez simplement ce qui suit à partir de l'invite de commande:

myservice.exe install -servicename "MyService" -displayname "My Service" -description "This is my service."

Vous n'avez pas besoin de connecter un ServiceInstaller et tout cela - TopShelf fait tout pour vous.

saille
la source
1
Salut, je reçois ceci: - "Impossible d'installer le package 'Topshelf 4.0.1'. Vous essayez d'installer ce package dans un projet qui cible '.NETFramework, Version = v4.5', mais le package ne contient aucun références d'assembly ou fichiers de contenu compatibles avec cette infrastructure. " qu'est-ce qui ne va pas ici?
3
Assurez-vous que vous ciblez le runtime .NET 4.5.2 complet, pas le profil client.
saille
s'il vous plaît pouvez-vous jeter plus de lumière sur myservice.exe et à partir de quel répertoire allez-vous ouvrir l'invite de commande
Izuagbala
1
@Izuagbala myservice.exe est l'application console que vous avez créée, avec TopShelf amorcé comme indiqué dans l'exemple de code.
saille
Myservice.exe peut-il être exécuté en tant que console après son installation en tant que service?. La documentation n'est pas claire: "Une fois l'application console créée, le développeur crée une seule classe de service" docs.topshelf-project.com/en/latest/overview
Michael Freidgeim
27

Voici donc la procédure pas à pas complète:

  1. Créer un nouveau projet d'application console (par exemple MyService)
  2. Ajoutez deux références de bibliothèque: System.ServiceProcess et System.Configuration.Install
  3. Ajoutez les trois fichiers imprimés ci-dessous
  4. Générez le projet et exécutez "InstallUtil.exe c: \ path \ to \ MyService.exe"
  5. Vous devriez maintenant voir MyService dans la liste des services (exécutez services.msc)

* InstallUtil.exe se trouve généralement ici: C: \ windows \ Microsoft.NET \ Framework \ v4.0.30319 \ InstallUtil.ex‌ e

Program.cs

using System;
using System.IO;
using System.ServiceProcess;

namespace MyService
{
    class Program
    {
        public const string ServiceName = "MyService";

        static void Main(string[] args)
        {
            if (Environment.UserInteractive)
            {
                // running as console app
                Start(args);

                Console.WriteLine("Press any key to stop...");
                Console.ReadKey(true);

                Stop();
            }
            else
            {
                // running as service
                using (var service = new Service())
                {
                    ServiceBase.Run(service);
                }
            }
        }

        public static void Start(string[] args)
        {
            File.AppendAllText(@"c:\temp\MyService.txt", String.Format("{0} started{1}", DateTime.Now, Environment.NewLine));
        }

        public static void Stop()
        {
            File.AppendAllText(@"c:\temp\MyService.txt", String.Format("{0} stopped{1}", DateTime.Now, Environment.NewLine));
        }
    }
}

MyService.cs

using System.ServiceProcess;

namespace MyService
{
    class Service : ServiceBase
    {
        public Service()
        {
            ServiceName = Program.ServiceName;
        }

        protected override void OnStart(string[] args)
        {
            Program.Start(args);
        }

        protected override void OnStop()
        {
            Program.Stop();
        }
    }
}

MyServiceInstaller.cs

using System.ComponentModel;
using System.Configuration.Install;
using System.ServiceProcess;

namespace MyService
{
    [RunInstaller(true)]
    public class MyServiceInstaller : Installer
    {
        public MyServiceInstaller()
        {
            var spi = new ServiceProcessInstaller();
            var si = new ServiceInstaller();

            spi.Account = ServiceAccount.LocalSystem;
            spi.Username = null;
            spi.Password = null;

            si.DisplayName = Program.ServiceName;
            si.ServiceName = Program.ServiceName;
            si.StartType = ServiceStartMode.Automatic;

            Installers.Add(spi);
            Installers.Add(si);
        }
    }
}
Nikolai Koudelia
la source
1
Si vous compilez votre projet pour 64 bits, vous devez utiliser InstallUtil.exe pour 64 bits qui se trouve ici: C: \ windows \ Microsoft.NET \ Framework64 \ ... La version pour 32 bits (C: \ windows \ Microsoft.NET \ Framework) vous lancera une BadImageFormatException ...
snytek
Cela fonctionne très bien, notez que comme le dit @snytek, si vous utilisez la base 64, assurez-vous d'utiliser le bon répertoire. De plus, si vous faites la même chose que moi et que vous oubliez de renommer le service en autre chose que "MyService", assurez-vous de désinstaller le service avant d'apporter les modifications au code.
dmoore1181
3

J'entends votre point de vouloir qu'un assemblage arrête le code répété, mais ce serait plus simple et réduire la répétition du code et faciliter la réutilisation de votre code d'une autre manière à l'avenir si ...... vous le divisez en 3 assemblys.

  1. Un assemblage de bibliothèque qui fait tout le travail. Alors ayez deux projets très très minces / simples:
  2. celui qui est la ligne de commande
  3. celui qui est le service Windows.
JonAlb
la source
1
C'est comme ça que je l'ai fait pendant des années - le service a à peu près Start()et des Stop()méthodes et l'application console a une boucle. À moins d'utiliser un framework comme TopShelf , c'est la meilleure option
base
d'accord avec cette réponse le plus. l'utilisation d'outils de fête en 3D pour des solutions simples rend les futures maintenances inutiles
tatigo
3

Voici une nouvelle façon de transformer une application console en service Windows en tant que service de travail basé sur le dernier .Net Core 3.1 .

Si vous créez un service de travail à partir de Visual Studio 2019, il vous donnera presque tout ce dont vous avez besoin pour créer un service Windows prêt à l'emploi, ce qui est également ce que vous devez modifier dans l'application console afin de le convertir en service Windows.

Voici les changements que vous devez faire:

Installez les packages NuGet suivants

Install-Package Microsoft.Extensions.Hosting.WindowsServices -Version 3.1.0
Install-Package Microsoft.Extensions.Configuration.Abstractions -Version 3.1.0

Changez Program.cs pour avoir une implémentation comme ci-dessous:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace ConsoleApp
{
    class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).UseWindowsService().Build().Run();
        }

        private static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureServices((hostContext, services) =>
                {
                    services.AddHostedService<Worker>();
                });
    }
}

et ajoutez Worker.cs où vous mettrez le code qui sera exécuté par les opérations de service:

using Microsoft.Extensions.Hosting;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp
{
    public class Worker : BackgroundService
    {
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            //do some operation
        }

        public override Task StartAsync(CancellationToken cancellationToken)
        {
            return base.StartAsync(cancellationToken);
        }

        public override Task StopAsync(CancellationToken cancellationToken)
        {
            return base.StopAsync(cancellationToken);
        }
    }
}

Lorsque tout est prêt et que l'application a été créée avec succès, vous pouvez utiliser sc.exe pour installer votre exe d'application console en tant que service Windows avec la commande suivante:

sc.exe create DemoService binpath= "path/to/your/file.exe"
moiJustAndrew
la source
2

Vous pouvez utiliser

reg add HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run /v ServiceName /d "c:\path\to\service\file\exe"

Et il apparaîtra dans la liste des services. Je ne sais pas si cela fonctionne correctement. Un service doit généralement écouter plusieurs événements.

Il existe cependant plusieurs wrapper de service qui peuvent exécuter n'importe quelle application en tant que service réel. Par exemple Microsofts SrvAny du Kit de ressources Win2003

Darcara
la source
Comme vous le dites, l'exe de service devra communiquer avec Windows. +1 pour lien vers SrvAny
Jodrell
5
Je considérerais cette approche comme dangereuse. Windows possède des bibliothèques et des utilitaires spéciaux pour gérer les services, et ils sont plus susceptibles de fonctionner de manière cohérente dans différentes versions de système d'exploitation et environnements. Pour l'application .NET, il est assez facile de créer un programme d'installation MSI dans VS. Il est également possible d'effectuer l'installation de manière progressive à l'aide de la méthode ManagedInstallerClass.InstallHelper.
VladV
1
Pas besoin d'installateurs et d'autres choses: utilisez simplement cette ligne de commande: sc create MyServiceName binPath = "c: \ chemin \ vers \ service \ file \ exe"
JDC
2

Tout d'abord, j'intègre la solution d'application console dans la solution de service Windows et je la référence.

Ensuite, je rends la classe de programme de l'application console publique

/// <summary>
/// Hybrid service/console application
/// </summary>
public class Program
{
}

Je crée ensuite deux fonctions au sein de l'application console

    /// <summary>
    /// Used to start as a service
    /// </summary>
    public void Start()
    {
        Main();
    }

    /// <summary>
    /// Used to stop the service
    /// </summary>
    public void Stop()
    {
       if (Application.MessageLoop)
            Application.Exit();   //windows app
        else
            Environment.Exit(1);  //console app
    }

Ensuite, dans le service Windows lui-même, j'instancie le programme et j'appelle les fonctions de démarrage et d'arrêt ajoutées dans OnStart et OnStop. Voir ci-dessous

class WinService : ServiceBase
{
    readonly Program _application = new Program();

    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    static void Main()
    {
        ServiceBase[] servicesToRun = { new WinService() };
        Run(servicesToRun);
    }

    /// <summary>
    /// Set things in motion so your service can do its work.
    /// </summary>
    protected override void OnStart(string[] args)
    {
        Thread thread = new Thread(() => _application.Start());
        thread.Start();
    }

    /// <summary>
    /// Stop this service.
    /// </summary>
    protected override void OnStop()
    {
        Thread thread = new Thread(() => _application.Stop());
        thread.Start();
    }
}

Cette approche peut également être utilisée pour un hybride application Windows / service Windows

Patrick Reynolds
la source
c'est essentiellement ce que JonAlb a dit dans la réponse précédente, mais merci pour l'exemple de code
tatigo
0

Vous devriez peut-être définir ce dont vous avez besoin, pour autant que je sache, vous ne pouvez pas exécuter votre application en tant que console ou service avec ligne de commande, en même temps. N'oubliez pas que le service est installé et que vous devez le démarrer dans le Gestionnaire de services, vous pouvez créer une nouvelle application qui démarre le service ou démarre un nouveau processus exécutant votre application console. Mais comme tu l'écris

"conserver l'application console comme un seul projet"

Une fois, j'étais à votre place, transformant une application console en service. Vous avez d'abord besoin du modèle, au cas où vous travaillez avec VS Express Edition. Voici un lien où vous pouvez avoir vos premiers pas: Service C # Windows , cela m'a été très utile. Ensuite, à l'aide de ce modèle, ajoutez votre code aux événements souhaités du service.

Pour améliorer votre service, il y a une autre chose que vous pouvez faire, mais ce n'est pas rapide et / ou facile, consiste à utiliser des domaines d'application et à créer des dll pour charger / décharger. Dans l'un, vous pouvez démarrer un nouveau processus avec l'application console, et dans un autre dll, vous pouvez simplement mettre la fonctionnalité que le service doit faire.

Bonne chance.

BlackCath
la source
0

Vous devez séparer la fonctionnalité en une ou plusieurs classes et la lancer via l'un des deux stubs. Le stub de console ou le stub de service.

Comme il est évident, lors de l'exécution de Windows, la myriade de services qui composent l'infrastructure ne présente pas (et ne peut pas directement) présenter les fenêtres de console à l'utilisateur. Le service doit communiquer avec l'utilisateur de manière non graphique: via le SCM; dans le journal des événements, vers un fichier journal, etc. Le service devra également communiquer avec Windows via le SCM, sinon il sera arrêté.

Il serait évidemment acceptable d'avoir une application console capable de communiquer avec le service, mais le service doit fonctionner indépendamment sans nécessiter d'interaction GUI.

Le stub de la console peut être très utile pour déboguer le comportement du service, mais ne doit pas être utilisé dans un environnement "production" qui, après tout, est le but de la création d'un service.

Je ne l'ai pas lu complètement mais cet article semble aller dans la bonne direction.

Jodrell
la source
0

J'utilise une classe de service qui suit le modèle standard prescrit par ServiceBase , et j'utilise des helpers pour faciliter le débogage F5. Cela permet de garder les données de service définies dans le service, ce qui les rend faciles à trouver et leur durée de vie facile à gérer.

Je crée normalement une application Windows avec la structure ci-dessous. Je ne crée pas d'application console; de cette façon, je n'ai pas une grosse boîte noire qui me saute au visage chaque fois que je lance l'application. Je reste dans le débogueur où se trouve toute l'action. j'utiliseDebug.WriteLine pour que les messages soient dirigés vers la fenêtre de sortie, qui s'ancre bien et reste visible après la fin de l'application.

Je ne prends généralement pas la peine d'ajouter du code de débogage pour m'arrêter; J'utilise simplement le débogueur à la place. Si j'ai besoin de déboguer l'arrêt, je fais du projet une application console, j'ajoute une Stopméthode de transfert et je l'appelle après un appel à Console.ReadKey.

public class Service : ServiceBase
{
    protected override void OnStart(string[] args)
    {
        // Start logic here.
    }

    protected override void OnStop()
    {
        // Stop logic here.
    }

    static void Main(string[] args)
    {
        using (var service = new Service()) {
            if (Environment.UserInteractive) {
                service.Start();
                Thread.Sleep(Timeout.Infinite);
            } else
                Run(service);
        }
    }
    public void Start() => OnStart(null);
}
Edward Brey
la source