.NET console application as Windows service

160,622

Solution 1

I usually use the following techinque to run the same app as a console application or as a service:

using System.ServiceProcess

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 is normally true for console app and false for a service. Techically, it is possible to run a service in user-interactive mode, so you could check a command-line switch instead.

Solution 2

I've had great success with TopShelf.

TopShelf is a Nuget package designed to make it easy to create .NET Windows apps that can run as console apps or as Windows Services. You can quickly hook up events such as your service Start and Stop events, configure using code e.g. to set the account it runs as, configure dependencies on other services, and configure how it recovers from errors.

From the Package Manager Console (Nuget):

Install-Package Topshelf

Refer to the code samples to get started.

Example:

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 also takes care of service installation, which can save a lot of time and removes boilerplate code from your solution. To install your .exe as a service you just execute the following from the command prompt:

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

You don't need to hook up a ServiceInstaller and all that - TopShelf does it all for you.

Solution 3

So here's the complete walkthrough:

  1. Create new Console Application project (e.g. MyService)
  2. Add two library references: System.ServiceProcess and System.Configuration.Install
  3. Add the three files printed below
  4. Build the project and run "InstallUtil.exe c:\path\to\MyService.exe"
  5. Now you should see MyService on the service list (run services.msc)

*InstallUtil.exe can be usually found here: 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);
        }
    }
}

Solution 4

Here is a newer way of how to turn a Console Application to a Windows Service as a Worker Service based on the latest .NET 6.

In Visual Studio 2022 you have out of the box Worker Service as a project template.

This gives you a main method and a Worker.cs on which you need a few more lines

Worker.cs on which I've added the StartAsync and StopAsync overrides to chose what my service does at start/stop.

namespace WorkerService
{
    public class Worker : BackgroundService
    {
        private readonly ILogger<Worker> _logger;

        public Worker(ILogger<Worker> logger)
        {
            _logger = logger;
        }

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

and Program.cs on which you will need to add .UseWindowsService()

using WorkerService;


IHost host = Host.CreateDefaultBuilder(args)
    .ConfigureServices(services =>
    {
        services.AddHostedService<Worker>();
    })
    .UseWindowsService()
    .Build();

await host.RunAsync();

And you will need to install the following NuGet for this method

Install-Package Microsoft.Extensions.Hosting.WindowsServices

Old answer -> .NET Core 3.1

If you create a Worker Service from Visual Studio 2019 it will give you almost everything you need for creating a Windows Service out of the box, which is also what you need to change to the console application in order to convert it to a Windows Service.

Here are the changes you need to do:

Install the following NuGet packages

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

Change Program.cs to have an implementation like below:

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

and add Worker.cs where you will put the code which will be run by the service operations:

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

Installing the app as a Windows Service

When everything is ready, and the application has built successfully, you can use sc.exe to install your console application exe as a Windows Service with the following command:

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

Solution 5

Firstly I embed the console application solution into the windows service solution and reference it.

Then I make the console application Program class public

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

I then create two functions within the console application

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

Then within the windows service itself I instantiate the Program and call the Start and Stop functions added within the OnStart and OnStop. See below

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

This approach can also be used for a windows application / windows service hybrid

Share:
160,622

Related videos on Youtube

Tomas
Author by

Tomas

Updated on July 08, 2022

Comments

  • Tomas
    Tomas almost 2 years

    I have console application and would like to run it as Windows service. VS2010 has project template which allow to attach console project and build Windows service. I would like to not add separated service project and if possible integrate service code into console application to keep console application as one project which could run as console application or as windows service if run for example from command line using switches.

    Maybe someone could suggest class library or code snippet which could quickly and easily transform c# console application to service?

    • Gabe
      Gabe over 12 years
      Why don't you just create a temporary service project and copy over the bits that make it a service?
    • Artem Koshelev
      Artem Koshelev over 12 years
      You could try Topshelf topshelf-project.com
    • Joe
      Joe over 12 years
      You could try the technique described here: einaregilsson.com/2007/08/15/…
    • Admin
      Admin about 11 years
      huh? I'm not sure. about this.
    • Luis Perez
      Luis Perez over 10 years
      A very simple top shelf alternative: runasservice.com
    • Fandango68
      Fandango68 about 6 years
      I am after how to use TopShelf on an existing Windows Service (.Net) project that is not a Console app. Must I convert my project to a Console app?
  • Jodrell
    Jodrell over 12 years
    As you say, the service exe will need to communicate with windows. +1 for link to SrvAny
  • VladV
    VladV over 12 years
    I'd consider this approach unsafe. Windows has special libraries and utilities to manage services, and they are more likely to work consistently in different OS versions and environments. For .NET app it is quite easy to create an MSI installer in VS. It is also posisble to perform installation progrmmatically using ManagedInstallerClass.InstallHelper method.
  • Basic
    Basic over 10 years
    This is how I've done it for years - the Service pretty much has Start() and Stop() methods and the console app has a loop. Short of using a framework like TopShelf, this is the best option
  • Oliver Weichhold
    Oliver Weichhold almost 10 years
    What I dont understand is why you've implemented the Environment.UserInteractive in Main() since one would assume that that method won't be invoked anyone if running as a service.
  • VladV
    VladV almost 10 years
    Main() is actually invoked. The system starts a process and expects it to initiate the service (e.g. call ServiceBase.Run).
  • Karlth
    Karlth over 9 years
    How do you install the service?
  • VladV
    VladV over 9 years
    You use ServiceInstaller class, see msdn.microsoft.com/en-us/library/….
  • tatigo
    tatigo over 8 years
    agree with that answer the most. using 3d party tools for simple solutions makes the future maintenances unnecessary complex
  • tatigo
    tatigo over 8 years
    this is basically what JonAlb have said in the prev answer, but thanks for the code example
  • Admin
    Admin almost 8 years
    Hi, i am getting this :- "Could not install package 'Topshelf 4.0.1'. You are trying to install this package into a project that targets '.NETFramework,Version=v4.5', but the package does not contain any assembly references or content files that are compatible with that framework." what is wrong here?
  • VladV
    VladV almost 8 years
    That's expected - your service would run as a separate process (so it would be shown in the task manager), but this process would be controlled by the system (e.g. started, stopped, restarted according to the service settings).
  • Admin
    Admin almost 8 years
    Hi @VladV - that was a typing mistake, actually i can't see any service name "Myservice" in the task manager. I have created a new console project and copied you code and then ran it.
  • VladV
    VladV almost 8 years
    If you run it as a console app, you won't see a service. The whole purpose of this code is to enable you to run it either as a console app, or as a service. To run as a service you need to install it first (using ServiceInstaller class - see MSDN link above - or installuitil.exe), and the run the service from the control panel.
  • Raulp
    Raulp almost 8 years
    ServiceInstaller class will be used to install the services Project I think and not the non Service Projects like above.!
  • VladV
    VladV almost 8 years
    ServiceInstaller is just a utility class to deal with Windows services (a little bit like installutil.exe or sc.exe utilities). You could use it to install whatever you want as a service, the OS doesn't care about the project type you use.
  • Rama
    Rama almost 8 years
    Make sure you are targetting the full .NET 4.5.2 runtime, not the Client profile.
  • JDC
    JDC almost 7 years
    No need for installers and stuff: just use this command line: sc create MyServiceName binPath= "c:\path\to\service\file\exe"
  • snytek
    snytek over 6 years
    If you are compiling your project for 64 bit you have to use the InstallUtil.exe for 64 bit which can be found here: C:\windows\Microsoft.NET\Framework64\... The version for 32 bit (C:\windows\Microsoft.NET\Framework) will throw a BadImageFormatException at you...
  • Alex 75
    Alex 75 over 6 years
    It will be useful to add the info about which dll and also the using it is required in order to use ServiceBase.
  • Izuagbala
    Izuagbala about 6 years
    please can you throw more light on myservice.exe and from which directory are you going to open the command prompt
  • Rama
    Rama about 6 years
    @Izuagbala myservice.exe is the console application that you have created, with TopShelf bootstrapped into it as shown in the code sample.
  • danimal
    danimal over 5 years
    Just add a reference in your project to System.ServiceProcess and you'll be able to use the code above
  • dmoore1181
    dmoore1181 over 5 years
    This works very well, note that as @snytek says, if you are using base 64, make sure that you use the correct directory. Also, if you happen to do the same as me and forget to rename the service to something other than "MyService", make sure that you uninstall the service before making the changes to the code.
  • Michael Freidgeim
    Michael Freidgeim about 5 years
    Can myservice.exe be run as console after it is installed as a service?. Documentation is not clear: "Once the console application is created, the developer creates a single service class " docs.topshelf-project.com/en/latest/overview/…
  • NibblyPig
    NibblyPig almost 3 years
    You may have to change the output type to Console Application or you'll get an error trying to do Console.ReadKey, if you created a windows service first and tried to convert to console app rather than the other way around.
  • Ak777
    Ak777 over 2 years
    This should have been the more latest answer and be considered with latest .netcore release.
  • meJustAndrew
    meJustAndrew about 2 years
    hey @Ak777 you are right. It took me some time to update it to use .NET 6, and the .NET Core version I think can be removed after it goes out of support.