Aplicación de consola .NET como servicio de Windows

145

Tengo una aplicación de consola y me gustaría ejecutarla como servicio de Windows. VS2010 tiene una plantilla de proyecto que permite adjuntar un proyecto de consola y construir el servicio de Windows. Me gustaría no agregar un proyecto de servicio separado y, si es posible, integrar el código de servicio en la aplicación de consola para mantener la aplicación de consola como un proyecto que podría ejecutarse como aplicación de consola o como servicio de Windows si se ejecuta, por ejemplo, desde la línea de comandos utilizando interruptores.

¿Quizás alguien podría sugerir una biblioteca de clases o un fragmento de código que podría transformar rápida y fácilmente la aplicación de consola de C # en servicio?

Tomás
fuente
¿Por qué no crea un proyecto de servicio temporal y copia los bits que lo convierten en un servicio?
Gabe
44
Puede probar Topshelf topshelf-project.com
Artem Koshelev
Puede probar la técnica descrita aquí: einaregilsson.com/2007/08/15/…
Joe
eh? No estoy seguro. sobre esto.
2
Una alternativa muy simple al estante superior: runasservice.com
Luis Perez

Respuestas:

185

Por lo general, uso la siguiente técnica para ejecutar la misma aplicación que una aplicación de consola o como servicio:

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 es cierto para la aplicación de consola y falso para un servicio. Técnicamente, es posible ejecutar un servicio en modo interactivo para el usuario, por lo que puede verificar un interruptor de línea de comando en su lugar.

VladV
fuente
3
Utiliza la clase ServiceInstaller, consulte msdn.microsoft.com/en-us/library/… .
VladV
2
Eso es esperado: su servicio se ejecutará como un proceso separado (por lo que se mostrará en el administrador de tareas), pero este proceso será controlado por el sistema (por ejemplo, iniciado, detenido, reiniciado de acuerdo con la configuración del servicio).
VladV
2
Si lo ejecuta como una aplicación de consola, no verá un servicio. Todo el propósito de este código es permitirle ejecutarlo como una aplicación de consola o como un servicio. Para ejecutarlo como un servicio, primero debe instalarlo (utilizando la clase ServiceInstaller, consulte el enlace de MSDN arriba, o installuitil.exe), y ejecute el servicio desde el panel de control.
VladV
2
ServiceInstaller es solo una clase de utilidad para tratar con los servicios de Windows (un poco como las utilidades installutil.exe o sc.exe). Puede usarlo para instalar lo que quiera como servicio, al sistema operativo no le importa el tipo de proyecto que use.
VladV
55
Simplemente agregue una referencia en su proyecto a System.ServiceProcess y podrá usar el código anterior
danimal
59

He tenido un gran éxito con TopShelf .

TopShelf es un paquete Nuget diseñado para facilitar la creación de aplicaciones de Windows .NET que pueden ejecutarse como aplicaciones de consola o como Servicios de Windows. Puede conectar rápidamente eventos como los eventos de inicio y detención de su servicio, configurar usando el código, por ejemplo, para establecer la cuenta en la que se ejecuta, configurar las dependencias de otros servicios y configurar cómo se recupera de los errores.

Desde la consola de Package Manager (Nuget):

Paquete superior de instalación

Consulte los ejemplos de código para comenzar.

Ejemplo:

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 también se encarga de la instalación del servicio, lo que puede ahorrar mucho tiempo y elimina el código repetitivo de su solución. Para instalar su .exe como un servicio, simplemente ejecute lo siguiente desde el símbolo del sistema:

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

No necesita conectar un ServiceInstaller y todo eso: TopShelf lo hace todo por usted.

saille
fuente
1
Hola, recibo esto: - "No se pudo instalar el paquete 'Topshelf 4.0.1'. Está intentando instalar este paquete en un proyecto que se dirige a '.NETFramework, Version = v4.5', pero el paquete no contiene ninguno referencias de ensamblaje o archivos de contenido que sean compatibles con ese marco ". ¿Que esta mal aquí?
3
Asegúrese de que se dirige al tiempo de ejecución completo de .NET 4.5.2, no al perfil del Cliente.
saille
por favor, ¿puede arrojar más luz sobre myservice.exe y desde qué directorio abrirá el símbolo del sistema?
Izuagbala
1
@Izuagbala myservice.exe es la aplicación de consola que ha creado, con TopShelf arrancado como se muestra en el ejemplo de código.
saille
¿Se puede ejecutar myservice.exe como consola después de instalarlo como servicio? La documentación no es clara: "Una vez que se crea la aplicación de consola, el desarrollador crea una única clase de servicio" docs.topshelf-project.com/en/latest/overview/…
Michael Freidgeim
27

Así que aquí está el tutorial completo:

  1. Crear un nuevo proyecto de aplicación de consola (por ejemplo, MyService)
  2. Agregue dos referencias de biblioteca: System.ServiceProcess y System.Configuration.Install
  3. Agregue los tres archivos impresos a continuación
  4. Compile el proyecto y ejecute "InstallUtil.exe c: \ path \ to \ MyService.exe"
  5. Ahora debería ver MyService en la lista de servicios (ejecute services.msc)

* InstallUtil.exe generalmente se puede encontrar aquí: 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
fuente
1
Si está compilando su proyecto para 64 bits, debe usar InstallUtil.exe para 64 bits, que se puede encontrar aquí: C: \ windows \ Microsoft.NET \ Framework64 \ ... La versión para 32 bits (C: \ windows \ Microsoft.NET \ Framework) te lanzará una BadImageFormatException ...
snytek
Esto funciona muy bien, tenga en cuenta que, como dice @snytek, si está utilizando la base 64, asegúrese de usar el directorio correcto. Además, si hace lo mismo que yo y olvida cambiar el nombre del servicio a otro que no sea "MyService", asegúrese de desinstalar el servicio antes de realizar los cambios en el código.
dmoore1181
3

Escucho su punto de querer que un ensamblaje detenga el código repetido pero, sería más simple y reduciría la repetición del código y facilitaría la reutilización de su código de otras maneras en el futuro si ...... lo divide en 3 ensamblajes.

  1. Una asamblea de biblioteca que hace todo el trabajo. Luego tenga dos proyectos muy muy delgados / simples:
  2. uno que es la línea de comando
  3. uno que es el servicio de windows.
JonAlb
fuente
1
Así es como lo he hecho durante años - el servicio más o menos tiene Start()y Stop()métodos y la aplicación de consola tiene un lazo. A falta de usar un marco como TopShelf , esta es la mejor opción
básico el
de acuerdo con esa respuesta más. El uso de herramientas de fiesta 3D para soluciones simples hace que los mantenimientos futuros sean innecesarios complejos
tatigo
3

Aquí hay una forma más nueva de cómo convertir una aplicación de consola en un servicio de Windows como un servicio de trabajo basado en el último .Net Core 3.1 .

Si crea un Servicio de trabajo de Visual Studio 2019, le dará casi todo lo que necesita para crear un Servicio de Windows de forma inmediata, que también es lo que necesita cambiar a la aplicación de consola para convertirlo en un Servicio de Windows.

Estos son los cambios que debes hacer:

Instale los siguientes paquetes NuGet

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

Cambie Program.cs para tener una implementación como la siguiente:

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

y agregue Worker.cs donde colocará el código que ejecutarán las operaciones de servicio:

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

Cuando todo esté listo y la aplicación se haya creado correctamente, puede usar sc.exe para instalar su aplicación de consola exe como un Servicio de Windows con el siguiente comando:

sc.exe create DemoService binpath= "path/to/your/file.exe"
yo soloAndrew
fuente
2

Puedes usar

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

Y aparecerá en la lista de servicios. No sé si eso funciona correctamente. Un servicio generalmente tiene que escuchar varios eventos.

Sin embargo, hay varios contenedores de servicios que pueden ejecutar cualquier aplicación como un servicio real. Por ejemplo, Microsofts SrvAny del Kit de recursos Win2003

Darcara
fuente
Como usted dice, el servicio exe necesitará comunicarse con Windows. +1 para el enlace a SrvAny
Jodrell
55
Consideraría este enfoque inseguro. Windows tiene bibliotecas y utilidades especiales para administrar los servicios, y es más probable que funcionen de manera consistente en diferentes versiones y entornos de SO. Para la aplicación .NET, es bastante fácil crear un instalador MSI en VS. También es posible realizar la instalación mediante programación utilizando el método ManagedInstallerClass.InstallHelper.
VladV
1
No necesita instaladores ni nada: solo use esta línea de comando: sc create MyServiceName binPath = "c: \ path \ to \ service \ file \ exe"
JDC
2

En primer lugar, inserto la solución de la aplicación de consola en la solución de servicio de Windows y hago referencia a ella.

Luego hago pública la clase de programa de la aplicación de consola

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

Luego creo dos funciones dentro de la aplicación de consola

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

Luego, dentro del propio servicio de Windows, ejemplifico el Programa y llamo a las funciones Start y Stop agregadas dentro de OnStart y OnStop. Vea abajo

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

Este enfoque también se puede utilizar para una aplicación de Windows / servicio híbrido de Windows

Patrick Reynolds
fuente
esto es básicamente lo que JonAlb ha dicho en la respuesta anterior, pero gracias por el ejemplo de código
tatigo el
0

Tal vez debería definir lo que necesita, por lo que sé, no puede ejecutar su aplicación como Consola o Servicio con línea de comandos, al mismo tiempo. Recuerde que el servicio está instalado y debe iniciarlo en el Administrador de servicios, puede crear una nueva aplicación que inicie el servicio o inicie un nuevo proceso que ejecute su aplicación de consola. Pero como escribiste

"mantener la aplicación de consola como un proyecto"

Una vez, estaba en su posición, convirtiendo una aplicación de consola en un servicio. Primero necesita la plantilla, en caso de que esté trabajando con VS Express Edition. Aquí hay un enlace donde puede dar sus primeros pasos: C # Servicio de Windows , esto fue muy útil para mí. Luego, usando esa plantilla, agregue su código a los eventos deseados del servicio.

Para mejorar su servicio, hay otra cosa que puede hacer, pero esto no es rápido y / o fácil, es usar dominios de aplicación y crear dlls para cargar / descargar. En uno puede comenzar un nuevo proceso con la aplicación de consola, y en otro dll simplemente puede poner la funcionalidad que el servicio tiene que hacer.

Buena suerte.

BlackCath
fuente
0

Debe separar la funcionalidad en una clase o clases y ejecutarla a través de uno de los dos apéndices. El trozo de consola o el trozo de servicio.

Como es evidente, cuando se ejecutan ventanas, los innumerables servicios que conforman la infraestructura no presentan (y no pueden) presentar directamente las ventanas de la consola al usuario. El servicio necesita comunicarse con el usuario de una manera no gráfica: a través del SCM; en el registro de eventos, en algún archivo de registro, etc. El servicio también deberá comunicarse con Windows a través del SCM; de lo contrario, se cerrará.

Obviamente, sería aceptable tener alguna aplicación de consola que pueda comunicarse con el servicio, pero el servicio debe ejecutarse de forma independiente sin un requisito para la interacción de la GUI.

El código auxiliar de la consola puede ser muy útil para depurar el comportamiento del servicio, pero no debe usarse en un entorno "productivo" que, después de todo, es el propósito de crear un servicio.

No lo he leído completamente, pero este artículo parece pintarse en la dirección correcta.

Jodrell
fuente
0

Utilizo una clase de servicio que sigue el patrón estándar prescrito por ServiceBase, y agrego ayudantes para facilitar la depuración de F5. Esto mantiene los datos del servicio definidos dentro del servicio, haciéndolos fáciles de encontrar y su vida útil fácil de administrar.

Normalmente creo una aplicación de Windows con la estructura a continuación. No creo una aplicación de consola; de esa manera no aparece una gran caja negra en mi cara cada vez que ejecuto la aplicación. Me quedo en el depurador donde está toda la acción. Lo uso Debug.WriteLinepara que los mensajes vayan a la ventana de salida, que se acopla bien y permanece visible después de que la aplicación finaliza.

Por lo general, no me molesto en agregar código de depuración para detener; Solo uso el depurador en su lugar. Si necesito depurar la detención, hago del proyecto una aplicación de consola, agrego un Stopmétodo de reenvío y lo llamo después de una llamada 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);
}
Edward Brey
fuente