Pages

Wednesday, June 15, 2016

Create an Easily Debuggable Windows Service

In a previous job, I needed to create Windows services. One thing that had tripped me up in the past was being able to debug the service. I figured someone (or ones) had solved this, so I did a bunch of research, consolidated what I found, documented it, and am finally (three years later?) posting it.

With the setup, below, you’ll have a Windows service that:

  • Runs in debug mode as you’d expect, without having to set unnecessary breakpoints.
  • Can run as a console app, allowing you to simply double-click the exe. This is useful for many things:
    • Output running messages to the console (the same info you’re logging, most likely).
    • Perform one-off runs for things like integration services that run on a schedule.
    • Easily run elsewhere without having to install as a service.

In addition, my steps include Nice-to-Dos like file and service naming, startup type, etc. These are more for me than you, so that I don’t have to look them up again, so think of them as a bonus.

The example below creates a sample FileImport service. Note that Visual Studio Express doesn’t have a Service project, but you can still create a service. (Sadly, I’m not covering that here, but might do so in a future post.)

Create a new Windows Service project.

image

Change the Assembly output type to Console Application (for debugging).  This, in my opinion, is the big deal. I can now write my program just like I would a console application, which is how I want to think of a service.

This is also a good time to change the assembly information (title, company, etc.).

image

Select Servic1.cs. Rename the Service1.cs file/class name, e.g. to FileImport.cs.

imageimage

image

Select FileImport.cs and view the designer (Shift F7), change ServiceName, e.g. FileImport

image

Rick-click design surface and choose Add Installer

image

Open ProjectInstaller.cs designer, select ServiceInstaller1 and edit:

image

  • Description
  • DisplayName, e.g. “Sample File Import”
  • ServiceName. Should be correct from Service1 designer change, otherwise FileImport
  • StartType, e.g. Automatic

image

If desired, in ProjectInstaller designer, select ServiceProcessInstaller and edit

  • Account = User (set to NetworkService or LocalService if desired.)

image

image

Open FileImport.cs code view, create a public member Start() for debugging.

namespace FileImport
{
    public partial class FileImport : ServiceBase
    {
        public FileImport()
        {
            InitializeComponent();
        }

        protected override void OnStart(string[] args)
        {
        }

        protected override void OnStop()
        {
        }

        public void Start(string[] args)
        {
            OnStart(args);
        }

    }
}

Change Program.Main() to this (or similar).

namespace FileImport
{
    static class Program
    {
        /// 
        /// The main entry point for the application.
        /// 
        static void Main()
        {
            ServiceBase[] ServicesToRun;
            ServicesToRun = new ServiceBase[]
            {
                new FileImport()
            };
            // If you don’t want the exe to run as console when double-clicked, then use:
            //   if (System.Diagnostics.Debugger.IsAttached)
            if (Environment.UserInteractive)
            {
                Console.WriteLine("Service running, press  to stop.");
                ((FileImport)ServicesToRun[0]).Start(null);
                Console.ReadLine();
                ((FileImport)ServicesToRun[0]).Stop();
            }
            else
            {
                ServiceBase.Run(ServicesToRun);
            }
        }
    }
}

At this point, you can run the project. It will open a console window and wait.

image

If you install the service, when the service is started it behaves like any other service, i.e. doesn’t display the console window.

Our service isn’t doing anything, so let’s see what the benefit is to creating a service as a console app instead of the default Windows app. We’re not going to do anything fancy like multithreading, I just want to give a flavor. In FileImport.cs, use the following code.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Threading.Tasks;
using System.Timers;

namespace FileImport
{
    public partial class FileImport : ServiceBase
    {
        Timer _timer = new Timer(5000);

        public FileImport()
        {
            InitializeComponent();
        }

        protected override void OnStart(string[] args)
        {
            _timer.Elapsed += OnTimerElapsed;
            _timer.Start();
        }

        private void OnTimerElapsed(object sender, ElapsedEventArgs e)
        {
            Console.WriteLine("The time is--or, to be precise, was: " + DateTime.Now.ToString());
        }

        protected override void OnStop()
        {
            Console.WriteLine("Stopping. Here's one method where you'd handle processes that shouldn't be interrupted, gracefully stop them, then end the service. Press  to finish stopping.");
            _timer.Stop();
            _timer.Dispose();
            //etc.
            Console.ReadLine();
        }

        public void Start(string[] args)
        {
            OnStart(args);
        }

    }
}

Run the program and it will output something like this.

image

Now, if you were to install and run this as a service, you still wouldn’t see anything. But you can imagine adding appending text to a file (for example) in the OnTimerElapsed event.

        private void OnTimerElapsed(object sender, ElapsedEventArgs e)
        {
            string msg = "The time is--or, to be precise, was: " + DateTime.Now.ToString() + "\r\n";
            System.IO.File.AppendAllText(Environment.ExpandEnvironmentVariables("%USERPROFILE%\\TestFileImport.txt"),msg);
            Console.WriteLine(msg);
        }

Of course, I can drop into the debugger as expected.

image

There you go! A simple way to build better Windows services.

References

http://einaregilsson.com/run-windows-service-as-a-console-program/

No comments:

Post a Comment