Aplikacja konsoli .NET jako usługa Windows

145

Mam aplikację konsolową i chciałbym ją uruchomić jako usługę Windows. VS2010 posiada szablon projektu, który umożliwia dołączenie projektu konsoli i zbudowanie usługi Windows. Chciałbym nie dodawać oddzielnego projektu usługi i jeśli to możliwe zintegrować kod usługi z aplikacją konsolową, aby aplikacja konsolowa była jednym projektem, który mógłby działać jako aplikacja konsoli lub jako usługa systemu Windows, gdyby była uruchamiana na przykład z wiersza poleceń za pomocą przełączników.

Może ktoś mógłby zasugerować bibliotekę klas lub fragment kodu, który mógłby szybko i łatwo przekształcić aplikację konsoli C # w usługę?

Tomas
źródło
Dlaczego po prostu nie utworzysz tymczasowego projektu usługi i nie skopiujesz bitów, które sprawiają, że jest to usługa?
Gabe
4
Możesz wypróbować Topshelf topshelf-project.com
Artem Koshelev
Możesz wypróbować technikę opisaną tutaj: einaregilsson.com/2007/08/15/…
Joe
co? Nie jestem pewny. o tym.
2
Bardzo prosta alternatywa z górnej półki: runasservice.com
Luis Perez

Odpowiedzi:

185

Zwykle używam następującej techniki, aby uruchomić tę samą aplikację jako aplikację konsolową lub jako usługę:

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.UserInteractivejest zwykle prawdziwe dla aplikacji konsolowej i fałszywe dla usługi. Technicznie rzecz biorąc, możliwe jest uruchomienie usługi w trybie interaktywnym użytkownika, więc zamiast tego można sprawdzić przełącznik wiersza polecenia.

VladV
źródło
3
Używasz klasy ServiceInstaller, zobacz msdn.microsoft.com/en-us/library/… .
VladV,
2
To jest oczekiwane - Twoja usługa działałaby jako oddzielny proces (więc byłaby wyświetlana w menedżerze zadań), ale proces ten byłby kontrolowany przez system (np. Uruchomiony, zatrzymany, uruchomiony ponownie zgodnie z ustawieniami usługi).
VladV
2
Jeśli uruchomisz ją jako aplikację konsolową, nie zobaczysz usługi. Celem tego kodu jest umożliwienie uruchomienia go jako aplikacji konsolowej lub usługi. Aby działać jako usługa, należy ją najpierw zainstalować (za pomocą klasy ServiceInstaller - patrz link MSDN powyżej - lub installuitil.exe), a następnie uruchomić usługę z panelu sterowania.
VladV
2
ServiceInstaller to po prostu klasa narzędziowa do obsługi usług systemu Windows (trochę jak narzędzia installutil.exe lub sc.exe). Możesz go użyć do zainstalowania dowolnej usługi jako usługi, system operacyjny nie dba o typ projektu, którego używasz.
VladV
5
Po prostu dodaj odniesienie w swoim projekcie do System.ServiceProcess, a będziesz mógł użyć powyższego kodu
danimal
59

Odniosłem wielki sukces z TopShelf .

TopShelf to pakiet Nuget zaprojektowany, aby ułatwić tworzenie aplikacji .NET Windows, które mogą działać jako aplikacje konsolowe lub jako usługi Windows. Możesz szybko podłączyć zdarzenia, takie jak zdarzenia Start i Stop usługi, skonfigurować za pomocą kodu, np. Ustawić konto, na którym działa, skonfigurować zależności z innymi usługami i skonfigurować sposób odzyskiwania po błędach.

Z konsoli Menedżera pakietów (Nuget):

Pakiet instalacyjny Topshelf

Zapoznaj się z przykładami kodu, aby rozpocząć.

Przykład:

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 zajmuje się również instalacją serwisową, co pozwala zaoszczędzić sporo czasu i usuwa standardowy kod z Twojego rozwiązania. Aby zainstalować plik .exe jako usługę, po prostu wykonaj następujące czynności w wierszu polecenia:

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

Nie musisz podłączać ServiceInstallera i tak dalej - TopShelf zrobi to wszystko za Ciebie.

żaglowiec
źródło
1
Cześć, otrzymuję to: - „Nie można zainstalować pakietu„ Topshelf 4.0.1 ”. Próbujesz zainstalować ten pakiet w projekcie, który jest przeznaczony dla„ .NETFramework, Version = v4.5 ”, ale pakiet nie zawiera żadnego odwołania do zestawów lub pliki zawartości, które są zgodne z tą strukturą ”. co tu jest nie tak?
3
Upewnij się, że celem jest pełne środowisko uruchomieniowe .NET 4.5.2, a nie profil klienta.
saille
proszę, czy możesz rzucić więcej światła na myservice.exe i z którego katalogu zamierzasz otworzyć
wiersz
1
@Izuagbala myservice.exe to aplikacja konsoli, którą utworzyłeś, z załadowanym programem TopShelf, jak pokazano w przykładowym kodzie.
żagiel
Czy myservice.exe można uruchomić jako konsolę po zainstalowaniu go jako usługi ?. Dokumentacja nie jest jasna: „Po utworzeniu aplikacji konsolowej programista tworzy pojedynczą klasę usługi” docs.topshelf-project.com/en/latest/overview/ ...
Michael Freidgeim
27

Oto pełna instrukcja:

  1. Utwórz nowy projekt aplikacji konsolowej (np. MyService)
  2. Dodaj dwa odwołania do bibliotek: System.ServiceProcess i System.Configuration.Install
  3. Dodaj trzy wydrukowane poniżej pliki
  4. Zbuduj projekt i uruchom „InstallUtil.exe c: \ path \ to \ MyService.exe”
  5. Teraz powinieneś zobaczyć MyService na liście usług (uruchom services.msc)

* InstallUtil.exe można zwykle znaleźć tutaj: 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
źródło
1
Jeśli kompilujesz swój projekt w wersji 64-bitowej, musisz użyć programu InstallUtil.exe dla wersji 64-bitowej, który można znaleźć tutaj: C: \ windows \ Microsoft.NET \ Framework64 \ ... Wersja 32-bitowa (C: \ windows \ Microsoft.NET \ Framework) wyrzuci wyjątek BadImageFormatException ...
snytek
Działa to bardzo dobrze, zauważ, że jak mówi @snytek, jeśli używasz podstawy 64, upewnij się, że używasz prawidłowego katalogu. Ponadto, jeśli zdarzy ci się zrobić to samo co ja i zapomnisz zmienić nazwę usługi na inną niż „MyService”, upewnij się, że odinstalowałeś usługę przed wprowadzeniem zmian w kodzie.
dmoore1181
3

Słyszę, że chcesz, aby jeden zestaw zatrzymywał powtarzający się kod, ale byłoby to najprostsze i ograniczyło powtarzanie kodu i ułatwiłoby ponowne użycie kodu w inny sposób w przyszłości, jeśli ...... podzielisz go na 3 zespoły.

  1. Jeden zespół biblioteczny, który wykonuje całą pracę. Następnie przygotuj dwa bardzo proste / proste projekty:
  2. taki, który jest linią poleceń
  3. taki, który jest usługą systemu Windows.
JonAlb
źródło
1
W ten sposób robię to od lat - usługa prawie ma Start()i Stop()metody, a aplikacja konsolowa ma pętlę. Oprócz korzystania z frameworka, takiego jak TopShelf , jest to najlepsza opcja
Basic
zgadzam się z tą odpowiedzią najbardziej. używanie narzędzi firm 3D do prostych rozwiązań sprawia, że ​​przyszłe konserwacje są niepotrzebne
tatigo
3

Oto nowszy sposób przekształcania aplikacji konsoli w usługę systemu Windows jako usługę roboczą w oparciu o najnowszy .Net Core 3.1 .

Jeśli utworzysz usługę Worker Service z programu Visual Studio 2019, da ci ona prawie wszystko, czego potrzebujesz do utworzenia usługi Windows po wyjęciu z pudełka, co również musisz zmienić na aplikację konsolową, aby przekonwertować ją na usługę Windows.

Oto zmiany, które musisz wprowadzić:

Zainstaluj następujące pakiety NuGet

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

Zmień Program.cs, aby miał implementację taką jak poniżej:

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

i dodaj Worker.cs, gdzie umieścisz kod, który będzie uruchamiany przez operacje serwisowe:

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

Gdy wszystko jest gotowe, a aplikacja została pomyślnie zbudowana, możesz użyć programu sc.exe, aby zainstalować aplikację konsoli exe jako usługę systemu Windows za pomocą następującego polecenia:

sc.exe create DemoService binpath= "path/to/your/file.exe"
meJustAndrew
źródło
2

Możesz użyć

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

I pojawi się na liście usług. Nie wiem jednak, czy to działa poprawnie. Usługa zwykle musi słuchać kilku wydarzeń.

Istnieje jednak kilka opakowań usług, które mogą uruchamiać dowolną aplikację jako prawdziwą usługę. Na przykład Microsofts SrvAny z zestawu zasobów Win2003

Darcara
źródło
Jak mówisz, exe usługi będzie musiało komunikować się z oknami. +1 dla linku do SrvAny
Jodrell,
5
Uważam to podejście za niebezpieczne. System Windows ma specjalne biblioteki i narzędzia do zarządzania usługami i jest bardziej prawdopodobne, że będą one działać konsekwentnie w różnych wersjach systemu operacyjnego i środowiskach. W przypadku aplikacji .NET utworzenie instalatora MSI w VS. Możliwe jest również przeprowadzenie instalacji w sposób progresywny przy użyciu metody ManagedInstallerClass.InstallHelper.
VladV
1
Nie potrzeba instalatorów i innych rzeczy: po prostu użyj tego wiersza poleceń: sc create MyServiceName binPath = "c: \ path \ to \ service \ file \ exe"
JDC
2

Najpierw osadzam rozwiązanie aplikacji konsoli w rozwiązaniu usługowym systemu Windows i odnoszę się do niego.

Następnie upubliczniam klasę Program aplikacji konsoli

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

Następnie tworzę dwie funkcje w aplikacji konsoli

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

Następnie w samej usłudze Windows tworzę instancję programu i wywołuję funkcje Start i Stop dodane w ramach OnStart i OnStop. Zobacz poniżej

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

To podejście można również zastosować w przypadku hybrydy aplikacji systemu Windows / usługi systemu Windows

Patrick Reynolds
źródło
to w zasadzie to, co JonAlb powiedział w poprzedniej odpowiedzi, ale dzięki za przykład kodu
tatigo
0

Może powinieneś zdefiniować, czego potrzebujesz, o ile wiem, nie możesz jednocześnie uruchamiać aplikacji jako konsoli lub usługi z wierszem poleceń. Pamiętaj, że usługa jest zainstalowana i musisz ją uruchomić w Menedżerze usług, możesz utworzyć nową aplikację, która uruchomi usługę lub uruchomi nowy proces z uruchomioną aplikacją konsoli. Ale jak napisałeś

„zachowaj aplikację konsolową jako jeden projekt”

Kiedyś byłem na twoim miejscu, zamieniając aplikację konsoli w usługę. Najpierw potrzebujesz szablonu, jeśli pracujesz z VS Express Edition. Oto łącze, w którym możesz postawić pierwsze kroki: Usługa C # Windows , to było dla mnie bardzo pomocne. Następnie za pomocą tego szablonu dodaj swój kod do żądanych zdarzeń usługi.

Aby ulepszyć usługę, możesz zrobić jeszcze jedną rzecz, ale nie jest to szybkie i / lub łatwe, polega na używaniu domen aplikacji i tworzeniu bibliotek dll do ładowania / wyładowywania. W jednym możesz rozpocząć nowy proces za pomocą aplikacji konsolowej, aw innym dll możesz po prostu umieścić funkcjonalność, którą ma wykonać usługa.

Powodzenia.

BlackCath
źródło
0

Musisz podzielić funkcjonalność na klasę lub klasy i uruchomić ją za pomocą jednego z dwóch kodów pośredniczących. Kod pośredniczący konsoli lub kod serwisowy.

Jak widać, podczas uruchamiania systemu Windows niezliczone usługi składające się na infrastrukturę nie prezentują (i nie mogą bezpośrednio) przedstawiać użytkownikowi okien konsoli. Usługa musi komunikować się z użytkownikiem w sposób nie graficzny: za pośrednictwem SCM; w dzienniku zdarzeń, do jakiegoś pliku dziennika itp. Usługa będzie musiała również komunikować się z oknami za pośrednictwem SCM, w przeciwnym razie zostanie wyłączona.

Oczywiście byłoby do zaakceptowania posiadanie aplikacji konsolowej, która może komunikować się z usługą, ale usługa musi działać niezależnie, bez konieczności interakcji z GUI.

Konsola może być bardzo przydatna do debugowania zachowania usługi, ale nie powinna być używana w środowisku „produkcyjnym”, które w końcu jest celem tworzenia usługi.

Nie przeczytałem go w pełni, ale ten artykuł wydaje się zmierzać w dobrym kierunku.

Jodrell
źródło
0

Używam klasy usług, która jest zgodna ze standardowym wzorcem zalecanym przez ServiceBasei dołączam pomocników do łatwego debugowania F5. Dzięki temu dane serwisowe są zdefiniowane w ramach usługi, dzięki czemu są one łatwe do znalezienia, a ich żywotność jest łatwa do zarządzania.

Zwykle tworzę aplikację Windows o poniższej strukturze. Nie tworzę aplikacji konsolowej; w ten sposób nie widzę dużego czarnego pudełka na twarzy za każdym razem, gdy uruchamiam aplikację. Pozostaję w debugerze, gdzie jest cała akcja. używamDebug.WriteLine aby wiadomości trafiały do ​​okna wyjściowego, które ładnie się dokuje i pozostaje widoczne po zakończeniu działania aplikacji.

Zwykle nie kłopoczę się dodawaniem kodu debugującego do zatrzymywania; Zamiast tego używam debugera. Jeśli muszę debugować zatrzymanie, tworzę projekt jako aplikację konsolową, dodaję Stopmetodę przekazywania i wywołuję ją po wywołaniu 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
źródło