.NET-Konsolenanwendung als Windows-Dienst

145

Ich habe eine Konsolenanwendung und möchte sie als Windows-Dienst ausführen. VS2010 verfügt über eine Projektvorlage, mit der Konsolenprojekte angehängt und Windows-Dienste erstellt werden können. Ich möchte kein separates Dienstprojekt hinzufügen und wenn möglich Dienstcode in die Konsolenanwendung integrieren, um die Konsolenanwendung als ein Projekt beizubehalten, das als Konsolenanwendung oder als Windows-Dienst ausgeführt werden kann, wenn es beispielsweise über die Befehlszeile mit Switches ausgeführt wird.

Vielleicht könnte jemand eine Klassenbibliothek oder ein Code-Snippet vorschlagen, mit dem die c # -Konsolenanwendung schnell und einfach in einen Dienst umgewandelt werden kann?

Tomas
quelle
Warum erstellen Sie nicht einfach ein temporäres Serviceprojekt und kopieren die Bits, die es zu einem Service machen?
Gabe
4
Sie könnten versuchen, Topshelf topshelf-project.com
Artem
Sie können die hier beschriebene Technik ausprobieren: einaregilsson.com/2007/08/15/…
Joe
huh? Ich bin mir nicht sicher. darüber.
2
Eine sehr einfache Top-Regal-Alternative: runasservice.com
Luis Perez

Antworten:

185

Normalerweise verwende ich die folgende Technik, um dieselbe App als Konsolenanwendung oder als Dienst auszuführen:

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.UserInteractiveist normalerweise wahr für die Konsolen-App und falsch für einen Dienst. Technisch gesehen ist es möglich, einen Dienst im benutzerinteraktiven Modus auszuführen, sodass Sie stattdessen einen Befehlszeilenschalter überprüfen können.

VladV
quelle
3
Sie verwenden die ServiceInstaller-Klasse, siehe msdn.microsoft.com/en-us/library/… .
VladV
2
Dies wird erwartet - Ihr Dienst wird als separater Prozess ausgeführt (so dass er im Task-Manager angezeigt wird), aber dieser Prozess wird vom System gesteuert (z. B. gestartet, gestoppt, gemäß den Diensteinstellungen neu gestartet).
VladV
2
Wenn Sie es als Konsolen-App ausführen, wird kein Dienst angezeigt. Der gesamte Zweck dieses Codes besteht darin, dass Sie ihn entweder als Konsolen-App oder als Dienst ausführen können. Um als Dienst ausgeführt zu werden, müssen Sie ihn zuerst installieren (mithilfe der ServiceInstaller-Klasse - siehe MSDN-Link oben - oder installuitil.exe) und den Dienst über die Systemsteuerung ausführen.
VladV
2
ServiceInstaller ist nur eine Dienstprogrammklasse für Windows-Dienste (ein bisschen wie die Dienstprogramme installutil.exe oder sc.exe). Sie können damit jeden gewünschten Dienst als Dienst installieren. Das Betriebssystem kümmert sich nicht um den von Ihnen verwendeten Projekttyp.
VladV
5
Fügen Sie einfach einen Verweis in Ihrem Projekt zu System.ServiceProcess hinzu, und Sie können den obigen Code verwenden
danimal
59

Ich habe mit TopShelf großen Erfolg gehabt .

TopShelf ist ein Nuget-Paket, mit dem Sie auf einfache Weise .NET Windows-Apps erstellen können, die als Konsolen-Apps oder als Windows-Dienste ausgeführt werden können. Sie können Ereignisse wie Start- und Stoppereignisse Ihres Dienstes schnell verknüpfen, mithilfe von Code konfigurieren, z. B. um das Konto festzulegen, unter dem es ausgeführt wird, Abhängigkeiten von anderen Diensten konfigurieren und die Wiederherstellung nach Fehlern konfigurieren.

Über die Package Manager-Konsole (Nuget):

Install-Package Topshelf

Lesen Sie die Codebeispiele, um loszulegen.

Beispiel:

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 kümmert sich auch um die Service-Installation, was viel Zeit spart und Boilerplate-Code aus Ihrer Lösung entfernt. Um Ihre .exe als Dienst zu installieren, führen Sie einfach Folgendes an der Eingabeaufforderung aus:

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

Sie müssen keinen ServiceInstaller anschließen und das alles - TopShelf erledigt alles für Sie.

Saille
quelle
1
Hallo, ich erhalte Folgendes: - "Paket 'Topshelf 4.0.1' konnte nicht installiert werden. Sie versuchen, dieses Paket in einem Projekt zu installieren, das auf '.NETFramework, Version = v4.5' abzielt, aber das Paket enthält keine Assemblyreferenzen oder Inhaltsdateien, die mit diesem Framework kompatibel sind. " was ist hier falsch?
3
Stellen Sie sicher, dass Sie auf die vollständige .NET 4.5.2-Laufzeit abzielen, nicht auf das Client-Profil.
Saille
Bitte können Sie mehr Licht auf myservice.exe werfen und aus welchem ​​Verzeichnis werden Sie die Eingabeaufforderung öffnen
Izuagbala
1
@Izuagbala myservice.exe ist die von Ihnen erstellte Konsolenanwendung, in die TopShelf wie im Codebeispiel gezeigt gebootet ist.
Saille
Kann myservice.exe nach der Installation als Dienst als Konsole ausgeführt werden? Die Dokumentation ist nicht klar: "Sobald die Konsolenanwendung erstellt ist, erstellt der Entwickler eine einzelne Serviceklasse" docs.topshelf-project.com/de/latest/overview/…
Michael Freidgeim
27

Hier ist die vollständige Anleitung:

  1. Neues Konsolenanwendungsprojekt erstellen (z. B. MyService)
  2. Fügen Sie zwei Bibliotheksreferenzen hinzu: System.ServiceProcess und System.Configuration.Install
  3. Fügen Sie die drei unten gedruckten Dateien hinzu
  4. Erstellen Sie das Projekt und führen Sie "InstallUtil.exe c: \ path \ to \ MyService.exe" aus.
  5. Jetzt sollte MyService in der Serviceliste angezeigt werden (run services.msc).

* InstallUtil.exe finden Sie normalerweise hier: 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
quelle
1
Wenn Sie Ihr Projekt für 64-Bit kompilieren, müssen Sie die Datei InstallUtil.exe für 64-Bit verwenden, die Sie hier finden: C: \ windows \ Microsoft.NET \ Framework64 \ ... Die Version für 32-Bit (C: \ windows) \ Microsoft.NET \ Framework) wird eine BadImageFormatException auf Sie werfen ...
snytek
Dies funktioniert sehr gut. Beachten Sie, dass Sie, wie @snytek sagt, bei Verwendung von Base 64 sicherstellen müssen, dass Sie das richtige Verzeichnis verwenden. Wenn Sie das Gleiche wie ich tun und vergessen, den Dienst in etwas anderes als "MyService" umzubenennen, müssen Sie den Dienst deinstallieren, bevor Sie Änderungen am Code vornehmen.
dmoore1181
3

Ich höre Ihren Standpunkt, dass eine Assembly wiederholten Code stoppen soll, aber es wäre am einfachsten und würde die Code-Wiederholung reduzieren und es einfacher machen, Ihren Code in Zukunft auf andere Weise wiederzuverwenden, wenn Sie ihn in drei Assemblys aufteilen würden.

  1. Eine Bibliotheksbaugruppe, die die ganze Arbeit erledigt. Dann haben Sie zwei sehr sehr schlanke / einfache Projekte:
  2. eine, die die Kommandozeile ist
  3. eine, die der Windows-Dienst ist.
JonAlb
quelle
1
So habe ich es jahrelang gemacht - der Service hat so ziemlich alles Start()und Stop()Methoden und die Konsolen-App hat eine Schleife. Ohne ein Framework wie TopShelf zu verwenden , ist dies die beste Option
Basic
stimme dieser Antwort am meisten zu. Die Verwendung von 3D-Party-Tools für einfache Lösungen macht die zukünftigen Wartungsarbeiten unnötig komplex
Tatigo
3

Hier finden Sie eine neuere Methode zum Verwandeln einer Konsolenanwendung in einen Windows-Dienst als Worker-Dienst basierend auf dem neuesten .NET Core 3.1 .

Wenn Sie einen Worker-Dienst aus Visual Studio 2019 erstellen, erhalten Sie fast alles, was Sie zum sofortigen Erstellen eines Windows-Dienstes benötigen. Dies müssen Sie auch in die Konsolenanwendung ändern, um ihn in einen Windows-Dienst zu konvertieren.

Hier sind die Änderungen, die Sie vornehmen müssen:

Installieren Sie die folgenden NuGet-Pakete

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

Ändern Sie Program.cs in eine Implementierung wie die folgende:

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

und fügen Sie Worker.cs hinzu, in das Sie den Code einfügen, der von den Servicevorgängen ausgeführt wird:

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

Wenn alles fertig ist und die Anwendung erfolgreich erstellt wurde, können Sie mit sc.exe Ihre Konsolenanwendung exe als Windows-Dienst mit dem folgenden Befehl installieren:

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

Sie können verwenden

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

Und es wird in der Serviceliste angezeigt. Ich weiß aber nicht, ob das richtig funktioniert. Ein Dienst muss normalerweise mehrere Ereignisse abhören.

Es gibt jedoch mehrere Service-Wrapper, mit denen jede Anwendung als echter Service ausgeführt werden kann. Zum Beispiel Microsoft SrvAny aus dem Win2003 Resource Kit

Darcara
quelle
Wie Sie sagen, muss die Service-Exe mit Windows kommunizieren. +1 für Link zu SrvAny
Jodrell
5
Ich würde diesen Ansatz als unsicher betrachten. Windows verfügt über spezielle Bibliotheken und Dienstprogramme zum Verwalten von Diensten, und es ist wahrscheinlicher, dass sie in verschiedenen Betriebssystemversionen und -umgebungen konsistent funktionieren. Für die .NET-App ist es recht einfach, ein MSI-Installationsprogramm in VS zu erstellen. Es ist auch möglich, die Installation mithilfe der ManagedInstallerClass.InstallHelper-Methode programmgesteuert durchzuführen.
VladV
1
Keine Notwendigkeit für Installer und andere Dinge: Verwenden Sie einfach diese Befehlszeile: sc create MyServiceName binPath = "c: \ Pfad \ zu \ Service \ Datei \ exe"
JDC
2

Zuerst binde ich die Konsolenanwendungslösung in die Windows-Servicelösung ein und verweise darauf.

Dann mache ich die Konsolenanwendung Program class öffentlich

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

Ich erstelle dann zwei Funktionen innerhalb der Konsolenanwendung

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

Dann instanziiere ich innerhalb des Windows-Dienstes selbst das Programm und rufe die Start- und Stoppfunktionen auf, die in OnStart und OnStop hinzugefügt wurden. Siehe unten

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

Dieser Ansatz kann auch für einen Windows-Anwendungs- / Windows-Dienst-Hybrid verwendet werden

Patrick Reynolds
quelle
Dies ist im Grunde, was JonAlb in der vorherigen Antwort gesagt hat, aber danke für das Codebeispiel
Tatigo
0

Vielleicht sollten Sie definieren, was Sie benötigen. Soweit ich weiß, können Sie Ihre App nicht gleichzeitig als Konsole oder Dienst mit Befehlszeile ausführen. Denken Sie daran, dass der Dienst installiert ist und Sie ihn im Services Manager starten müssen. Sie können eine neue Anwendung erstellen, die den Dienst startet oder einen neuen Prozess zum Ausführen Ihrer Konsolen-App startet. Aber wie du geschrieben hast

"Konsolenanwendung als ein Projekt behalten"

Einmal war ich in Ihrer Position und habe aus einer Konsolenanwendung einen Dienst gemacht. Zuerst benötigen Sie die Vorlage, falls Sie mit VS Express Edition arbeiten. Hier ist ein Link, über den Sie Ihre ersten Schritte ausführen können: C # Windows-Dienst , das war sehr hilfreich für mich. Fügen Sie dann mithilfe dieser Vorlage Ihren Code zu den gewünschten Ereignissen des Dienstes hinzu.

Um Ihren Service zu verbessern, können Sie noch etwas anderes tun. Dies ist jedoch nicht schnell und / oder einfach. Sie verwenden Appdomains und erstellen DLLs zum Laden / Entladen. In einer können Sie einen neuen Prozess mit der Konsolen-App starten, und in einer anderen DLL können Sie einfach die Funktionen festlegen, die der Dienst ausführen muss.

Viel Glück.

BlackCath
quelle
0

Sie müssen die Funktionalität in eine Klasse oder Klassen aufteilen und diese über einen von zwei Stubs starten. Der Konsolen- oder Service-Stub.

Wie zu sehen ist, präsentieren die unzähligen Dienste, aus denen die Infrastruktur besteht, beim Ausführen von Fenstern dem Benutzer keine Konsolenfenster (und können diese auch nicht direkt). Der Dienst muss nicht grafisch mit dem Benutzer kommunizieren: über das SCM; im Ereignisprotokoll, in einer Protokolldatei usw. Der Dienst muss auch über das SCM mit Windows kommunizieren, sonst wird er heruntergefahren.

Es wäre natürlich akzeptabel, eine Konsolen-App zu haben, die mit dem Dienst kommunizieren kann, aber der Dienst muss unabhängig ausgeführt werden, ohne dass eine GUI-Interaktion erforderlich ist.

Der Console-Stub kann sehr nützlich für das Debuggen des Dienstverhaltens sein, sollte jedoch nicht in einer "produzierten" Umgebung verwendet werden, die schließlich zum Erstellen eines Dienstes dient.

Ich habe es nicht vollständig gelesen, aber dieser Artikel scheint in die richtige Richtung zu weisen .

Jodrell
quelle
0

Ich verwende eine Serviceklasse, die dem von vorgeschriebenen Standardmuster folgt ServiceBase, und gehe auf Helfer ein, um das F5-Debugging zu vereinfachen. Auf diese Weise bleiben die Servicedaten innerhalb des Service definiert, sodass sie leicht zu finden und ihre Lebensdauer einfach zu verwalten sind.

Normalerweise erstelle ich eine Windows-Anwendung mit der folgenden Struktur. Ich erstelle keine Konsolenanwendung. Auf diese Weise taucht nicht jedes Mal, wenn ich die App starte, eine große Black Box in meinem Gesicht auf. Ich bleibe im Debugger, wo die ganze Aktion ist. Ich benutze, Debug.WriteLinedamit die Nachrichten in das Ausgabefenster gehen, das gut andockt und sichtbar bleibt, nachdem die App beendet wurde.

Normalerweise füge ich keinen Debug-Code zum Stoppen hinzu. Ich benutze stattdessen nur den Debugger. Wenn ich das Stoppen debuggen muss, mache ich das Projekt zu einer Konsolen-App, füge eine StopWeiterleitungsmethode hinzu und rufe sie nach einem Aufruf von auf 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
quelle