Run a Windows Service from Visual Studio

This is part of a series on creating and installing Windows services.

  1. How to Create a Windows Service
  2. How to Install a Windows Service
  3. How to Run a Windows Service from Visual Studio
  4. How to Avoid Windows Service Start-up Timeouts
  5. How to Make a Windows Service Install Itself
  6. How to Handle Windows Service Errors
  7. How to Build a Windows Service Framework

So far, we have learned how to create a basic shell for a Windows service and how to install it using both sc and InstallUtil. Currently, the code for our simple demo service looks like this:

class Program
{
    static void Main(string[] args)
    {
        ServiceBase.Run(new MyService());
    }
}

class MyService : ServiceBase
{
    public const string Name = "DemoService";
    public MyService()
    {
        ServiceName = Name;
    }
}

[RunInstaller(true)]
public class MyInstaller : Installer
{
    public MyInstaller()
    {
        Installers.Add(new ServiceProcessInstaller
            {
                Account = ServiceAccount.LocalSystem
            });
        Installers.Add(new ServiceInstaller
            {
                ServiceName = MyService.Name,
                DisplayName = MyService.Name,
                Description = "This is a demo service"
            });
    }
}

As we saw in our first episode, if we simply run this application from Visual Studio (perhaps via the F5 key), we are met with this error message informing us that we cannot run the service from the command line or debugger:

NoServicesFromConsole

If we want to be able to efficiently develop our service from Visual Studio, it sure would be nice if we could actually run (and/or debug) it from Visual Studio. To begin, we need to abstract MyService away from the fact that is running via an implementation of ServiceBase. To do so, we will create a new reusable implementation of ServiceBase, called BasicService:

public interface IService : IDisposable
{
    void Start();
}

public class BasicService : ServiceBase
{
    private readonly IService _service;

    public BasicService(IService service, string name)
    {
        _service = service;
        ServiceName = name;
    }

    protected override void OnStart(string[] args)
    {
        _service.Start();
    }

    protected override void OnStop()
    {
        _service.Dispose();
    }
}

Note that BasicService overrides the two critical methods on ServiceBase, OnStart and OnStop, which are called when the service is commanded to start and stop by the service control manager. BasicService delegates those calls to a collaborating interface, IService. In order to convert our existing service to use this new infrastructure, we must change MyService to implement IService, and slightly modify our Main method:

class Program
{
    public const string Name = "DemoService";

    static void Main(string[] args)
    {
        ServiceBase.Run(new BasicService(new MyService(), Name));
    }
}

class MyService : IService
{
    public void Start(){}
    public void Dispose(){}
}

[RunInstaller(true)]
public class MyInstaller : Installer
{
    public MyInstaller()
    {
        Installers.Add(new ServiceProcessInstaller
            {
                Account = ServiceAccount.LocalSystem
            });
        Installers.Add(new ServiceInstaller
            {
                ServiceName = Program.Name,
                DisplayName = Program.Name,
                Description = "This is a demo service"
            });
    }
}

Now that the “complex business logic” of our application (yes, I know it still doesn’t do anything) is abstracted away from the fact that it will run via a service, we are free to run it another way. Utilizing Environment.UserInteractive, we can modify the main method to turn our application into a hybrid console/service application:

class Program
{
    public const string Name = "DemoService";

    static void Main(string[] args)
    {
        if (Environment.UserInteractive)
        {
            using (var service = new MyService())
            {
                service.Start();
                Console.WriteLine("Running {0}, press any key to stop", Name);
                Console.ReadKey();
            }
        }
        else
        {
            ServiceBase.Run(new BasicService(new MyService(), Name));
        }
    }
}

Running the application no longer throws up an error!

runAsConsoleInteractive

Since I did promise that we would have the service actually do something this time, let’s build out MyService a bit:

class MyService : IService
{
    private bool _stopped;
    
    public void Start()
    {
        ThreadPool.QueueUserWorkItem(
            o =>
            {
                while (!_stopped)
                {
                    Console.WriteLine("Still here!");
                    Thread.Sleep(1000);
                }
            });
    }

    public void Dispose()
    {
        _stopped = true;
    }
}

The service will simply write a message to the console every second until we stop it. And because of our changes today, we can easily verify that by hitting F5:

serviceDoesSomethingConsole

Note that it is also possible to run the service by calling the executable from an interactive command line or by double-clicking it in Windows Explorer. These approaches also trigger the “run as a console app” branch in our main method. With just a little bit of additional code, we have made it much easier to develop and debug our service.

  1. Hi,
    Thanks for the article it helped a lot and I created windows. I have added installer and it installs without error. Problem is starting the service it gives error not able to respond in timely fashion. Though on logs and task manager I can see that service is working perfectly.
    From directly clicking exe and I have added few commandpromt options as well passing arguments those all things are working.
    Just was thinking that I have changed it to run from visual studio is this the reason for the error.. can you please help?

Leave a Reply

Your email address will not be published. Required fields are marked *