Instalowanie wielu wystąpień tej samej usługi systemu Windows na serwerze

96

Dlatego stworzyliśmy usługę Windows do dostarczania danych do naszej aplikacji klienckiej i wszystko idzie świetnie. Klient wymyślił zabawne żądanie konfiguracji, które wymaga dwóch wystąpień tej usługi działających na tym samym serwerze i skonfigurowanych do wskazywania oddzielnych baz danych.

Do tej pory nie byłem w stanie tego zrobić i miałem nadzieję, że moi koledzy z stackoverflow mogą dać kilka wskazówek, dlaczego.

Obecne ustawienia:

Skonfigurowałem projekt, który zawiera usługę systemu Windows, od teraz będziemy go nazywać AppService i plik ProjectInstaller.cs, który obsługuje niestandardowe kroki instalacji, aby ustawić nazwę usługi na podstawie klucza w pliku App.config :

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

W tym przypadku Util jest po prostu klasą statyczną, która ładuje nazwę usługi z pliku konfiguracyjnego.

Odtąd próbowałem zainstalować obie usługi na dwa różne sposoby i obie zakończyły się niepowodzeniem w identyczny sposób.

Pierwszym sposobem było po prostu zainstalowanie pierwszej kopii usługi, skopiowanie zainstalowanego katalogu i zmiana jego nazwy, a następnie uruchomienie następującego polecenia po zmodyfikowaniu konfiguracji aplikacji, aby zmienić żądaną nazwę usługi:

InstallUtil.exe /i AppService.exe

Kiedy to nie zadziałało, próbowałem stworzyć drugi projekt instalatora, wyedytowałem plik konfiguracyjny i zbudowałem drugi instalator. Kiedy uruchomiłem instalator, działało dobrze, ale usługa nie pojawiła się w services.msc, więc uruchomiłem poprzednie polecenie dla drugiej zainstalowanej bazy kodu.

Za każdym razem otrzymałem następujące dane wyjściowe z InstallUtil (tylko odpowiednie części):

Uruchamianie przeprowadzonej instalacji.

Rozpoczynanie fazy instalacji instalacji.

Instalowanie usługi App Service Two ... Usługa App Service Two została pomyślnie zainstalowana. Tworzenie źródła EventLog App Service Two w dzienniku aplikacji ...

Wystąpił wyjątek podczas fazy instalacji. System.NullReferenceException: odwołanie do obiektu nie jest ustawione na wystąpienie obiektu.

Rozpoczyna się faza wycofywania instalacji.

Przywracanie dziennika zdarzeń do poprzedniego stanu dla źródłowej usługi App Service Two. Usługa Service App Service 2 jest usuwana z systemu ... Service App Service 2 została pomyślnie usunięta z systemu.

Faza wycofywania została zakończona pomyślnie.

Transakcja instalacji została zakończona. Instalacja nie powiodła się i przywrócenie zostało wykonane.

Przepraszamy za rozwlekły post, chciałem się upewnić, że jest wystarczająco dużo istotnych informacji. Element, który do tej pory mnie zaskoczył, to stwierdzenie, że instalacja usługi zakończyła się pomyślnie i dopiero po utworzeniu źródła EventLog wydaje się, że NullReferenceException zostaje wyrzucony. Więc jeśli ktoś wie, co robię źle lub ma lepsze podejście, będzie to bardzo wdzięczne.

Switters
źródło

Odpowiedzi:

81

Czy próbowałeś użyć kontrolera sc / service util? Rodzaj

sc create

w wierszu poleceń, a otrzymasz wpis pomocy. Myślę, że zrobiłem to w przeszłości dla Subversion i użyłem tego artykułu jako odniesienia:

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

jamesaharvey
źródło
5
Znalazłem tę stronę może być przydatna: http://journalofasoftwaredev.wordpress.com/2008/07/16/multiple-instances-of-same-windows-service/. Możesz wstawić kod do instalatora, aby uzyskać żądaną nazwę usługi po uruchomieniu installutil.
Vivian River
9
Link do bloga wordpress został zmieniony na: journalofasoftwaredev.wordpress.com/2008/07
STLDev
21
  sc create [servicename] binpath= [path to your exe]

To rozwiązanie zadziałało dla mnie.

Rajesh Kumar
źródło
5
po prostu wskazać; [path to your exe]musi być pełną ścieżką i nie zapomnij o spacji pobinpath=
mkb
2
To rzeczywiście pozwala na wielokrotne instalowanie usługi. Jednak wszystkie informacje dostarczone przez instalatora usługi. Opis funkcji, typ logowania itp. Są ignorowane
Noel Widmer
20

Możesz uruchomić wiele wersji tej samej usługi, wykonując następujące czynności:

1) Skopiuj plik wykonywalny usługi i konfigurację do własnego folderu.

2) Skopiuj plik Install.Exe do folderu wykonywalnego usługi (z folderu platformy .net)

3) Utwórz plik konfiguracyjny o nazwie Install.exe.config w folderze wykonywalnym usługi o następującej zawartości (unikalne nazwy usług):

<?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) Utwórz plik wsadowy, aby zainstalować usługę o następującej zawartości:

REM Install
InstallUtil.exe YourService.exe
pause

5) Będąc tam, utwórz plik wsadowy dezinstalacji

REM Uninstall
InstallUtil.exe -u YourService.exe
pause

EDYTOWAĆ:

Pamiętaj, że jeśli coś przegapiłem, oto klasa ServiceInstaller (dostosuj zgodnie z wymaganiami):

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
źródło
Myślę, że to, co opisujesz, jest mniej więcej tym, co zrobiłem, zezwalając na ustawienie ServiceName i DisplayName z moich usług app.config. Próbowałem tego, co opisujesz, ale niestety spowodowało to ten sam problem, co w moim pytaniu.
Switters
Mam taki szablon, którego używam od wieków, więc może coś przeoczyłem, jak wygląda twoja klasa ServiceInstaller, opublikuje kopię roboczą tego, którego używam, daj mi znać, czy to pomaga?
Mark Redman
Nasi instalatorzy usług są w rzeczywistości prawie identyczni. Używam klasy statycznej do ładowania usługi i wyświetlania nazw z pliku konfiguracyjnego, ale poza tym są one bardzo podobne. Domyślam się, dlaczego to nie działa w moim przypadku, że może być coś dziwnego w naszym kodzie usługi. Niestety, wiele rąk było na tym. Z tego co rozumiem, twoja odpowiedź powinna zadziałać w większości przypadków dzięki za pomoc.
Switters
2
Ogromne dzięki za pomoc. Myślę, że plik konfiguracyjny instalacji musi nosić nazwę InstallUtil.exe.confg, a nie Install.exe.config dla pliku InstallUtil.exe
NullReference
Fajne podejście, które całkowicie działa. Dzieje się tak, jeśli wiesz, który plik InstallUtil.exe skopiować do folderu instalacyjnego (osobiście mam zainstalowanych mnóstwo wersji frameworka, co jest pogarszane przez kopie 64-bitowe). Utrudniłoby to wyjaśnienie zespołowi Helpdesk, jeśli wykonują instalacje. Ale jak na instalację prowadzoną przez dewelopera jest bardzo elegancki.
timmi4sa
11

Stare pytanie, wiem, ale miałem szczęście, używając opcji / nazwa usługi w programie InstallUtil.exe. Nie widzę tego jednak we wbudowanej pomocy.

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

Nie jestem do końca pewien, gdzie po raz pierwszy o tym przeczytałem, ale od tamtej pory tego nie widziałem. YMMV.

Jonathon Watney
źródło
3
Zwraca ten błąd:An exception occurred during the Install phase. System.ComponentModel.Win32Exception: The specified service already exists
mkb
@mkb Czy masz inną usługę o nazwie „Moja usługa”?
Jonathon Watney
Tak, jak w pytaniu mam jedną usługę, ten sam plik wykonywalny, ale chcę zainstalować dwie jej instancje, każda z inną konfiguracją. Kopiuję i wklejam exe usługi, ale ten nie działał.
mkb
1
/ servicename = "My Service InstanceOne" i / servicename = "My Service InstanceTwo" Nazwy muszą być unikalne.
granadaCoder
11

Innym szybkim sposobem określenia wartości niestandardowej dla ServiceNamei DisplayNamejest użycie installutilparametrów wiersza polecenia.

  1. W Twojej ProjectInstallerklasie zastąp metody wirtualne Install(IDictionary stateSaver)iUninstall(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. Zbuduj swój projekt
  3. Zainstaluj usługę, installutildodając własną nazwę za pomocą /servicenameparametru:

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

Należy pamiętać, że jeśli nie określisz /servicenamew wierszu poleceń, usługa zostanie zainstalowana z wartościami ServiceName i DisplayName określonymi we właściwościach ProjectInstaller / config

Andrea
źródło
2
Znakomity!! Dziękuję - to było dokładnie to, czego potrzebowaliśmy i na temat.
Iofacture
7

Nie miałem szczęścia z powyższymi metodami, gdy korzystałem z naszego oprogramowania do automatycznego wdrażania do częstego instalowania / odinstalowywania usług systemu Windows obok siebie, ale w końcu wymyśliłem następujące, które pozwala mi przekazać parametr określający sufiks do nazwy usługi w wierszu poleceń. Pozwala również projektantowi na prawidłowe funkcjonowanie i można go łatwo dostosować, aby w razie potrzeby zastąpić całą nazwę.

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;
  }
}

Mając to na uwadze, mogę wykonać następujące czynności: Jeśli zadzwoniłem do usługi „Awesome Service”, mogę zainstalować wersję usługi UAT w następujący sposób:

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

Spowoduje to utworzenie usługi o nazwie „Awesome Service - UAT”. Użyliśmy tego do uruchomienia wersji DEVINT, TESTING i ACCEPTANCE tej samej usługi działającej równolegle na jednym komputerze. Każda wersja ma swój własny zestaw plików / konfiguracji - nie próbowałem instalować wielu usług wskazujących na ten sam zestaw plików.

UWAGA: musisz użyć tego samego /ServiceSuffixparametru, aby odinstalować usługę, więc aby odinstalować, wykonaj następujące czynności:

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

tristankoffee
źródło
To świetnie, ale to tylko dla instalatora. Po uzyskaniu nowej nazwy instancji, skąd usługa Windows będzie wiedzieć o tej nowej nazwie? Czy musisz to przekazać przy budowie usługi Windows?
progLearner
Dzięki! Instalator ustawi nazwę w usłudze Windows podczas jej instalowania przy użyciu wartości ustawionych w metodzie SetNames () powyżej.
tristankoffee
Jasne, ale jak możesz ustawić tę nazwę ze świata zewnętrznego?
progLearner
W mojej odpowiedzi jest polecenie używane w wierszu poleceń, aby zainstalować (i odinstalować) usługę w świecie zewnętrznym. Wartość, do której przekazujesz, /ServiceSuffix="UAT"jest używana przez instalator do ustawiania sufiksu w usłudze. W moim przykładzie przekazana wartość to UAT. W moim scenariuszu chciałem po prostu dodać sufiks do istniejącej nazwy usługi, ale nie ma powodu, dla którego nie mógłbyś dostosować tego, aby całkowicie zastąpić nazwę przekazaną wartością.
tristankoffee
Dzięki, ale to jest wprowadzanie z wiersza poleceń (= ręczne wprowadzanie), a nie kod. Zgodnie z pierwotnym pytaniem: po uzyskaniu nowej nazwy instancji, w jaki sposób usługa Windows będzie wiedzieć o tej nowej nazwie? Czy musisz to przekazać przy budowie usługi Windows?
progLearner
4

Aby to zadziałało, zapisałem nazwę usługi i nazwę wyświetlaną w pliku app.config dla mojej usługi. Następnie w mojej klasie instalatora ładuję plik app.config jako XmlDocument i używam xpath, aby pobrać wartości i zastosować je do ServiceInstaller.ServiceName i ServiceInstaller.DisplayName, przed wywołaniem InitializeComponent (). Zakłada się, że nie ustawiłeś jeszcze tych właściwości w InitializeComponent (), w takim przypadku ustawienia z pliku konfiguracyjnego zostaną zignorowane. Poniższy kod jest tym, co wywołuję z mojego konstruktora klasy Instalatora, przed InitializeComponent ():

       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;
          }
      }

Nie wierzę, że czytanie pliku konfiguracyjnego bezpośrednio z ConfigurationManager.AppSettings lub czegoś podobnego będzie działało tak, jak po uruchomieniu instalatora, jest uruchamiany w kontekście InstallUtil.exe, a nie .exe Twojej usługi. Możesz być w stanie zrobić coś z ConfigurationManager.OpenExeConfiguration, jednak w moim przypadku to nie zadziałało, ponieważ próbowałem uzyskać niestandardową sekcję konfiguracji, która nie została załadowana.

chris.house.00
źródło
Cześć Chris House! Natknąłem się na twoją odpowiedź, ponieważ tworzę samoobsługowy interfejs API sieci Web oparty na OWIN wokół harmonogramu Quartz.NET i umieszczam go w usłudze systemu Windows. Całkiem zręcznie! Mam nadzieję, że czujesz się dobrze!
NovaJoe
Cześć Chris House! Natknąłem się na twoją odpowiedź, ponieważ tworzę samoobsługowy interfejs API sieci Web oparty na OWIN wokół harmonogramu Quartz.NET i umieszczam go w usłudze systemu Windows. Całkiem zręcznie! Mam nadzieję, że czujesz się dobrze!
NovaJoe
4

Wystarczy, aby poprawić doskonałą odpowiedź @ chris.house.00 tego , można rozważyć następujące funkcje odczytu z ustawieniami aplikacji:

 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
źródło
2

Miałem podobną sytuację, w której potrzebowałem poprzedniej usługi i zaktualizowanej usługi działającej obok siebie na tym samym serwerze. (To było coś więcej niż tylko zmiana bazy danych, to także zmiany kodu). Nie mogłem więc po prostu dwukrotnie uruchomić tego samego pliku .exe. Potrzebowałem nowego pliku .exe, który został skompilowany z nowymi bibliotekami DLL, ale z tego samego projektu. Sama zmiana nazwy usługi i nazwy wyświetlanej usługi nie zadziałała. Nadal otrzymuję komunikat „usługa już istnieje”, co moim zdaniem wynika z tego, że używam projektu wdrożeniowego. To, co w końcu zadziałało, to w moich właściwościach projektu wdrożeniowego znajduje się właściwość o nazwie „ProductCode”, która jest Guid.

wprowadź opis obrazu tutaj

Następnie pomyślnie odbuduj projekt instalacji do nowego zainstalowanego pliku .exe lub .msi.

cmartin
źródło
1

Najprostszym podejściem jest oparcie nazwy usługi na nazwie biblioteki DLL:

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
źródło