C# Dienst mit eigener Installationsroutine (per /i)

Nach langer suche und vielen Zusammenstückeln, habe ich nun endlich ein komplettes Paket um einen Dienst die Möglichkeit zu implementieren sich selbst per Kommandozeilenparameter zu installieren.

Ebenso wird zeitgleich eine Konsolenausgabe mit implementiert. Das einzige Problem hier ist, solange der Projekt-Typ nach Standard auf “Windows Anwendung” steht, funktionieren das Attachen nicht ganz komplikationslos, da die Anwendung in der Konsole direkt zurückkehrt ohne aufs Ende des Prozesses zu warten. Um diese Problem wegzubekommen, muss der Projekt-Typ auf “Konsolen Anwendung” umgestellt werden.

Dieser Quellcode hat einen großen Vorteil gegenüber anderen die noch vorhanden sind, da hier beim installieren und deinstallieren das gleiche durchgeführt wird, was auch das InstallUtil Programm durchführt. Daher kann das Array welches bei Install und Deinstall übergeben wird, entsprechend der Parameter die am InstallUtil vorhanden sind erweitert werden.

(Vorne weg noch ein kleiner Tip, sollte die using-Direktive zu einer Klasse fehlen und die Klasse wurde richtig geschrieben, dann kann per [Strg]+[.] die Direktive automatisch ermittelt und hinzugefügt werden. Für den Fall das ich welche vergessen habe.)

Zuerst einmal die Konstanten und die Imports, welche innerhalb der “static class Program” eingefügt werden müssen:

const uint ATTACH_PARENT_PROCESS = 0x0ffffffff;
const int ERROR_ACCESS_DENIED = 5; // Process ist schon attached von einer anderen Konsole
[DllImport("kernel32", SetLastError = true)]
static extern bool AllocConsole();
[DllImport("kernel32", SetLastError = true)]
static extern bool FreeConsole();
[DllImport("kernel32", SetLastError = true)]
static extern bool AttachConsole(uint dwProcessID);

Die Main-Prozedur (der noch der Paramter “string[] args” hinzugefügt werden muss, wenn nicht vorhanden) muss nun folgender weise erweitert werden:

ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[] { new MyNewService() };
if (System.Environment.UserInteractive)
{
  if(!AttachConsole(ATTACH_PARENT_PROCESS) || Marshal.GetLastWin32Error() == ERROR_ACCESS_DENIED)
    AllocConsole();
  // Uebergabe Parameter
  bool install = false;
  bool uninstall = false;
  bool debug = false;
  string value = "";
  int pos = -1;
  for (int i = 0; i < args.Length; i++)
  {
    var arg = args[i];
    value = "";
    pos = arg.IndexOf('=');
    if (pos &gt; 0)
    {
      value = arg.Substring(pos + 1);
      arg = arg.Substring(0, pos);
    }
    switch(arg.ToLower())
    {
      case "/i": install = true; break;
      case "/u": uninstall = true; break;
      case "/c": debug = true; break;
      case "/account": /*Account-Typ aus der Variable value speichern*/ break;
      case "/user": /*Benutzer aus der Variable value speichern*/ break;
      case "/passwd": /*Passwort aus der Variable value speichern*/ break;
      default:
        Console.WriteLine("Argument nicht verstanden: {0}", arg);
        break;
    }
  }
  try
  {
    if (install)
    {
      var combArgs = new string[] { Assembly.GetExecutingAssembly().Location };
      System.Configuration.Install
        .ManagedInstallerClass.InstallHelper(combArgs);
    }
    else if (uninstall)
    {
      var combArgs = new string[] { "/u", Assembly.GetExecutingAssembly().Location };
      System.Configuration.Install
        .ManagedInstallerClass.InstallHelper(combArgs);
    }
    else if (debug)
    {
      var svc = ServicesToRun[0] as MyNewService;
      svc.OnStartDebug(args);
      Console.WriteLine("[Return] fürs beenden drücken...");
      Console.ReadLine();
      svc.OnStopDebug();
    }
  }
  catch (Exception exc)
  {
    Console.WriteLine(exc.Message);
  }
  // Konsole wieder freigeben
  FreeConsole();
}
else
{
  // Wenn das Programm nicht ueber die Konsole gestartet wurde dann als Dienst starten lassen
  ServiceBase.Run(ServicesToRun);
}

Zusätzlich müssen die 2 Funktionen (OnStartDebug/OnStopDebug) angelegt werden, die einzig die gleichnamigen (ohne Debug am Ende) protected Methoden aufrufen, wenn der Debug-Modus gewünscht ist. Wenn nicht, dann einfach diesen Teil aus den obigen Quellcode entfernen.