Skip to main content

Debugging .NET Windows Service apps more easily

Here at work, I keep seeing a couple of my fellow developers struggle to debug their Windows Service apps that they're writing.  What they have to do is remove the service, install the new version of the service, start the service, then attach the debugger.

If there's an error when the service first starts up, the workaround is to introduce a Thread.Sleep before it actually does anything, giving you enough time to attach the debugger.

By default, you can't directly run the service from within Visual Studio. If you try, you get the following message:



If you look at the code that actually starts the service, you'll see that it's not that different from a console app.

By default, it looks something like this:

    internal static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        private static void Main()
        {
            ServiceBase[] ServicesToRun;
            ServicesToRun = new ServiceBase[] { new Service1() };
            ServiceBase.Run(ServicesToRun);
        }
    }
And the Service1 class looks like this:

    public partial class Service1 : ServiceBase
    {
        public Service1()
        {
            this.InitializeComponent();
        }
 
        protected override void OnStart(string[] args)
        {
            Console.WriteLine("Hello, world!");
        }
 
        protected override void OnStop()
        {
        }
    }
The code that runs when the service Starts happens in OnStart() .  There are other events that you can override, such as OnPowerEvent, OnSessionChange, OnShutdown, but for the most part, you'd primarily be concerned about OnStart.

So how can we get a Windows Service to run like a console app?  Pretty simply actually. I'm just going to post the solution here and walk you through it:

    internal static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        private static void Main(string[] args)
        {
            var service = new Service1();
            if (Environment.UserInteractive)
            {
                service.Start(args);
                Thread.Sleep(Timeout.Infinite);
            }
            else
            {
                var servicesToRun = new ServiceBase[] { service };
                ServiceBase.Run(servicesToRun);
            }
        }
    }


First, you should change the signature of Main() to include an array of arguments.
  private static void Main(string[] args)
Next, we'll want to create a new instance of Service1 and keep that in a variable.
var service = new Service1();
Now comes the magic part.  We want to be able to check whether the app is being ran as an application, or as a service.  How can we determine that?  Simple:
if (Environment.UserInteractive)
The above code checks if the application is being ran by the user, or as a service.  If it's being ran by the user, then we want to treat it like a console application, otherwise, run it as a service.  This gives us the flexibility of being able to run it in two different modes.

Next, you'll see me calling a method on Service1 called "Start" instead of "OnStart".  The problem is that OnStart is protected, which means we can only access that method from derived classes, and since Program is not deriving Service1, we can't access that method.  The simplest way to be able to call the "OnStart" method would be to just add a public method to Service1 that will do it for us:

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

And now you can run and debug your service from Visual Studio without having to go through the trouble of installing it.  Once you feel that you have it working, you can then install it by creating a Installer Project, or manually installing it using the "installutil".





Comments

Popular posts from this blog

Add business days with moment.js

Date . prototype . addBusinessDays = function ( businessDays : number ) { // 5 business days in a week const weeks = Math . floor ( businessDays / 5) ; // Convert business days (5 days in a week) to regular days (7 days in a week) const days = weeks * 7 ; const date = moment ( this ) ; // Convert the incoming date value to a moment value. const newDate = moment ( this ) . add ( days + ( businessDays % 5) , 'days' ) ; if ( newDate . day () < date . day () || newDate . day () % 6 === 0) { // Add 2 more days if we land on a weekend, or we went through a weekend. newDate . add (2 , 'days' ) ; // .add is not pure and will change the underlying value } return newDate . toDate () ; } ;

Using the BIQU Hermit Crab CAN

I was recently offered to try out the BIQU Hermit Crab CAN.  This was great timing as one of my printers was down due to a bad thermistor cable in the cable drag chain.  I was in the process of removing the wiring and also replacing the drag chain to an open style which allows me to open up each segment and easily add or remove wires.   What is the Hermit Crab? The Hermit Crab is a quick change tool, which allows you to easily and quickly change out your tool. For example, you can change to a FDM extruder, or a laser engraver, or a cutting tool, or whatever you want. The CAN version uses just one USB-C cable (doesn't use USB protocol, just uses that style cable for convenience). The USB-C cable carries the data and power to the hotend. The CAN version has a bunch of nice features such as accelerometer, Neopixel RGB LEDs, and TMC2209 stepper driver. Current setup In this photo, you can see my current setup as I'm in the process of rewiring.  I've got an Orbiter extruder with

Leading zeros in SQL

So at work today, I ran across a SQL stored procedure called 'fixLeadingZeros' that has the below implementation. It's actually much longer than this since it does it for several columns.... and some columns have longer values. In other words, if the field is supposed to have 8 digits, it has 8 statements.  I couldn't help but to feel a bit disgusted from it and had to share it. UPDATE [SomeTable] SET SomeColumn = '0' + [SomeColumn] WHERE len (SomeColumn) =4 UPDATE [SomeTable] SET SomeColumn = '00' + SomeColumn WHERE len (SomeColumn) =3 UPDATE [SomeTable] SET SomeColumn = '000' + SomeColumn WHERE len (SomeColumn) = 2 UPDATE [SomeTable] SET SomeColumn = '0000' + SomeColumn WHERE len (SomeColumn) =1 UPDATE [Some] SET SomeColumn = '00000' WHERE SomeColumn ='' OR SomeColumn is null This could have been done a LOT easier.  If your goal is to update existing columns so that t