Debugging a windows service might be a little bit hard. Because each change in your code implies to uninstall/reinstall the service. A way to avoid this issue is to launch the service from the command line. Then you will be able to add breakpoints in your source, even in the OnStart method…
Launching the service from the command line
There is a few things to change in order to allow launching a service from the command line.
Changing application properties
In the Application tab of the service property window, set the output type to “Console Application”
Update the Main method
Now we have to detect when service is launched from the command line (or from the IDE) and when it is launched from the mmc or using the net start command.
For this, we will update the Main method in Program.cs file.
before:
ServicesToRun = new ServiceBase[] { new Service1() }; ServiceBase.Run(ServicesToRun);
after:
ServiceBase[] servicesToRun = new ServiceBase[] { new Service1() }; if (Environment.UserInteractive) { // executable has been started from command line (or from the Visual Studio IDE) Type type = typeof(ServiceBase); BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; MethodInfo method = type.GetMethod("OnStart", flags); foreach (ServiceBase service in servicesToRun) { try { method.Invoke(service, new object[] {args}); Console.ReadKey(true); } catch (Exception e) { Console.WriteLine("Unable to start the service '" + service.ServiceName + "'. " + "System returns: " + e.Message + (e.InnerException != null ? " " + e.InnerException.Message : "")); } } } else { // Normal service behaviour (when started as a service) ServiceBase.Run(servicesToRun); }
Please note, that you have to update the Main prototype in order to allow passing arguments.
public static void Main()
becomes
public static void Main(string[] args)
Then, we can add some “extra stuff” to this service:
- Allow the user to stop the service from the command line (using CTL+C)
- Add a TraceSwitch and the abiliy to change the trace level from the console (using CTL+D)
- Display this new features when user press the ENTER key.
public static class Program { public static TraceSwitch tTrace = new TraceSwitch("TraceLevelSwitch", "Maximum level of displayed trace"); // Default value is set in the .config file /// /// The main entry point for the application. /// Arguments passed through the command line /// public static void Main(string[] args) { // More than one user Service may run within the same process. To add // another service to this process, change the following line to // create a second service object. For example, // // ServicesToRun = new ServiceBase[] {new Service1(), new MySecondUserService()}; ServiceBase[] servicesToRun = new ServiceBase[] { new Service1() }; if (Environment.UserInteractive) { // executable has been started from command line (or from the Visual Studio IDE) Type type = typeof(ServiceBase); BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; MethodInfo method = type.GetMethod("OnStart", flags); PrintUsage(); foreach (ServiceBase service in servicesToRun) { try { method.Invoke(service, new object[] {args}); } catch (Exception e) { Console.WriteLine("Unable to start the service '" + service.ServiceName + "'. " + "System returns: " + e.Message + (e.InnerException != null ? " " + e.InnerException.Message : "")); } } ConsoleKeyInfo cki; Console.TreatControlCAsInput = true; do { StringBuilder sb = new StringBuilder(); sb.Length = 0; // Wait for user input cki = Console.ReadKey(true); if (cki.Modifiers != 0) { if ((cki.Modifiers & ConsoleModifiers.Alt) != 0) { sb.Append("ALT+"); } if ((cki.Modifiers & ConsoleModifiers.Shift) != 0) sb.Append("SHIFT+"); if ((cki.Modifiers & ConsoleModifiers.Control) != 0) sb.Append("CTL+"); } sb.Append(cki.Key.ToString()); // Process user input if ((cki.Key == ConsoleKey.Enter) && ((cki.Modifiers & ConsoleModifiers.Control) == 0) && ((cki.Modifiers & ConsoleModifiers.Shift) == 0) && ((cki.Modifiers & ConsoleModifiers.Alt) == 0)) { PrintUsage(); } else if ((cki.Key == ConsoleKey.D) && ((cki.Modifiers & ConsoleModifiers.Control) != 0) && ((cki.Modifiers & ConsoleModifiers.Shift) == 0) && ((cki.Modifiers & ConsoleModifiers.Alt) == 0)) { // Decrease the trace level // (set it to Verbose if the current value is Off) if (tTrace.Level == TraceLevel.Off) tTrace.Level = TraceLevel.Verbose; else tTrace.Level--; Console.WriteLine("New trace level: " + tTrace.Level.ToString()); } else if (!((cki.Key == ConsoleKey.C && (cki.Modifiers & ConsoleModifiers.Control) != 0) && ((cki.Modifiers & ConsoleModifiers.Shift) == 0) && ((cki.Modifiers & ConsoleModifiers.Alt) == 0))) Console.WriteLine(sb + " - ignored."); } while (!((cki.Key == ConsoleKey.C && (cki.Modifiers & ConsoleModifiers.Control) != 0) && ((cki.Modifiers & ConsoleModifiers.Shift) == 0) && ((cki.Modifiers & ConsoleModifiers.Alt) == 0))); // User press CTL+C keys -> Exit // Close all services foreach (ServiceBase service in servicesToRun) try { service.Stop(); } catch (Exception ex) { // Just log it Console.WriteLine("Error occured when trying to stop service(s). System returns: " + ex.Message + (ex.InnerException != null ? " " + ex.InnerException.Message : "")); } } else { // Normal service behaviour (when started as a service) ServiceBase.Run(servicesToRun); } } private static void PrintUsage() { Console.WriteLine("Press CTL+C to exit"); Console.WriteLine("Press CTL+D to change the trace level. Current level is " + tTrace.Level.ToString()); Console.WriteLine("------------------------------------------------"); } }
Same snippet, but in Visual Basic
Public NotInheritable Class Program Private Sub New() End Sub Public Shared tTrace As New TraceSwitch("TraceLevelSwitch", "Maximum level of displayed trace") ' Default value is set in the .config file ''' ''' The main entry point for the application. ''' Arguments passed through the command line ''' Public Shared Sub Main(args As String()) ' More than one user Service may run within the same process. To add ' another service to this process, change the following line to ' create a second service object. For example, ' ' ServicesToRun = new ServiceBase[] {new Service1(), new MySecondUserService()}; Dim servicesToRun As ServiceBase() = New ServiceBase() {New Service1()} If Environment.UserInteractive Then ' executable has been started from command line (or from the Visual Studio IDE) Dim type As Type = GetType(ServiceBase) Dim flags As BindingFlags = BindingFlags.Instance Or BindingFlags.NonPublic Dim method As MethodInfo = type.GetMethod("OnStart", flags) PrintUsage() For Each service As ServiceBase In servicesToRun Try method.Invoke(service, New Object() {args}) Catch e As Exception Console.WriteLine("Unable to start the service '" + service.ServiceName + "'. " + "System returns: " + e.Message + (If(e.InnerException IsNot Nothing, " " + e.InnerException.Message, ""))) End Try Next Dim cki As ConsoleKeyInfo Console.TreatControlCAsInput = True Do Dim sb As New StringBuilder() sb.Length = 0 ' Wait for user input cki = Console.ReadKey(True) If cki.Modifiers <> 0 Then If (cki.Modifiers And ConsoleModifiers.Alt) <> 0 Then sb.Append("ALT+") End If If (cki.Modifiers And ConsoleModifiers.Shift) <> 0 Then sb.Append("SHIFT+") End If If (cki.Modifiers And ConsoleModifiers.Control) <> 0 Then sb.Append("CTL+") End If End If sb.Append(cki.Key.ToString()) ' Process user input If (cki.Key = ConsoleKey.Enter) AndAlso ((cki.Modifiers And ConsoleModifiers.Control) = 0) AndAlso ((cki.Modifiers And ConsoleModifiers.Shift) = 0) AndAlso ((cki.Modifiers And ConsoleModifiers.Alt) = 0) Then PrintUsage() ElseIf (cki.Key = ConsoleKey.D) AndAlso ((cki.Modifiers And ConsoleModifiers.Control) <> 0) AndAlso ((cki.Modifiers And ConsoleModifiers.Shift) = 0) AndAlso ((cki.Modifiers And ConsoleModifiers.Alt) = 0) Then ' Decrease the trace level ' (set it to Verbose if the current value is Off) If tTrace.Level = TraceLevel.Off Then tTrace.Level = TraceLevel.Verbose Else tTrace.Level -= 1 End If Console.WriteLine("New trace level: " + tTrace.Level.ToString()) ElseIf Not ((cki.Key = ConsoleKey.C AndAlso (cki.Modifiers And ConsoleModifiers.Control) <> 0) AndAlso ((cki.Modifiers And ConsoleModifiers.Shift) = 0) AndAlso ((cki.Modifiers And ConsoleModifiers.Alt) = 0)) Then Console.WriteLine(sb + " - ignored.") End If Loop While Not ((cki.Key = ConsoleKey.C AndAlso (cki.Modifiers And ConsoleModifiers.Control) <> 0) AndAlso ((cki.Modifiers And ConsoleModifiers.Shift) = 0) AndAlso ((cki.Modifiers And ConsoleModifiers.Alt) = 0)) ' User press CTL+C keys -> Exit ' Close all services For Each service As ServiceBase In servicesToRun Try service.[Stop]() Catch ex As Exception ' Just log it Console.WriteLine("Error occured when trying to stop service(s). System returns: " + ex.Message + (If(ex.InnerException IsNot Nothing, " " + ex.InnerException.Message, ""))) End Try Next Else ' Normal service behaviour (when started as a service) ServiceBase.Run(servicesToRun) End If End Sub Private Shared Sub PrintUsage() Console.WriteLine("Press CTL+C to exit") Console.WriteLine("Press CTL+D to change the trace level. Current level is " + tTrace.Level.ToString()) Console.WriteLine("------------------------------------------------") End Sub End Class
Leave a comment