Debugging Windows Service application with console in C#

An easier way to debug and develop Windows Services

  • Share

Developing Windows Service might a a real headache since you do not have any visual part to see the output. Even if you want to rely on Visual Studio debugger you need to install the service, stop it (if already running), deploy, start the service from Windows MMC and finally hook up Visual Studio to the running service process.

Whooh, a lot of things to be done just to start debugging and you have to do it every time you want to start your debugging session (of course not including install every time obviously).

If you look at the service it is basically not any special type of application. The only big difference is that it does not have any UI and basically you have to rely only on logs. Well because of this you can test your code as any other simply using Console application. Of course after debug, you would have to implement your code from console into an actual Windows Service application. You can always use DEBUG condition for code block, but this does not work if your serivce is running executable compiled in Debug mode.

#if DEBUG
	//Run in console
#else
	//Run as service
#endif
    

Of course it does not make much sense to have live code running compiled in debug mode, but there is also a condition when you wnat to debug release compiled code as Debug and Releaso code might act differently during the runtime.

To avoid this step, the following approach will help you have an application which behaves as a console while debugging and as a service while running out of debugging session. First step is to create a Console application and add an interface which will be used to extend Windows Service class.

    interface IWindowsService
    {
        void StartService(string[] args);
        void StopService();
    }
    

This interface will be used to force implementation of custom methods which will expose Service Stop and Service Start logic since it is not exposed by default in a Windows Service class. To have something to test and get some results, service will simply write to a console which is set to output to a file, since there is no UI for the service application, but to output to default, screen output if not running in debug session.

The key point is the condition which checked whether code is running in a debug session or there is a parameter -c in arguments. The parameter condition is there in case you want to debug the executable outside of a debugger.

Note

Writing output to console is synchronous which means your code will be blocked while writing. To avoid this, use async writing aprocah which is described in the article "Non blocking writing to console in C#"

    partial class WindowsService : ServiceBase, IWindowsService
    {
        private CancellationTokenSource cancellationTokenSource;
        private CancellationToken cancellationToken;
        public WindowsService()
        {
            InitializeComponent();
        }

        protected override void OnStart(string[] args)
        {
            this.StartService(args);
        }

        protected override void OnStop()
        {
            this.StopService();
        }


        public void StartService(string[] args)
        {
            cancellationTokenSource = new CancellationTokenSource();
            cancellationToken = cancellationTokenSource.Token;

            //Set output to file if not in debug
            if (!Debugger.IsAttached && !args.Contains<string>("-c"))
            {
                string filePath = string.Format("log.{0}.txt", DateTime.Now.ToString("yyyyMMdd.HHmmssff"));
                filePath = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), filePath);
                Console.SetOut(new StreamWriter(new FileStream(filePath, FileMode.CreateNew)) { AutoFlush=true});
            }

            Task.Run(() =>
        {
            while (!cancellationToken.IsCancellationRequested)
            {
                Console.WriteLine("Hello from service @ {0}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"));
                Thread.Sleep(2000);
            }
        }, cancellationToken);
        }

        public void StopService()
        {
            cancellationTokenSource.Cancel();
        }
    }

    

Now the main logic is in static method Main in console Programs.cs class file

   class Program
    {
        static void Main(string[] args)
        {
            if (Debugger.IsAttached || args.Contains<string>("-c"))
            {
                //Start console

                IWindowsService service =new WindowsService() as IWindowsService;
                service.StartService(args);

                Console.WriteLine("Service running...");
                Console.ReadLine();

                service.StopService();

                Console.WriteLine("Service stopped, press any key to end...");
                Console.ReadLine();
            }
            else
            {
                //Start servive

                var serviceBase = new ServiceBase[] { new WindowsService() };
                ServiceBase.Run(serviceBase);
            }
        }
    }
    

Basically code will run a service start code if not in a debugging code, otherwise it will just execute service start implementation from the service class. If you install this service using installuntil.exe tool from Visual Studio console and start it from Windows MMC, you will see that output is written in file where service executable is located. If you run the executable from command prompt with parameter -c you will get the console with output on in the console window instead of file.

This way you have single solution which behaves different depending whether it is running in debug session or not.

The whole sample solution is attached, so you can just download it and use it out of the box.

  • Share

Disclaimer

Purpose of the code contained in snippets or available for download in this article is solely for learning and demo purposes. Author will not be held responsible for any failure or damages caused due to any other usage.

Comments for this article

comments powered by Disqus