Debugging a Windows Service

Introduction

In my last article I showed a simple example of how to create a Windows Service using VB. This time we’re going to have a look at how to debug the service from Visual Studio.

Since a service must run from the context of the Service Control Manager you can’t normally debug it in the same manner as you would any other project type. Normally you would need to build the project, install it using the InstallUtil.exe command line tool, and attach a debugger to the process while it’s running. Another approach is to create a separate project (console application) and call your main code from there.

Neither of these are an ideal way of doing the debugging so I’m going to show an alternative method, which you also can use to run your service from the command line, just like any other application.

Converting the service into a command line tool

When you create a Windows Service project in Visual Studio 2008, you get a ServiceBase designer to which you add controls and your code to. Normally you would, at the very least, override the OnLoad() and the OnStop() methods. But the designer also creates a shared (static) Main() method for you, which is the real starting point of the service. To be able to see that you need to open the designer generated code file. In VB this file is normally hidden, so you need to press the Show all files button in the Solution Explorer.

image

The Main() method looks like this (with some comments removed).

<MTAThread()> _
<System.Diagnostics.DebuggerNonUserCode()> _
Public Shared Sub Main()
  Dim ServicesToRun() As System.ServiceProcess.ServiceBase
  ServicesToRun = _
New System.ServiceProcess.ServiceBase() {New FileWatcherService} System.ServiceProcess.ServiceBase.Run(ServicesToRun) End Sub



A Main() method can receive arguments from the command line, you just have to add a string array as a parameter of the method. The whole idea is that if a specific command line switch is passed to the executable it should not act as a Windows Service but instead run as any other EXE file. So I rewrote the above method to this:



' The main entry point for the process
<MTAThread()> _
<System.Diagnostics.DebuggerNonUserCode()> _
Public Shared Sub Main(ByVal args() As String)
  Dim runAsService As Boolean = True
  For Each arg As String In args
    Select Case arg.ToLower
      Case "/c", "-c"
        runAsService = False
    End Select
  Next
  If runAsService Then
    Dim ServicesToRun() As System.ServiceProcess.ServiceBase
    ServicesToRun = _
New System.ServiceProcess.ServiceBase() {New FileWatcherService} System.ServiceProcess.ServiceBase.Run(ServicesToRun) Else Dim service As New FileWatcherService service.CommandLineExecution = True service.OnStart(New String() {""}) End If End Sub



If the /c or –c switch is found among the command line parameters then it will start as a regular program. As you can see, if the switch is found, instead of creating an instance of the ServiceBase class I create an instance of my service class directly and call its OnStart() method. I also added a CommandLineExecution property to the class so I can keep track of how it is supposed to be executed. If you need to set a breakpoint or step through the Main() method you need to remove the DebuggerNonUserCode attribute, otherwise Visual Studio will just run through this method without stopping (this is what you normally would want to do with designer created code).



Now I also needed to make some changes to the OnStart() method.



Protected Overrides Sub OnStart(ByVal args() As String)
  Dim handle As IntPtr = Me.ServiceHandle
  If Not Me.CommandLineExecution Then
    _serviceStatus.currentState = Fix(State.SERVICE_START_PENDING)
    SetServiceStatus(handle, _serviceStatus)
  End If
  Dim logMessage As String = String.Empty
  If Not ReadSettings(logMessage) Then
    WriteLogMessage(logMessage, EventLogEntryType.Error)
    _serviceStatus.currentState = Fix(State.SERVICE_STOPPED)
    If Not Me.CommandLineExecution Then
      SetServiceStatus(handle, _serviceStatus)
    End If
  Else
    If Not String.IsNullOrEmpty(logMessage) Then
      WriteLogMessage(logMessage, EventLogEntryType.Information)
    End If
    'Start the file watching...
    With FileSystemWatcher1
      .BeginInit()
      .Filter = _fileMask
      .IncludeSubdirectories = _includeSubFolders
      .Path = _folderName
      .EnableRaisingEvents = True
      .EndInit()
      If Not Me.CommandLineExecution Then
        _serviceStatus.currentState = Fix(State.SERVICE_RUNNING)
        SetServiceStatus(handle, _serviceStatus)
      Else
        Threading.Thread.Sleep(Threading.Timeout.Infinite)
      End If
    End With
  End If
End Sub



If the CommandLineExecution property is set to True I skip all the calls to the SetServiceStatus() Win32 API function. I also end the method by letting the current thread sleep for infinity, if I didn’t do that the program would just exit when it reached the end of the method.



I also made a slight change to the WriteLogMessage() method, if the application isn’t running as a service there is no reason to do the logging to the Event Viewer. Instead it simply shows a message box.



Private Sub WriteLogMessage(ByVal message As String, _
ByVal type As EventLogEntryType) If Not Me.CommandLineExecution Then If Not EventLog.SourceExists("File Observer") Then EventLog.CreateEventSource("File Observer", "File Observer Log") End If Dim log As New EventLog() log.Source = "File Observer" log.WriteEntry(message, type) Else MsgBox(String.Format("{0}: {1}", type.ToString, message)) End If End Sub



Debug the service



To be able to run this service as a regular application from Visual Studio you must pass the command line switch /c. You can specify command line arguments on the Debug tab of the project properties dialog.



image



Conclusion



In this post I showed you how to turn a Windows Service into a regular executable using a command line switch to allow you to debug it from Visual Studio. Of course, instead of a command line switch you could instead just check if the application is in debug or release mode. But I wanted to be able to run the service as a regular application even after it was compiled and deployed in release mode.



Have fun!

7 thoughts on “Debugging a Windows Service”

  1. Hi Joacim i’m having a problem here:

    System.ServiceProcess.ServiceBase.Run(ServicesToRun)

    when the debug hits this i get this error:

    Cannot start service from the command line or a debugger. A windows service must first be installed (using installutil.exe) and then started with the ServerExplorer, Services Admin Tool or the Net Start command

    i tried using installutil but it doesn’t recognize installutil as a command. Any thoughts? i could really use some help

  2. You should find the InstallUtil.exe program in C:\Windows\Microsoft.NET\Framework\v2.0.50727
    If you open a Visual Studio command prompt instead of a regular command prompt the PATH environment variable is changed to include this folder.

  3. This is my 2nd day at .NET and I have to write a multi-threaded windows service. Coming from VB6 and leveraging helpful tips like yours has brought me a long way in 2 days… however I still have no idea how to keep my program running after all the code in the OnStart sub finishes executing when running in debug mode.

    I assume it exits back to Main() and when that sub exits, the process ends. Because I’m leveraging a timer and launching threads I have simply used a MsgBox to halt primary thread execution while new threads are launched in the background via timer intervals, but I was curious if you had come up with a better way to keep things running.

  4. @Amal: What I did in the above example was to call Thread.Sleep and sleep for ifinity. In a real app that might not be the best solution.

    In a very reason project which also called for a Windows Service what I did was something very similar to what I showed above. But instead of adding the CommandLineExecution property and check the command line arguments in sub Main I used conditional compiler directives to check if I was in debug or release mode.

    #If DEBUG Then
    ‘Do this
    #Else
    ‘Do that
    #End If

    If I was in debug mode I created and attached to a console so I could use Console.WriteLine and Console.ReadLine, so I simply added a Console.ReadLine at the end of the OnStart event so the application didn’t end until I pressed the Enter key.

    This code will attach and allocate the console for you.

    Const attachParentProcess As UInt32 = UInt32.MaxValue
    Const errorAccessDenied As Integer = 5

    If Not AttachConsole(attachParentProcess) AndAlso Marshal.GetLastWin32Error() = errorAccessDenied Then
    MsgBox(“Unable to attach a console to this process.”, MsgBoxStyle.Critical)
    Else
    AllocConsole()
    ‘Write all Debug.Print to the console window
    Debug.Listeners.Add(New ConsoleTraceListener())
    End If

    To run it you need to declare these functions:
    Private Declare Function AllocConsole Lib “kernel32.dll” () As Boolean
    Private Declare Function AttachConsole Lib “kernel32.dll” (dwProcessId As UInt32) As Boolean
    Private Declare Function FreeConsole Lib “kernel32.dll” () As Boolean

    Remember to call the FreeConsole function before you exit the app.

    Note that you don’t have to use conditional compilation, you can use the same approach as described in this blog post, but you should only attach to a console if you are not running the program as a service.

  5. Ahh ok. I actually was using Call AttachConsole(-1) to attach to the console session, but was not using AllocConsole.

    I also found that if I used Console.ReadLine, it blocked further output on the console from threaded workers. I ended up going back to a MsgBox that simply says “Running in debug mode. Click OK to exit.” and a call to OnStop() afterward, which calls FreeConsole (if running in debug).

    Thanks for replying!

Leave a Reply

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


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>