Installieren mehrerer Instanzen desselben Windows-Dienstes auf einem Server

96

Deshalb haben wir einen Windows-Dienst erstellt, um Daten an unsere Client-Anwendung weiterzuleiten, und alles läuft hervorragend. Der Client hat eine unterhaltsame Konfigurationsanforderung erstellt, für die zwei Instanzen dieses Dienstes auf demselben Server ausgeführt und so konfiguriert werden müssen, dass sie auf separate Datenbanken verweisen.

Bisher war ich nicht in der Lage, dies zu erreichen, und ich hoffte, dass meine Kollegen im Stackoverflow möglicherweise einige Hinweise geben könnten, warum.

Aktuelles Setup:

Ich habe das Projekt eingerichtet, das den Windows-Dienst enthält. Wir nennen es ab sofort AppService und die Datei ProjectInstaller.cs, die benutzerdefinierte Installationsschritte ausführt, um den Dienstnamen basierend auf einem Schlüssel in der App.config wie folgt festzulegen ::

this.serviceInstaller1.ServiceName = Util.ServiceName;
this.serviceInstaller1.DisplayName = Util.ServiceName;
this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.LocalSystem;

In diesem Fall ist Util nur eine statische Klasse, die den Dienstnamen aus der Konfigurationsdatei lädt.

Von hier an habe ich zwei verschiedene Methoden ausprobiert, um beide Dienste zu installieren, und beide sind auf identische Weise fehlgeschlagen.

Die erste Möglichkeit bestand darin, einfach die erste Kopie des Dienstes zu installieren, das installierte Verzeichnis zu kopieren und umzubenennen und dann den folgenden Befehl auszuführen, nachdem die App-Konfiguration geändert wurde, um den gewünschten Dienstnamen zu ändern:

InstallUtil.exe /i AppService.exe

Als das nicht funktionierte, habe ich versucht, ein zweites Installationsprojekt zu erstellen, die Konfigurationsdatei bearbeitet und das zweite Installationsprogramm erstellt. Als ich das Installationsprogramm ausführte, funktionierte es einwandfrei, aber der Dienst wurde nicht in services.msc angezeigt, sodass ich den vorherigen Befehl für die zweite installierte Codebasis ausführte.

Beide Male habe ich die folgende Ausgabe von InstallUtil erhalten (nur relevante Teile):

Ausführen einer getätigten Installation.

Beginn der Installationsphase der Installation.

Installieren von Service App Service Two ... Service App Service Two wurde erfolgreich installiert. Erstellen des EventLog-Quell-App-Dienstes Zwei im Protokoll Anwendung ...

Während der Installationsphase ist eine Ausnahme aufgetreten. System.NullReferenceException: Objektreferenz nicht auf eine Instanz eines Objekts festgelegt.

Die Rollback-Phase der Installation beginnt.

Wiederherstellen des Ereignisprotokolls auf den vorherigen Status für Quell-App-Service Zwei. Service App Service Two wird aus dem System entfernt ... Service App Service Two wurde erfolgreich aus dem System entfernt.

Die Rollback-Phase wurde erfolgreich abgeschlossen.

Die getätigte Installation ist abgeschlossen. Die Installation ist fehlgeschlagen und das Rollback wurde durchgeführt.

Entschuldigung für den langwierigen Beitrag, wollte sicherstellen, dass es genügend relevante Informationen gibt. Das Stück, das mich bisher verblüfft hat, ist, dass es besagt, dass die Installation des Dienstes erfolgreich abgeschlossen wurde und dass die NullReferenceException erst ausgelöst wird, nachdem die EventLog-Quelle erstellt wurde. Wenn also jemand weiß, was ich falsch mache oder einen besseren Ansatz hat, wäre er sehr dankbar.

Switters
quelle

Antworten:

81

Haben Sie den sc / service controller util ausprobiert? Art

sc create

an einer Befehlszeile, und es gibt Ihnen den Hilfeeintrag. Ich glaube, ich habe dies in der Vergangenheit für Subversion getan und diesen Artikel als Referenz verwendet:

http://svn.apache.org/repos/asf/subversion/trunk/notes/windows-service.txt

Jamesaharvey
quelle
5
Ich fand diese Seite nützlich : http://journalofasoftwaredev.wordpress.com/2008/07/16/multiple-instances-of-same-windows-service/. Sie können Code in das Installationsprogramm einfügen, um den gewünschten Dienstnamen zu erhalten, wenn Sie installutil ausführen.
Vivian River
9
Der Link zum WordPress-Blog wurde geändert in: journalofasoftwaredev.wordpress.com/2008/07
STLDev
21
  sc create [servicename] binpath= [path to your exe]

Diese Lösung hat bei mir funktioniert.

Rajesh Kumar
quelle
5
nur um darauf hinzuweisen; [path to your exe]muss voller Pfad sein und den Raum nachbinpath=
mkb
2
Dadurch kann ein Dienst tatsächlich mehrmals installiert werden. Alle vom Service-Installationsprogramm bereitgestellten Informationen. Fe Beschreibung, Anmeldetyp usw. wird ignoriert
Noel Widmer
20

Sie können mehrere Versionen desselben Dienstes ausführen, indem Sie folgende Schritte ausführen:

1) Kopieren Sie die ausführbare Datei und die Konfiguration des Dienstes in einen eigenen Ordner.

2) Kopieren Sie Install.Exe in den ausführbaren Ordner des Dienstes (aus dem .net Framework-Ordner).

3) Erstellen Sie eine Konfigurationsdatei mit dem Namen Install.exe.config im ausführbaren Ordner des Dienstes mit den folgenden Inhalten (eindeutige Dienstnamen):

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="ServiceName" value="The Service Name"/>
    <add key="DisplayName" value="The Service Display Name"/>
  </appSettings>
</configuration>

4) Erstellen Sie eine Batchdatei, um den Dienst mit den folgenden Inhalten zu installieren:

REM Install
InstallUtil.exe YourService.exe
pause

5) Erstellen Sie während Ihres Aufenthalts eine Batchdatei zum Deinstallieren

REM Uninstall
InstallUtil.exe -u YourService.exe
pause

BEARBEITEN:

Wenn ich etwas verpasst habe, ist hier die ServiceInstaller-Klasse (nach Bedarf anpassen):

using System.Configuration;

namespace Made4Print
{
    partial class ServiceInstaller
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;
        private System.ServiceProcess.ServiceInstaller FileProcessingServiceInstaller;
        private System.ServiceProcess.ServiceProcessInstaller FileProcessingServiceProcessInstaller;

        /// <summary> 
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Component Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.FileProcessingServiceInstaller = new System.ServiceProcess.ServiceInstaller();
            this.FileProcessingServiceProcessInstaller = new System.ServiceProcess.ServiceProcessInstaller();
            // 
            // FileProcessingServiceInstaller
            // 
            this.FileProcessingServiceInstaller.ServiceName = ServiceName;
            this.FileProcessingServiceInstaller.DisplayName = DisplayName;
            // 
            // FileProcessingServiceProcessInstaller
            // 
            this.FileProcessingServiceProcessInstaller.Account = System.ServiceProcess.ServiceAccount.LocalSystem;
            this.FileProcessingServiceProcessInstaller.Password = null;
            this.FileProcessingServiceProcessInstaller.Username = null;
            // 
            // ServiceInstaller
            // 
            this.Installers.AddRange(new System.Configuration.Install.Installer[] { this.FileProcessingServiceInstaller, this.FileProcessingServiceProcessInstaller });
        }

        #endregion

        private string ServiceName
        {
            get
            {
                return (ConfigurationManager.AppSettings["ServiceName"] == null ? "Made4PrintFileProcessingService" : ConfigurationManager.AppSettings["ServiceName"].ToString());
            }
        }

        private string DisplayName
        {
            get
            {
                return (ConfigurationManager.AppSettings["DisplayName"] == null ? "Made4Print File Processing Service" : ConfigurationManager.AppSettings["DisplayName"].ToString());
            }
        }
    }
}
Mark Redman
quelle
Ich denke, was Sie beschreiben, ist mehr oder weniger das, was ich getan habe, indem ich zugelassen habe, dass der Dienstname und der Anzeigename in meiner Dienst-App festgelegt werden. Ich habe versucht, was Sie beschreiben, aber leider führte dies zu demselben Problem, das in meiner Frage aufgeführt ist.
Switters
Ich habe eine Art Vorlage, die ich seit Ewigkeiten verwende. Vielleicht habe ich etwas verpasst. Wie sieht Ihre ServiceInstaller-Klasse aus? Wird eine Arbeitskopie einer von mir verwendeten Vorlage veröffentlicht? Lassen Sie mich wissen, ob dies hilfreich ist.
Mark Redman
Unsere Service-Installateure sind eigentlich nahezu identisch. Ich verwende eine statische Klasse, um den Dienst zu laden und Namen aus der Konfigurationsdatei anzuzeigen, aber ansonsten sind sie sehr ähnlich. Ich vermute, warum es bei mir nicht funktioniert, dass unser Service-Code möglicherweise etwas Besonderes enthält. Leider waren viele Hände drauf. Soweit ich weiß, sollte Ihre Antwort in den meisten Fällen funktionieren, danke für die Hilfe.
Switters
2
Riesige Hilfe, danke. Ich denke, die Installationskonfigurationsdatei muss InstallUtil.exe.confg heißen, nicht Install.exe.config für InstallUtil.exe
NullReference
Ein schöner Ansatz, der total funktioniert. Das ist, wenn Sie wissen, welche InstallUtil.exe in Ihren Installationsordner kopiert werden soll (ich persönlich habe Tonnen von Framework-Versionen installiert, was durch die 64-Bit-Kopien noch verstärkt wird). Dies würde es ziemlich schwierig machen, dem Helpdesk-Team zu erklären, ob sie die Installationen durchführen. Für eine von Entwicklern geleitete Installation ist sie jedoch sehr elegant.
timmi4sa
11

Alte Frage, ich weiß, aber ich hatte Glück mit der Option / servicename auf InstallUtil.exe. Ich sehe es jedoch nicht in der eingebauten Hilfe.

InstallUtil.exe /servicename="My Service" MyService.exe

Ich bin mir nicht ganz sicher, wo ich das erste Mal darüber gelesen habe, aber ich habe es seitdem nicht mehr gesehen. YMMV.

Jonathon Watney
quelle
3
Gibt diesen Fehler zurück:An exception occurred during the Install phase. System.ComponentModel.Win32Exception: The specified service already exists
mkb
@mkb Haben Sie einen anderen Dienst namens "Mein Dienst"?
Jonathon Watney
Ja, wie in der Frage habe ich einen Dienst, dieselbe ausführbare Datei, aber ich möchte zwei Instanzen davon installieren, jede mit unterschiedlicher Konfiguration. Ich habe die Service-Exe kopiert und eingefügt, aber diese hat nicht funktioniert.
mkb
1
/ servicename = "My Service InstanceOne" und / servicename = "My Service InstanceTwo" Die Namen müssen eindeutig sein.
GranadaCoder
11

Eine weitere schnelle Möglichkeit, einen benutzerdefinierten Wert für ServiceNameund anzugeben, DisplayNameist die Verwendung von installutilBefehlszeilenparametern.

  1. ProjectInstallerÜberschreiben Sie in Ihrer Klasse virtuelle Methoden Install(IDictionary stateSaver)undUninstall(IDictionary savedState)

    public override void Install(System.Collections.IDictionary stateSaver)
    {
        GetCustomServiceName();
        base.Install(stateSaver);
    }
    
    public override void Uninstall(System.Collections.IDictionary savedState)
    {
        GetCustomServiceName();
        base.Uninstall(savedState);
    }
    
    //Retrieve custom service name from installutil command line parameters
    private void GetCustomServiceName()
    {
        string customServiceName = Context.Parameters["servicename"];
        if (!string.IsNullOrEmpty(customServiceName))
        {
            serviceInstaller1.ServiceName = customServiceName;
            serviceInstaller1.DisplayName = customServiceName;
        }
    }
  2. Erstellen Sie Ihr Projekt
  3. Installieren Sie den Dienst, installutilindem Sie Ihren benutzerdefinierten Namen mithilfe des folgenden /servicenameParameters hinzufügen :

    installutil.exe /servicename="CustomServiceName" "c:\pathToService\SrvcExecutable.exe"
    

Beachten Sie, dass /servicenameder Dienst mit den in ProjectInstaller properties / config angegebenen Werten für ServiceName und DisplayName installiert wird , wenn Sie dies nicht in der Befehlszeile angeben

Andrea
quelle
2
Brillant!! Vielen Dank - genau das war nötig und auf den Punkt.
Iofacture
7

Ich hatte nicht viel Glück mit den oben genannten Methoden, als ich unsere automatisierte Bereitstellungssoftware zum häufigen Installieren / Deinstallieren von Windows-Diensten nebeneinander verwendete, aber ich kam schließlich auf Folgendes, mit dem ich einen Parameter übergeben konnte, um ein Suffix anzugeben auf den Dienstnamen in der Befehlszeile. Außerdem kann der Designer ordnungsgemäß funktionieren und kann bei Bedarf problemlos angepasst werden, um den gesamten Namen zu überschreiben.

public partial class ProjectInstaller : System.Configuration.Install.Installer
{
  protected override void OnBeforeInstall(IDictionary savedState)
  {
    base.OnBeforeInstall(savedState);
    SetNames();
  }

  protected override void OnBeforeUninstall(IDictionary savedState)
  {
    base.OnBeforeUninstall(savedState);
    SetNames();
  }

  private void SetNames()
  {
    this.serviceInstaller1.DisplayName = AddSuffix(this.serviceInstaller1.DisplayName);
    this.serviceInstaller1.ServiceName = AddSuffix(this.serviceInstaller1.ServiceName);
  }

  private string AddSuffix(string originalName)
  {
    if (!String.IsNullOrWhiteSpace(this.Context.Parameters["ServiceSuffix"]))
      return originalName + " - " + this.Context.Parameters["ServiceSuffix"];
    else
      return originalName;
  }
}

In diesem Sinne kann ich Folgendes tun: Wenn ich den Dienst "Awesome Service" genannt habe, kann ich eine UAT-Version des Dienstes wie folgt installieren:

InstallUtil.exe /ServiceSuffix="UAT" MyService.exe

Dadurch wird der Dienst mit dem Namen "Awesome Service - UAT" erstellt. Wir haben dies verwendet, um DEVINT-, TESTING- und ACCEPTANCE-Versionen desselben Dienstes auszuführen, die nebeneinander auf einem einzelnen Computer ausgeführt werden. Jede Version verfügt über einen eigenen Satz von Dateien / Konfigurationen. Ich habe nicht versucht, mehrere Dienste zu installieren, die auf denselben Satz von Dateien verweisen.

HINWEIS: Sie müssen denselben /ServiceSuffixParameter zum Deinstallieren des Dienstes verwenden, damit Sie zur Deinstallation Folgendes ausführen können:

InstallUtil.exe /u /ServiceSuffix="UAT" MyService.exe

Tristankoffee
quelle
Das ist großartig, aber das ist nur für den Installer. Woher weiß der Windows-Dienst von diesem neuen Namen, wenn Sie einen neuen Instanznamen haben? Müssen Sie es beim Aufbau des Windows-Dienstes weitergeben?
progLearner
Vielen Dank! Das Installationsprogramm legt den Namen im Windows-Dienst während der Installation mit den Werten fest, die in der obigen SetNames () -Methode festgelegt wurden.
Tristankoffee
Sicher, aber wie können Sie diesen Namen von außen setzen?
progLearner
In meiner Antwort ist der Befehl, der in der Befehlszeile verwendet wird, um den Dienst in der Außenwelt zu installieren (und zu deinstallieren). Der Wert, den Sie übergeben, /ServiceSuffix="UAT"wird vom Installationsprogramm verwendet, um das Suffix für den Dienst festzulegen. In meinem Beispiel ist der übergebene Wert UAT. In meinem Szenario wollte ich dem vorhandenen Namen des Dienstes nur ein Suffix hinzufügen, aber es gibt keinen Grund, warum Sie dies nicht anpassen könnten, um den Namen vollständig durch den übergebenen Wert zu ersetzen.
tristankoffee
Danke, aber das ist eine Befehlszeileneingabe (= manuelle Eingabe), kein Code. Gemäß der ursprünglichen Frage: Wenn Sie einen neuen Instanznamen haben, wie erfährt der Windows-Dienst von diesem neuen Namen? Müssen Sie es beim Aufbau des Windows-Dienstes weitergeben?
progLearner
4

Um dies zu erreichen, habe ich den Dienstnamen und den Anzeigenamen in einer app.config für meinen Dienst gespeichert. Dann lade ich in meiner Installationsklasse die Datei app.config als XmlDocument und verwende xpath, um die Werte abzurufen und auf ServiceInstaller.ServiceName und ServiceInstaller.DisplayName anzuwenden, bevor ich InitializeComponent () aufrufe. Dies setzt voraus, dass Sie diese Eigenschaften nicht bereits in InitializeComponent () festlegen. In diesem Fall werden die Einstellungen aus Ihrer Konfigurationsdatei ignoriert. Der folgende Code wird von meinem Installer-Klassenkonstruktor vor InitializeComponent () aufgerufen:

       private void SetServiceName()
       {
          string configurationFilePath = Path.ChangeExtension(Assembly.GetExecutingAssembly().Location, "exe.config");
          XmlDocument doc = new XmlDocument();
          doc.Load(configurationFilePath);

          XmlNode serviceName = doc.SelectSingleNode("/xpath/to/your/@serviceName");
          XmlNode displayName = doc.SelectSingleNode("/xpath/to/your/@displayName");

          if (serviceName != null && !string.IsNullOrEmpty(serviceName.Value))
          {
              this.serviceInstaller.ServiceName = serviceName.Value;
          }

          if (displayName != null && !string.IsNullOrEmpty(displayName.Value))
          {
              this.serviceInstaller.DisplayName = displayName.Value;
          }
      }

Ich glaube nicht, dass das Lesen der Konfigurationsdatei direkt aus ConfigurationManager.AppSettings oder ähnlichem funktioniert, wenn das Installationsprogramm ausgeführt wird. Es wird im Kontext von InstallUtil.exe ausgeführt, nicht in der EXE-Datei Ihres Dienstes. Möglicherweise können Sie mit ConfigurationManager.OpenExeConfiguration etwas tun. In meinem Fall funktionierte dies jedoch nicht, da ich versuchte, zu einem benutzerdefinierten Konfigurationsabschnitt zu gelangen, der nicht geladen wurde.

chris.house.00
quelle
Hallo Chris House! Ich bin über Ihre Antwort gestolpert, weil ich eine selbst gehostete OWIN-basierte Web-API um den Quartz.NET-Scheduler herum erstelle und sie in einen Windows-Dienst stecke. Ziemlich schick! Ich hoffe es geht dir gut!
NovaJoe
Hallo Chris House! Ich bin über Ihre Antwort gestolpert, weil ich eine selbst gehostete OWIN-basierte Web-API um den Quartz.NET-Scheduler herum erstelle und sie in einen Windows-Dienst stecke. Ziemlich schick! Ich hoffe es geht dir gut!
NovaJoe
4

Nur um perfekte Antwort von @ chris.house.00 zu verbessern diese , können Sie folgende Funktion betrachten von Ihren App - Einstellungen zu lesen:

 public void GetServiceAndDisplayName(out string serviceNameVar, out string displayNameVar)
        {
            string configurationFilePath = Path.ChangeExtension(Assembly.GetExecutingAssembly().Location, "exe.config");
            XmlDocument doc = new XmlDocument();
            doc.Load(configurationFilePath);

            XmlNode serviceName = doc.SelectSingleNode("//appSettings//add[@key='ServiceName']");
            XmlNode displayName = doc.SelectSingleNode("//appSettings//add[@key='DisplayName']");


            if (serviceName != null && (serviceName.Attributes != null && (serviceName.Attributes["value"] != null)))
            {
                serviceNameVar = serviceName.Attributes["value"].Value;
            }
            else
            {
                serviceNameVar = "Custom.Service.Name";
            }

            if (displayName != null && (displayName.Attributes != null && (displayName.Attributes["value"] != null)))
            {
                displayNameVar = displayName.Attributes["value"].Value;
            }
            else
            {
                displayNameVar = "Custom.Service.DisplayName";
            }
        }
Teoman Shipahi
quelle
2

Ich hatte eine ähnliche Situation, in der ich einen vorherigen Dienst und einen aktualisierten Dienst benötigte, der nebeneinander auf demselben Server ausgeführt wurde. (Es war mehr als nur eine Datenbankänderung, es waren auch Codeänderungen). Ich konnte also nicht zweimal dieselbe EXE-Datei ausführen. Ich brauchte eine neue EXE-Datei, die mit neuen DLLs kompiliert wurde, aber aus demselben Projekt stammt. Nur das Ändern des Dienstnamens und des Anzeigenamens des Dienstes hat bei mir nicht funktioniert. Ich habe immer noch den Fehler "Dienst bereits vorhanden" erhalten, der meiner Meinung nach darauf zurückzuführen ist, dass ich ein Bereitstellungsprojekt verwende. Was letztendlich für mich funktioniert hat, ist, dass es in meinen Deployment Project-Eigenschaften eine Eigenschaft namens "ProductCode" gibt, die eine Richtlinie ist.

Geben Sie hier die Bildbeschreibung ein

Danach wird das Setup-Projekt auf eine neue .exe oder .msi neu installiert, die erfolgreich installiert wurde.

cmartin
quelle
1

Der einfachste Ansatz besteht darin, den Dienstnamen auf dem DLL-Namen zu basieren:

string sAssPath = System.Reflection.Assembly.GetExecutingAssembly().Location;
string sAssName = System.IO.Path.GetFileNameWithoutExtension(sAssPath);
if ((this.ServiceInstaller1.ServiceName != sAssName)) {
    this.ServiceInstaller1.ServiceName = sAssName;
    this.ServiceInstaller1.DisplayName = sAssName;
}
Igor Krupitsky
quelle