Applicazione console .NET come servizio di Windows


145

Ho un'applicazione console e vorrei eseguirla come servizio di Windows. VS2010 ha un modello di progetto che consente di collegare il progetto console e creare un servizio Windows. Vorrei non aggiungere un progetto di servizio separato e, se possibile, integrare il codice di servizio nell'applicazione console per mantenere l'applicazione console come un progetto che potrebbe essere eseguito come applicazione console o come servizio Windows se eseguito ad esempio dalla riga di comando utilizzando gli switch.

Forse qualcuno potrebbe suggerire la libreria di classi o lo snippet di codice che potrebbe trasformare rapidamente e facilmente l'applicazione console # in servizio?


Perché non creare un progetto di servizio temporaneo e copiarlo sui bit che lo rendono un servizio?
Gabe,

4
Puoi provare Topshelf topshelf-project.com
Artem Koshelev il

Potresti provare la tecnica qui descritta: einaregilsson.com/2007/08/15/…
Joe

eh? Non ne sono sicuro. a questo proposito.

2
Un'alternativa molto semplice al top shelf: runasservice.com
Luis Perez,

Risposte:


185

Di solito utilizzo la seguente tecnica per eseguire la stessa app come applicazione console o come servizio:

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.UserInteractiveè normalmente vero per l'app console e falso per un servizio. Tecnicamente, è possibile eseguire un servizio in modalità interattiva per l'utente, quindi è possibile controllare un'opzione della riga di comando.


3
Si utilizza la classe ServiceInstaller, vedere msdn.microsoft.com/en-us/library/… .
VladV,

2
Questo è previsto: il servizio verrà eseguito come processo separato (quindi verrà visualizzato nel task manager), ma questo processo verrebbe controllato dal sistema (ad es. Avvio, arresto, riavvio in base alle impostazioni del servizio).
VladV,

2
Se lo esegui come app console, non vedrai un servizio. Lo scopo di questo codice è di consentirti di eseguirlo come app console o come servizio. Per eseguire come servizio è necessario prima installarlo (utilizzando la classe ServiceInstaller - vedere il collegamento MSDN sopra - o installuitil.exe) ed eseguire il servizio dal pannello di controllo.
VladV,

2
ServiceInstaller è solo una classe di utilità per gestire i servizi di Windows (un po 'come le utility installutil.exe o sc.exe). Potresti usarlo per installare quello che vuoi come servizio, il sistema operativo non si preoccupa del tipo di progetto che usi.
VladV,

5
Aggiungi un riferimento nel tuo progetto a System.ServiceProcess e sarai in grado di utilizzare il codice sopra
danimal

59

Ho avuto un grande successo con TopShelf .

TopShelf è un pacchetto Nuget progettato per semplificare la creazione di app .NET per Windows che possono essere eseguite come app console o come servizi Windows. È possibile collegare rapidamente eventi come gli eventi Start e Stop del servizio, configurare utilizzando il codice, ad esempio per impostare l'account con cui viene eseguito, configurare le dipendenze da altri servizi e configurare il modo in cui recupera dagli errori.

Dalla console di Package Manager (Nuget):

Install-Package Topshelf

Fare riferimento agli esempi di codice per iniziare.

Esempio:

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 si occupa anche dell'installazione del servizio, il che può far risparmiare molto tempo e rimuovere il codice del boilerplate dalla tua soluzione. Per installare il tuo .exe come servizio è sufficiente eseguire quanto segue dal prompt dei comandi:

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

Non è necessario collegare un ServiceInstaller e tutto il resto: TopShelf fa tutto per te.


1
Salve, sto ottenendo questo: - "Impossibile installare il pacchetto 'Topshelf 4.0.1'. Stai tentando di installare questo pacchetto in un progetto che ha come target '.NETFramework, Version = v4.5', ma il pacchetto non contiene alcun riferimenti di assembly o file di contenuto compatibili con tale framework. " cosa c'è di sbagliato qui?

3
Assicurati di scegliere come target l'intero runtime .NET 4.5.2, non il profilo client.
salpa il

per favore, puoi gettare più luce su myservice.exe e da quale directory
aprirai

1
@Izuagbala myservice.exe è l'applicazione console che hai creato, con TopShelf avviato come mostrato nell'esempio di codice.
saille

Myservice.exe può essere eseguito come console dopo l'installazione come servizio? La documentazione non è chiara: "Una volta creata l'applicazione console, lo sviluppatore crea una singola classe di servizio" docs.topshelf-project.com/en/latest/overview/…
Michael Freidgeim

27

Quindi, ecco la procedura dettagliata completa:

  1. Crea nuovo progetto Applicazione console (ad es. MyService)
  2. Aggiungi due riferimenti alla libreria: System.ServiceProcess e System.Configuration.Install
  3. Aggiungi i tre file stampati di seguito
  4. Compilare il progetto ed eseguire "InstallUtil.exe c: \ path \ to \ MyService.exe"
  5. Ora dovresti vedere MyService nell'elenco dei servizi (esegui services.msc)

* InstallUtil.exe di solito si trova qui: 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);
        }
    }
}

1
Se stai compilando il tuo progetto per 64 bit devi usare InstallUtil.exe per 64 bit che puoi trovare qui: C: \ windows \ Microsoft.NET \ Framework64 \ ... La versione per 32 bit (C: \ windows \ Microsoft.NET \ Framework) genererà una BadImageFormatException ...
snytek,

Funziona molto bene, nota che come dice @snytek, se stai usando la base 64, assicurati di usare la directory corretta. Inoltre, se ti capita di fare lo stesso e dimentichi di rinominare il servizio con qualcosa di diverso da "MyService", assicurati di disinstallare il servizio prima di apportare le modifiche al codice.
dmoore1181,

3

Ho sentito che vuoi che un assembly fermi il codice ripetuto ma, sarebbe più semplice e ridurrebbe la ripetizione del codice e renderebbe più facile riutilizzare il tuo codice in altri modi in futuro se ...... lo spezzerai in 3 assembly.

  1. Un gruppo di librerie che svolge tutto il lavoro. Quindi hanno due progetti molto molto sottili / semplici:
  2. uno che è la riga di comando
  3. uno che è il servizio di Windows.

1
Questo è come ho fatto per anni - il servizio è praticamente Start()e Stop()metodi e l'applicazione console ha un ciclo. A corto di usare un framework come TopShelf , questa è l'opzione migliore
Basic

essere d'accordo con quella risposta di più. l'uso di strumenti per feste 3D per soluzioni semplici rende le future manutenzioni inutili complesse
tatigo,

3

Ecco un modo più recente di come trasformare un'applicazione console in un servizio Windows come un servizio di lavoro basato sull'ultimo .Net Core 3.1 .

Se crei un servizio di lavoro da Visual Studio 2019 ti fornirà quasi tutto il necessario per la creazione di un servizio Windows, che è anche ciò che devi modificare nell'applicazione console per convertirlo in un servizio Windows.

Ecco le modifiche che devi fare:

Installa i seguenti pacchetti NuGet

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

Cambia Program.cs per avere un'implementazione come di seguito:

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

e aggiungi Worker.cs dove inserirai il codice che verrà eseguito dalle operazioni di servizio:

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

Quando tutto è pronto e l'applicazione è stata compilata correttamente, è possibile utilizzare sc.exe per installare exe dell'applicazione console come servizio di Windows con il comando seguente:

sc.exe create DemoService binpath= "path/to/your/file.exe"

2

Puoi usare

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

E apparirà nell'elenco dei servizi. Non so, comunque se funzioni correttamente. Un servizio di solito deve ascoltare diversi eventi.

Esistono diversi wrapper di servizi, che possono eseguire qualsiasi applicazione come un vero servizio. Ad esempio Microsofts SrvAny dal Resource Kit Win2003


Come dici tu, il servizio exe dovrà comunicare con Windows. +1 per il collegamento a SrvAny
Jodrell

5
Considererei questo approccio non sicuro. Windows ha librerie e utilità speciali per gestire i servizi e hanno maggiori probabilità di funzionare in modo coerente in diverse versioni e ambienti del sistema operativo. Per l'app .NET è abbastanza facile creare un programma di installazione MSI in VS. È anche possibile eseguire l'installazione progressivamente utilizzando il metodo ManagedInstallerClass.InstallHelper.
VladV,

1
Non c'è bisogno di programmi di installazione e roba: basta usare questa riga di comando: sc create MyServiceName binPath = "c: \ path \ to \ service \ file \ exe"
JDC,

2

Innanzitutto ho incorporato la soluzione dell'applicazione console nella soluzione di servizio di Windows e ho fatto riferimento a essa.

Quindi rendo pubblica l'applicazione Program class

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

Quindi creo due funzioni all'interno dell'applicazione 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
    }

Quindi all'interno del servizio Windows stesso istanzio il Programma e chiamo le funzioni Start e Stop aggiunte all'interno di OnStart e OnStop. Vedi sotto

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

Questo approccio può essere utilizzato anche per un'applicazione ibrida per Windows / servizio Windows


questo è fondamentalmente ciò che JonAlb ha detto nella risposta precedente, ma grazie per l'esempio di codice
tatigo,

0

Forse dovresti definire ciò di cui hai bisogno, per quanto ne so, non puoi eseguire la tua app come Console o Servizio con riga di comando, allo stesso tempo. Ricorda che il servizio è installato e devi avviarlo in Gestione servizi, puoi creare una nuova applicazione che avvia il servizio o avvia un nuovo processo eseguendo la tua app console. Ma come hai scritto

"mantieni l'applicazione console come un unico progetto"

Una volta, ero nella tua posizione, trasformando un'applicazione console in un servizio. Innanzitutto è necessario il modello, nel caso in cui si lavori con VS Express Edition. Ecco un link in cui puoi avere i tuoi primi passi: Servizio Windows C # , questo mi è stato molto utile. Quindi utilizzando quel modello, aggiungi il tuo codice agli eventi desiderati del servizio.

Per migliorare il tuo servizio, c'è un'altra cosa che puoi fare, ma questo non è rapido e / o facile, sta usando gli appdomain e creando dll da caricare / scaricare. In uno puoi avviare un nuovo processo con l'app console e in un altro dll puoi semplicemente mettere la funzionalità che il servizio deve fare.

In bocca al lupo.


0

È necessario separare la funzionalità in una o più classi e avviarla tramite uno dei due stub. Lo stub della console o lo stub di servizio.

Come è evidente, durante l'esecuzione di Windows, i numerosi servizi che compongono l'infrastruttura non presentano (e non possono direttamente) presentare all'utente le finestre della console. Il servizio deve comunicare con l'utente in modo non grafico: tramite SCM; nel registro eventi, ad alcuni file di registro, ecc. Il servizio dovrà anche comunicare con Windows tramite SCM, altrimenti verrà arrestato.

Sarebbe ovviamente accettabile disporre di alcune app per console in grado di comunicare con il servizio, ma il servizio deve essere eseguito in modo indipendente senza richiedere l'interazione con la GUI.

Lo stub della console può essere molto utile per il comportamento del servizio di debug ma non dovrebbe essere utilizzato in un ambiente "prodotto" che, dopo tutto, ha lo scopo di creare un servizio.

Non l'ho letto completamente, ma questo articolo sembra pungere nella giusta direzione.


0

Uso una classe di servizio che segue il modello standard prescritto da ServiceBase, e cerco gli helper per un facile debug F5. Ciò mantiene i dati di servizio definiti all'interno del servizio, rendendoli facili da trovare e la loro vita facile da gestire.

Normalmente creo un'applicazione Windows con la struttura seguente. Non creo un'applicazione console; in questo modo non mi viene in mente una grande scatola nera ogni volta che eseguo l'app. Rimango nel debugger dove si trova tutta l'azione. Uso in Debug.WriteLinemodo che i messaggi vadano nella finestra di output, che si aggancia bene e rimane visibile dopo il termine dell'app.

Di solito non mi preoccupo di aggiungere il codice di debug per l'arresto; Invece uso solo il debugger. Se devo interrompere il debug, trasformo il progetto in un'app console, aggiungo un Stopmetodo di inoltro e lo chiamo dopo una chiamata a 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);
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.