Aplicativo de console .NET como serviço do Windows

145

Eu tenho um aplicativo de console e gostaria de executá-lo como serviço do Windows. O VS2010 possui um modelo de projeto que permite anexar o projeto do console e criar o serviço do Windows. Gostaria de não adicionar um projeto de serviço separado e, se possível, integrar o código de serviço no aplicativo de console para manter o aplicativo de console como um projeto que poderia ser executado como aplicativo de console ou como serviço do Windows, se executado, por exemplo, na linha de comando usando comutadores.

Talvez alguém possa sugerir uma biblioteca de classes ou um trecho de código que possa transformar rápida e facilmente o aplicativo de console c # em serviço?

Tomas
fonte
Por que você simplesmente não cria um projeto de serviço temporário e copia os bits que o tornam um serviço?
Gabe
4
Você poderia tentar Topshelf topshelf-project.com
Artem Koshelev 14/10
Você pode tentar a técnica descrita aqui: einaregilsson.com/2007/08/15/…
Joe
Hã? Não tenho certeza. sobre isso.
2
Uma alternativa muito simples prateleira superior: runasservice.com
Luis Perez

Respostas:

185

Normalmente, uso o seguinte techinque para executar o mesmo aplicativo que um aplicativo de console ou um serviço:

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.UserInteractivenormalmente é verdadeiro para o aplicativo de console e falso para um serviço. Tecnicamente, é possível executar um serviço no modo interativo do usuário, para que você possa verificar uma opção de linha de comando.

VladV
fonte
3
Você usa a classe ServiceInstaller, consulte msdn.microsoft.com/en-us/library/… .
VladV
2
Isso é esperado - seu serviço seria executado como um processo separado (portanto, seria mostrado no gerenciador de tarefas), mas esse processo seria controlado pelo sistema (por exemplo, iniciado, parado, reiniciado de acordo com as configurações do serviço).
VladV
2
Se você executá-lo como um aplicativo de console, não verá um serviço. O objetivo deste código é permitir que você o execute como um aplicativo de console ou como um serviço. Para executar como um serviço, você precisa instalá-lo primeiro (usando a classe ServiceInstaller - consulte o link do MSDN acima - ou installuitil.exe) e executar o serviço no painel de controle.
VladV
2
ServiceInstaller é apenas uma classe de utilitário para lidar com os serviços do Windows (um pouco como os utilitários installutil.exe ou sc.exe). Você pode usá-lo para instalar o que quiser como serviço, o sistema operacional não se importa com o tipo de projeto usado.
VladV
5
Basta adicionar uma referência no seu projeto ao System.ServiceProcess e você poderá usar o código acima
danimal
59

Eu tive um grande sucesso com o TopShelf .

O TopShelf é um pacote Nuget projetado para facilitar a criação de aplicativos .NET Windows que podem ser executados como aplicativos de console ou como Serviços do Windows. Você pode conectar rapidamente eventos como o serviço Iniciar e parar eventos, configurar usando o código, por exemplo, para definir a conta em que é executado, configurar dependências em outros serviços e configurar como ele se recupera de erros.

No console do gerenciador de pacotes (Nuget):

Prateleira superior do pacote de instalação

Consulte os exemplos de código para começar.

Exemplo:

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

O TopShelf também cuida da instalação do serviço, o que pode economizar muito tempo e remove o código padrão da sua solução. Para instalar o .exe como um serviço, basta executar o seguinte no prompt de comando:

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

Você não precisa conectar um ServiceInstaller e tudo mais - o TopShelf faz tudo por você.

veleiro
fonte
1
Olá, estou recebendo o seguinte: - "Não foi possível instalar o pacote 'Topshelf 4.0.1'. Você está tentando instalar este pacote em um projeto que tem como destino '.NETFramework, Version = v4.5', mas o pacote não contém nenhum referências de montagem ou arquivos de conteúdo compatíveis com essa estrutura ". o que há de errado aqui?
3
Verifique se você está direcionando o tempo de execução completo do .NET 4.5.2, não o perfil do Cliente.
Saille
por favor, você pode esclarecer mais sobre o myservice.exe e de qual diretório você abrirá o prompt de comando
Izuagbala
1
@Izuagbala myservice.exe é o aplicativo de console que você criou, com o TopShelf inicializado como mostrado no exemplo de código.
saille
O myservice.exe pode ser executado como console depois de instalado como um serviço ?. A documentação não está clara: "Depois que o aplicativo do console é criado, o desenvolvedor cria uma única classe de serviço" docs.topshelf-project.com/en/latest/overview/…
Michael Freidgeim
27

Então, aqui está o passo a passo completo:

  1. Crie um novo projeto de aplicativo de console (por exemplo, MyService)
  2. Adicione duas referências de biblioteca: System.ServiceProcess e System.Configuration.Install
  3. Adicione os três arquivos impressos abaixo
  4. Crie o projeto e execute "InstallUtil.exe c: \ path \ to \ MyService.exe"
  5. Agora você deve ver o MyService na lista de serviços (execute services.msc)

* O InstallUtil.exe geralmente pode ser encontrado aqui: 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
fonte
1
Se você estiver compilando seu projeto para 64 bits, use o InstallUtil.exe para 64 bits, que pode ser encontrado aqui: C: \ windows \ Microsoft.NET \ Framework64 \ ... A versão para 32 bits (C: \ windows \ Microsoft.NET \ Framework) irá lançar um BadImageFormatException em você ...
snytek
Isso funciona muito bem, observe que, como o @snytek diz, se você estiver usando a base 64, certifique-se de usar o diretório correto. Além disso, se você fizer o mesmo que eu e esquecer de renomear o serviço para algo diferente de "MyService", desinstale o serviço antes de fazer as alterações no código.
dmoore1181
3

Eu ouço o seu argumento de querer que um assembly pare o código repetido, mas seria mais simples e reduziria a repetição de código e tornaria mais fácil reutilizar seu código de outras maneiras no futuro se ...... você o dividisse em 3 assemblies.

  1. Um assembly de biblioteca que faz todo o trabalho. Então, tenha dois projetos muito muito pequenos / simples:
  2. aquele que é a linha de comando
  3. um que é o serviço windows.
JonAlb
fonte
1
Isto é como eu tenho feito isso há anos - o serviço muito bonito tem Start()e Stop()métodos ea aplicação de console tem um loop. Exceto pelo uso de uma estrutura como o TopShelf , esta é a melhor opção
Básico
concordo com essa resposta mais. usando ferramentas do partido 3D para soluções simples torna o futuro manutenções desnecessárias complexo
tatigo
3

Aqui está uma maneira mais recente de como transformar um aplicativo de console em um serviço do Windows como um serviço de trabalho com base no mais recente .Net Core 3.1 .

Se você criar um Serviço de Trabalho do Visual Studio 2019, ele fornecerá quase tudo o que você precisa para criar um Serviço do Windows pronto para o uso, que também é o que você precisa alterar para o aplicativo do console para convertê-lo em um Serviço do Windows.

Aqui estão as alterações que você precisa fazer:

Instale os seguintes pacotes NuGet

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

Altere Program.cs para ter uma implementação como abaixo:

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 adicione Worker.cs onde você colocará o código que será executado pelas operações de serviço:

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 tudo estiver pronto e o aplicativo tiver sido construído com êxito, você poderá usar o sc.exe para instalar o aplicativo do console exe como um Serviço do Windows com o seguinte comando:

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

Você pode usar

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

E aparecerá na lista de serviços. Eu não sei, se isso funciona corretamente embora. Um serviço geralmente precisa ouvir vários eventos.

Existem vários wrappers de serviço que podem executar qualquer aplicativo como um serviço real. Por exemplo, Microsoft Microsofts SrvAny do Win2003 Resource Kit

Darcara
fonte
Como você diz, o serviço exe precisará se comunicar com o Windows. +1 para link para SrvAny
Jodrell
5
Eu consideraria esta abordagem insegura. O Windows possui bibliotecas e utilitários especiais para gerenciar serviços, e é mais provável que funcionem consistentemente em diferentes versões e ambientes do sistema operacional. Para o aplicativo .NET, é muito fácil criar um instalador MSI no VS. Também é possível executar a instalação progressivamente usando o método ManagedInstallerClass.InstallHelper.
VladV
1
Não há necessidade de instaladores e outras coisas: basta usar esta linha de comando: sc criar MyServiceName binPath = "c: \ path \ to \ Service \ arquivo \ exe"
JDC
2

Primeiramente, incorporei a solução de aplicativo de console à solução de serviço do Windows e a referencio.

Torno pública a classe de programa do aplicativo de console

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

Em seguida, crio duas funções no aplicativo de 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
    }

Então, dentro do próprio serviço do Windows, instancio o programa e chamo as funções Start e Stop adicionadas no OnStart e OnStop. Ver abaixo

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

Essa abordagem também pode ser usada para um aplicativo Windows / serviço híbrido do Windows

Patrick Reynolds
fonte
isso é basicamente o que JonAlb ter dito na resposta anterior, mas graças para o exemplo de código
tatigo
0

Talvez você deva definir o que precisa, tanto quanto eu sei, não pode executar seu aplicativo como Console ou Serviço com linha de comando ao mesmo tempo. Lembre-se de que o serviço está instalado e você deve iniciá-lo no Services Manager. Você pode criar um novo aplicativo que inicie o serviço ou inicie um novo processo executando o aplicativo do console. Mas como você escreveu

"mantenha o aplicativo do console como um projeto"

Uma vez, eu estava na sua posição, transformando um aplicativo de console em um serviço. Primeiro você precisa do modelo, caso esteja trabalhando com o VS Express Edition. Aqui está um link onde você pode dar seus primeiros passos: Serviço do Windows em C # , isso foi muito útil para mim. Em seguida, usando esse modelo, adicione seu código aos eventos desejados do serviço.

Para melhorar seu serviço, há outra coisa que você pode fazer, mas isso não é rápido e / ou fácil, é usar domínios de aplicativos e criar dlls para carregar / descarregar. Em um, você pode iniciar um novo processo com o aplicativo do console e, em outra DLL, basta colocar a funcionalidade que o serviço precisa fazer.

Boa sorte.

BlackCath
fonte
0

Você precisa separar a funcionalidade em uma classe ou classes e iniciá-la por meio de um dos dois stubs. O stub do console ou stub de serviço.

Como é óbvio, ao executar janelas, os inúmeros serviços que compõem a infraestrutura não apresentam (e não podem diretamente) apresentar janelas de console ao usuário. O serviço precisa se comunicar com o usuário de forma não gráfica: via SCM; no log de eventos, para algum arquivo de log etc. O serviço também precisará se comunicar com o Windows via SCM, caso contrário, será desativado.

Obviamente, seria aceitável ter algum aplicativo de console que possa se comunicar com o serviço, mas o serviço precisa ser executado independentemente, sem a necessidade de interação com a GUI.

O stub do console pode ser muito útil para o comportamento do serviço de depuração, mas não deve ser usado em um ambiente "produzido" que, afinal, é o objetivo de criar um serviço.

Eu não li completamente, mas este artigo parece estar na direção certa.

Jodrell
fonte
0

Eu uso uma classe de serviço que segue o padrão padrão prescrito por ServiceBase, e adiro os auxiliares para facilitar a depuração F5. Isso mantém os dados de serviço definidos dentro do serviço, facilitando a localização e o gerenciamento de suas vidas úteis.

Normalmente, crio um aplicativo do Windows com a estrutura abaixo. Eu não crio um aplicativo de console; Dessa forma, não recebo uma grande caixa preta aparecendo na minha cara toda vez que executo o aplicativo. Eu fico no depurador onde está toda a ação. Eu uso Debug.WriteLinepara que as mensagens vão para a janela de saída, que encaixa perfeitamente e permanece visível após o término do aplicativo.

Eu geralmente não me incomodo em adicionar código de depuração para parar; Eu apenas uso o depurador. Se eu precisar depurar a parada, torno o projeto um aplicativo de console, adiciono um Stopmétodo de encaminhador e o chamo após uma chamada para 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
fonte