Debugging a Windows Service


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.


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
  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
    If Not String.IsNullOrEmpty(logMessage) Then
      WriteLogMessage(logMessage, EventLogEntryType.Information)
    End If
    'Start the file watching...
    With FileSystemWatcher1
      .Filter = _fileMask
      .IncludeSubdirectories = _includeSubFolders
      .Path = _folderName
      .EnableRaisingEvents = True
      If Not Me.CommandLineExecution Then
        _serviceStatus.currentState = Fix(State.SERVICE_RUNNING)
        SetServiceStatus(handle, _serviceStatus)
      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.



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!

Comments are closed.

Recent Comments