Installazione di più istanze dello stesso servizio Windows su un server


96

Quindi abbiamo prodotto un servizio Windows per fornire dati alla nostra applicazione client e tutto sta andando alla grande. Il client ha escogitato una divertente richiesta di configurazione che richiede due istanze di questo servizio in esecuzione sullo stesso server e configurate per puntare a database separati.

Finora non sono stato in grado di farlo accadere e speravo che i miei colleghi membri di stackoverflow potessero essere in grado di fornire alcuni suggerimenti sul perché.

Configurazione corrente:

Ho impostato il progetto che contiene il servizio Windows, lo chiameremo AppService d'ora in poi, e il file ProjectInstaller.cs che gestisce i passaggi di installazione personalizzati per impostare il nome del servizio in base a una chiave in App.config in questo modo :

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

In questo caso Util è solo una classe statica che carica il nome del servizio dal file di configurazione.

Da qui in avanti ho provato due modi diversi per installare entrambi i servizi ed entrambi hanno fallito in modo identico.

Il primo modo era semplicemente installare la prima copia del servizio, copiare la directory installata e rinominarla, quindi eseguire il seguente comando dopo aver modificato la configurazione dell'app per cambiare il nome del servizio desiderato:

InstallUtil.exe /i AppService.exe

Quando non ha funzionato, ho provato a creare un secondo progetto di installazione, ho modificato il file di configurazione e ho creato il secondo programma di installazione. Quando ho eseguito il programma di installazione ha funzionato bene, ma il servizio non è stato visualizzato in services.msc, quindi ho eseguito il comando precedente sulla seconda base di codice installata.

Entrambe le volte ho ricevuto il seguente output da InstallUtil (solo parti rilevanti):

Esecuzione di un'installazione con transazione.

Inizio della fase di installazione dell'installazione.

Installazione del servizio App Service Two ... Il servizio App Service Two è stato installato correttamente. Creazione del servizio app di origine EventLog due nell'applicazione di registro in corso ...

Si è verificata un'eccezione durante la fase di installazione. System.NullReferenceException: riferimento oggetto non impostato su un'istanza di un oggetto.

La fase di rollback dell'installazione sta iniziando.

Ripristino del registro eventi allo stato precedente per il servizio app di origine due. Il servizio app servizio due è stato rimosso dal sistema ... il servizio app servizio due è stato rimosso correttamente dal sistema.

La fase di rollback è stata completata correttamente.

L'installazione transata è stata completata. L'installazione non è riuscita e il rollback è stato eseguito.

Ci scusiamo per il post prolisso, volevo assicurarmi che ci fossero abbastanza informazioni pertinenti. Il pezzo che finora mi ha lasciato perplesso è che afferma che l'installazione del servizio viene completata con successo e solo dopo che va a creare la sorgente EventLog che sembra essere generata l'eccezione NullReferenceException. Quindi, se qualcuno sa cosa sto sbagliando o ha un approccio migliore, sarebbe molto apprezzato.

Risposte:


81

Hai provato l'utilità del controller sc / service? genere

sc create

in una riga di comando e ti darà la voce della guida. Penso di averlo fatto in passato per Subversion e ho usato questo articolo come riferimento:

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


5
Ho trovato questa pagina per essere utile: http://journalofasoftwaredev.wordpress.com/2008/07/16/multiple-instances-of-same-windows-service/. Puoi inserire il codice nel programma di installazione per ottenere il nome del servizio che desideri quando esegui installutil.
Vivian River

9
Il collegamento al blog wordpress è stato modificato in: journalofasoftwaredev.wordpress.com/2008/07
STLDev

21
  sc create [servicename] binpath= [path to your exe]

Questa soluzione ha funzionato per me.


5
solo per sottolineare; [path to your exe]deve essere percorso completo e non dimenticare lo spazio dopobinpath=
mkb

2
Ciò consente effettivamente a un servizio di essere installato più volte. Tuttavia, tutte le informazioni fornite dall'installatore del servizio. La descrizione di Fe, il tipo di accesso, ecc. Vengono ignorati
Noel Widmer

20

È possibile eseguire più versioni dello stesso servizio procedendo come segue:

1) Copia l'eseguibile del servizio e config nella sua cartella.

2) Copia Install.Exe nella cartella eseguibile del servizio (dalla cartella .net framework)

3) Creare un file di configurazione denominato Install.exe.config nella cartella eseguibile del servizio con i seguenti contenuti (nomi di servizio univoci):

<?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) Creare un file batch per installare il servizio con i seguenti contenuti:

REM Install
InstallUtil.exe YourService.exe
pause

5) Mentre sei lì, crea un file batch di disinstallazione

REM Uninstall
InstallUtil.exe -u YourService.exe
pause

MODIFICARE:

Nota che se mi sono perso qualcosa, ecco la classe ServiceInstaller (aggiusta come richiesto):

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

Penso che quello che stai descrivendo sia più o meno quello che ho fatto consentendo a ServiceName e DisplayName di essere impostati dai miei servizi app.config Ho provato quello che descrivi ma sfortunatamente ha provocato lo stesso problema elencato nella mia domanda.
Switters

Ho un modello che uso, che ho usato per anni, quindi forse mi sono perso qualcosa, com'è la tua classe ServiceInstaller, posterò una copia funzionante di quella che uso, fammi sapere che questo aiuta?
Mark Redman

I nostri installatori di servizi sono in realtà quasi identici. Uso una classe statica per caricare il servizio e visualizzare i nomi dal file di configurazione, ma a parte questo sono molto simili. La mia ipotesi sul motivo per cui non funziona per me è che potrebbe esserci qualcosa di strano nel codice del nostro servizio. Sfortunatamente ci sono state molte mani sopra. Da quanto ho capito, la tua risposta dovrebbe funzionare nella maggior parte dei casi, grazie per l'aiuto.
Switters

2
Enorme aiuto grazie. Penso che il file di configurazione dell'installazione debba essere chiamato InstallUtil.exe.confg non Install.exe.config per InstallUtil.exe
NullReference

Un bel approccio che funziona perfettamente. Cioè se sai quale InstallUtil.exe copiare nella tua cartella di installazione (personalmente ho tonnellate di versioni di framework installate che sono esacerbate dalle copie a 64 bit). Ciò renderebbe piuttosto difficile spiegare al team di Helpdesk se eseguono le installazioni. Ma per un'installazione guidata dallo sviluppatore è molto elegante.
timmi4sa

11

Vecchia domanda, lo so, ma ho avuto fortuna usando l'opzione / servicename su InstallUtil.exe. Tuttavia non lo vedo elencato nella guida integrata.

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

Non sono del tutto sicuro di dove l'ho letto per la prima volta, ma da allora non l'ho più visto. YMMV.


3
Restituisce questo errore:An exception occurred during the Install phase. System.ComponentModel.Win32Exception: The specified service already exists
mkb

@mkb Hai un altro servizio chiamato "My Service"?
Jonathon Watney,

Sì, poiché nella domanda ho un servizio, lo stesso eseguibile, ma voglio installarne due istanze, ciascuna con una configurazione diversa. Ho copiato e incollato l'exe del servizio ma questo non ha funzionato.
mkb

1
/ servicename = "My Service InstanceOne" e / servicename = "My Service InstanceTwo" I nomi devono essere univoci.
granadaCoder

11

Un altro modo rapido per specificare un valore personalizzato per ServiceNamee DisplayNameutilizza i installutilparametri della riga di comando.

  1. Nella tua ProjectInstallerclasse sovrascrivi i metodi virtuali Install(IDictionary stateSaver)eUninstall(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. Costruisci il tuo progetto
  3. Installa il servizio installutilaggiungendo il tuo nome personalizzato utilizzando il /servicenameparametro:

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

Si noti che se non si specifica /servicenamenella riga di comando il servizio verrà installato con i valori ServiceName e DisplayName specificati in ProjectInstaller properties / config


2
Brillante!! Grazie - questo era esattamente ciò di cui avevamo bisogno e al punto.
Iofacture

7

Non ho avuto molta fortuna con i metodi di cui sopra quando ho utilizzato il nostro software di distribuzione automatizzata per installare / disinstallare frequentemente i servizi Windows affiancati, ma alla fine ho trovato quanto segue che mi consente di passare un parametro per specificare un suffisso al nome del servizio sulla riga di comando. Consente inoltre al designer di funzionare correttamente e potrebbe essere facilmente adattato per sostituire l'intero nome, se necessario.

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

Con questo in mente, posso fare quanto segue: Se ho chiamato il servizio "Awesome Service", posso installare una versione UAT del servizio come segue:

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

Questo creerà il servizio con il nome "Awesome Service - UAT". Lo abbiamo utilizzato per eseguire le versioni DEVINT, TESTING e ACCEPTANCE dello stesso servizio in esecuzione fianco a fianco su una singola macchina. Ogni versione ha il proprio set di file / configurazioni: non l'ho provato per installare più servizi che puntano allo stesso set di file.

NOTA: devi usare lo stesso /ServiceSuffixparametro per disinstallare il servizio, quindi dovresti eseguire quanto segue per disinstallare:

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


È fantastico, ma è solo per l'installatore. Una volta che hai un nuovo nome di istanza, come farà il servizio Windows a sapere di questo nuovo nome? Devi trasmetterlo alla costruzione del servizio Windows?
progLearner

Grazie! Il programma di installazione imposterà il nome sul servizio Windows durante l'installazione utilizzando i valori impostati nel metodo SetNames () sopra.
Tristankoffee

Certo, ma come puoi impostare questo nome dal mondo esterno?
progLearner

Nella mia risposta c'è il comando utilizzato dalla riga di comando per installare (e disinstallare) il servizio nel mondo esterno. Il valore che si passa /ServiceSuffix="UAT"viene utilizzato dall'installatore per impostare il suffisso sul servizio. Nel mio esempio, il valore passato è UAT. Nel mio scenario volevo solo aggiungere un suffisso al nome esistente del servizio, ma non c'è motivo per cui non potresti adattarlo per sostituire completamente il nome con il valore che è passato.
tristankoffee

Grazie, ma questo è un input della riga di comando (= input manuale), non un codice. Come da domanda originale: una volta che hai un nuovo nome di istanza, come farà il servizio Windows a conoscere questo nuovo nome? Devi trasmetterlo alla costruzione del servizio Windows?
progLearner

4

Quello che ho fatto per far funzionare questo è archiviare il nome del servizio e il nome visualizzato in un app.config per il mio servizio. Quindi nella mia classe di installazione, carico app.config come XmlDocument e uso xpath per ottenere i valori e applicarli a ServiceInstaller.ServiceName e ServiceInstaller.DisplayName, prima di chiamare InitializeComponent (). Ciò presuppone che tu non stia già impostando queste proprietà in InitializeComponent (), nel qual caso le impostazioni dal tuo file di configurazione verranno ignorate. Il codice seguente è quello che sto chiamando dal costruttore della mia classe di installazione, prima di 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;
          }
      }

Non credo che la lettura del file di configurazione direttamente da ConfigurationManager.AppSettings o qualcosa di simile funzionerà come quando viene eseguito il programma di installazione, viene eseguito nel contesto di InstallUtil.exe, non nel .exe del servizio. Potresti essere in grado di fare qualcosa con ConfigurationManager.OpenExeConfiguration, tuttavia nel mio caso, questo non ha funzionato perché stavo cercando di ottenere una sezione di configurazione personalizzata che non è stata caricata.


Ciao Chris House! Sono incappato nella tua risposta perché sto costruendo un'API Web basata su OWIN self-hosted attorno allo scheduler Quartz.NET e inserendola in un servizio Windows. Abbastanza lucido! Spero che tu stia bene!
NovaJoe

Ciao Chris House! Sono incappato nella tua risposta perché sto costruendo un'API Web basata su OWIN self-hosted attorno allo scheduler Quartz.NET e inserendola in un servizio Windows. Abbastanza lucido! Spero che tu stia bene!
NovaJoe

4

Proprio per migliorare la risposta perfetta di @ chris.house.00 questo , si può considerare la funzione seguente per leggere da impostazioni dell'app:

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

2

Ho avuto una situazione simile, in cui dovevo avere un servizio precedente e un servizio aggiornato in esecuzione fianco a fianco sullo stesso server. (Era più di una semplice modifica del database, erano anche modifiche al codice). Quindi non potevo eseguire lo stesso .exe due volte. Avevo bisogno di un nuovo .exe compilato con nuove DLL ma dallo stesso progetto. La semplice modifica del nome del servizio e del nome visualizzato del servizio non ha funzionato per me, ho comunque ricevuto l'errore "servizio già esistente" che credo sia dovuto al fatto che sto utilizzando un progetto di distribuzione. Quello che alla fine ha funzionato per me è che all'interno del mio Deployment Project Properties c'è una proprietà chiamata "ProductCode" che è una Guid.

inserisci qui la descrizione dell'immagine

Successivamente, ricostruire il progetto di installazione in un nuovo .exe o .msi installato correttamente.


1

L'approccio più semplice è quello di basare il nome del servizio sul nome della 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;
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.