Installation de plusieurs instances du même service Windows sur un serveur

96

Nous avons donc produit un service Windows pour fournir des données à notre application client et tout se passe bien. Le client a présenté une demande de configuration amusante qui nécessite deux instances de ce service s'exécutant sur le même serveur et configurées pour pointer vers des bases de données distinctes.

Jusqu'à présent, je n'ai pas été en mesure de faire en sorte que cela se produise et j'espérais que mes collègues membres de stackoverflow pourraient être en mesure de vous expliquer pourquoi.

Configuration actuelle:

J'ai configuré le projet qui contient le service Windows, nous l'appellerons désormais AppService, et le fichier ProjectInstaller.cs qui gère les étapes d'installation personnalisées pour définir le nom du service en fonction d'une clé dans App.config comme suit :

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

Dans ce cas, Util est juste une classe statique qui charge le nom du service à partir du fichier de configuration.

À partir de maintenant, j'ai essayé deux façons différentes d'installer les deux services et les deux ont échoué de la même manière.

La première méthode consistait simplement à installer la première copie du service, à copier le répertoire installé et à le renommer, puis à exécuter la commande suivante après avoir modifié la configuration de l'application pour changer le nom du service souhaité:

InstallUtil.exe /i AppService.exe

Lorsque cela n'a pas fonctionné, j'ai essayé de créer un deuxième projet d'installation, modifié le fichier de configuration et construit le deuxième programme d'installation. Lorsque j'ai exécuté le programme d'installation, cela fonctionnait bien mais le service ne s'affichait pas dans services.msc, j'ai donc exécuté la commande précédente sur la deuxième base de code installée.

Les deux fois, j'ai reçu la sortie suivante d'InstallUtil (parties pertinentes uniquement):

Exécution d'une installation traitée.

Début de la phase d'installation de l'installation.

Installation du service App Service Two ... Le service App Service Two a été installé avec succès. Création de l'application EventLog source App Service Two dans l'application de journal ...

Une exception s'est produite lors de la phase d'installation. System.NullReferenceException: La référence d'objet n'est pas définie sur une instance d'un objet.

La phase de restauration de l'installation commence.

Restauration du journal des événements à l'état précédent pour App Service Two source. Service App Service Two est en cours de suppression du système ... Service App Service Two a été supprimé du système avec succès.

La phase de restauration s'est terminée avec succès.

L'installation traitée est terminée. L'installation a échoué et la restauration a été effectuée.

Désolé pour le long post, je voulais m'assurer qu'il y avait suffisamment d'informations pertinentes. L'élément qui m'a jusqu'à présent perplexe est qu'il indique que l'installation du service se termine avec succès et que ce n'est qu'après avoir créé la source EventLog que l'exception NullReferenceException semble être lancée. Donc, si quelqu'un sait ce que je fais de mal ou a une meilleure approche, ce serait très apprécié.

Switters
la source

Réponses:

81

Avez-vous essayé l'utilitaire sc / service controller? Type

sc create

à une ligne de commande, et il vous donnera l'entrée d'aide. Je pense avoir fait cela dans le passé pour Subversion et utilisé cet article comme référence:

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

Jamesaharvey
la source
5
J'ai trouvé cette page utile: http://journalofasoftwaredev.wordpress.com/2008/07/16/multiple-instances-of-same-windows-service/. Vous pouvez insérer du code dans le programme d'installation pour obtenir le nom de service souhaité lorsque vous exécutez installutil.
Vivian River
9
Le lien vers le blog wordpress a été changé en: journalofasoftwaredev.wordpress.com/2008/07
STLDev
21
  sc create [servicename] binpath= [path to your exe]

Cette solution a fonctionné pour moi.

Rajesh Kumar
la source
5
juste pour souligner; [path to your exe]doit être chemin complet et n'oubliez pas l'espace aprèsbinpath=
mkb
2
Cela permet en effet à un service d'être installé plusieurs fois. Cependant, toutes les informations fournies par l'installateur du service. La description de Fe, le type de connexion, etc. est ignoré
Noel Widmer
20

Vous pouvez exécuter plusieurs versions du même service en procédant comme suit:

1) Copiez l'exécutable et la configuration du service dans son propre dossier.

2) Copiez Install.Exe dans le dossier exécutable du service (à partir du dossier .net framework)

3) Créez un fichier de configuration appelé Install.exe.config dans le dossier exécutable du service avec le contenu suivant (noms de service uniques):

<?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) Créez un fichier de commandes pour installer le service avec le contenu suivant:

REM Install
InstallUtil.exe YourService.exe
pause

5) Pendant que vous y êtes, créez un fichier batch de désinstallation

REM Uninstall
InstallUtil.exe -u YourService.exe
pause

ÉDITER:

Notez que si j'ai manqué quelque chose, voici la classe ServiceInstaller (ajustez si nécessaire):

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
la source
Je pense que ce que vous décrivez est plus ou moins ce que j'ai fait en permettant au ServiceName et au DisplayName d'être définis à partir de mes services app.config.J'ai essayé ce que vous décrivez, mais malheureusement, cela a abouti au même problème que celui répertorié dans ma question.
Switters
J'ai en quelque sorte un modèle que j'utilise, que j'utilise depuis des lustres, alors peut-être que j'ai manqué quelque chose, à quoi ressemble votre classe ServiceInstaller, publierai une copie de travail de celui que j'utilise, faites-moi savoir si cela aide?
Mark Redman
Nos installateurs de service sont en fait presque identiques. J'utilise une classe statique pour charger le service et afficher les noms du fichier de configuration, mais à part cela, ils sont très similaires. Je suppose que cela ne fonctionne pas pour moi, c'est qu'il peut y avoir quelque chose d'un peu particulier dans notre code de service. Beaucoup de mains ont été dessus malheureusement. D'après ce que je comprends cependant, votre réponse devrait fonctionner dans la majorité des cas, merci pour l'aide.
Switters
2
Énorme aide merci. Je pense que le fichier de configuration d'installation doit être nommé InstallUtil.exe.confg et non Install.exe.config pour InstallUtil.exe
NullReference
Une belle approche qui fonctionne totalement. C'est si vous savez quel InstallUtil.exe copier dans votre dossier d'installation (j'ai personnellement des tonnes de versions de framework installées, ce qui est exacerbé par les copies 64 bits). Cela rendrait assez difficile d'expliquer à l'équipe du Helpdesk s'ils font les installations. Mais pour une installation dirigée par un développeur, c'est très élégant.
timmi4sa
11

Ancienne question, je sais, mais j'ai eu de la chance en utilisant l'option / servicename sur InstallUtil.exe. Cependant, je ne le vois pas répertorié dans l'aide intégrée.

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

Je ne sais pas exactement où j'ai lu pour la première fois à ce sujet, mais je ne l'ai pas vu depuis. YMMV.

Jonathon Watney
la source
3
Renvoie cette erreur:An exception occurred during the Install phase. System.ComponentModel.Win32Exception: The specified service already exists
mkb
@mkb Avez-vous un autre service appelé "Mon service"?
Jonathon Watney
Oui, comme dans la question, j'ai un service, le même exécutable, mais je souhaite en installer deux instances, chacune avec une configuration différente. J'ai copié-collé l'exe du service mais celui-ci n'a pas fonctionné.
mkb
1
/ servicename = "My Service InstanceOne" et / servicename = "My Service InstanceTwo" Les noms doivent être uniques.
granadaCoder
11

Un autre moyen rapide de spécifier une valeur personnalisée pour ServiceNameet DisplayNameconsiste à utiliser des installutilparamètres de ligne de commande.

  1. Dans votre ProjectInstallerclasse, remplacez les méthodes virtuelles Install(IDictionary stateSaver)etUninstall(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. Construisez votre projet
  3. Installez le service en installutilajoutant votre nom personnalisé à l'aide du /servicenameparamètre:

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

Veuillez noter que si vous ne spécifiez pas /servicenamedans la ligne de commande, le service sera installé avec les valeurs ServiceName et DisplayName spécifiées dans ProjectInstaller properties / config

Andrea
la source
2
Brillant!! Merci - c'était exactement ce dont nous avions besoin et au point.
Iofacture
7

Je n'ai pas eu beaucoup de chance avec les méthodes ci-dessus lors de l'utilisation de notre logiciel de déploiement automatisé pour installer / désinstaller fréquemment des services Windows côte à côte, mais j'ai finalement proposé ce qui suit qui me permet de passer un paramètre pour spécifier un suffixe au nom du service sur la ligne de commande. Il permet également au concepteur de fonctionner correctement et peut facilement être adapté pour remplacer le nom entier si nécessaire.

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

Dans cet esprit, je peux faire ce qui suit: Si j'ai appelé le service "Awesome Service", je peux installer une version UAT du service comme suit:

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

Cela créera le service avec le nom "Awesome Service - UAT". Nous l'avons utilisé pour exécuter les versions DEVINT, TESTING et ACCEPTANCE du même service côte à côte sur une seule machine. Chaque version a son propre ensemble de fichiers / configurations - je n'ai pas essayé cela pour installer plusieurs services pointant vers le même ensemble de fichiers.

REMARQUE: vous devez utiliser le même /ServiceSuffixparamètre pour désinstaller le service, vous devez donc exécuter ce qui suit pour désinstaller:

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

tristankoffee
la source
C'est génial, mais c'est juste pour l'installateur. Une fois que vous avez un nouveau nom d'instance, comment le service Windows connaîtra-t-il ce nouveau nom? Devez-vous le transmettre lors de la construction du service Windows?
progLearner le
Merci! Le programme d'installation définira le nom sur le service Windows lors de son installation en utilisant les valeurs définies dans la méthode SetNames () ci-dessus.
tristankoffee le
Bien sûr, mais comment pouvez-vous définir ce nom du monde extérieur?
progLearner
Dans ma réponse est la commande utilisée sur la ligne de commande pour installer (et désinstaller) le service dans le monde extérieur. La valeur que vous passez /ServiceSuffix="UAT"est utilisée par le programme d'installation pour définir le suffixe sur le service. Dans mon exemple, la valeur transmise est UAT. Dans mon scénario, je voulais juste ajouter un suffixe au nom existant du service, mais il n'y a aucune raison pour que vous ne puissiez pas l'adapter pour remplacer entièrement le nom par la valeur transmise.
tristankoffee
Merci, mais c'est une entrée de ligne de commande (= entrée manuelle), pas de code. Selon la question d'origine: une fois que vous avez un nouveau nom d'instance, comment le service Windows connaîtra-t-il ce nouveau nom? Devez-vous le transmettre lors de la construction du service Windows?
progLearner
4

Ce que j'ai fait pour que cela fonctionne, c'est de stocker le nom du service et le nom d'affichage dans un app.config pour mon service. Ensuite, dans ma classe d'installation, je charge app.config en tant que XmlDocument et utilise xpath pour obtenir les valeurs et les appliquer à ServiceInstaller.ServiceName et ServiceInstaller.DisplayName, avant d'appeler InitializeComponent (). Cela suppose que vous ne définissez pas déjà ces propriétés dans InitializeComponent (), auquel cas les paramètres de votre fichier de configuration seront ignorés. Le code suivant est ce que j'appelle de mon constructeur de classe d'installation, avant 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;
          }
      }

Je ne crois pas que la lecture du fichier de configuration directement à partir de ConfigurationManager.AppSettings ou quelque chose de similaire fonctionnera car lorsque le programme d'installation s'exécute, il est exécuté dans le contexte d'InstallUtil.exe, pas .exe de votre service. Vous pourrez peut-être faire quelque chose avec ConfigurationManager.OpenExeConfiguration, mais dans mon cas, cela n'a pas fonctionné car j'essayais d'accéder à une section de configuration personnalisée qui n'était pas chargée.

chris.house.00
la source
Salut Chris House! Je suis tombé sur votre réponse parce que je construis une API Web auto-hébergée basée sur OWIN autour du planificateur Quartz.NET et que je la colle dans un service Windows. Assez lisse! En espérant que tu vas bien!
NovaJoe
Salut Chris House! Je suis tombé sur votre réponse parce que je construis une API Web auto-hébergée basée sur OWIN autour du planificateur Quartz.NET et que je la colle dans un service Windows. Assez lisse! En espérant que tu vas bien!
NovaJoe
4

Juste pour améliorer la réponse parfaite de @ chris.house.00 ceci , vous pouvez envisager la fonction suivante pour lire les paramètres de votre application:

 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";
            }
        }
Shipahi Teoman
la source
2

J'ai eu une situation similaire, où je devais avoir un service précédent et un service mis à jour fonctionnant côte à côte sur le même serveur. (C'était plus qu'un simple changement de base de données, c'était aussi des changements de code). Je ne pouvais donc pas exécuter le même .exe deux fois. J'avais besoin d'un nouveau .exe compilé avec de nouvelles DLL mais à partir du même projet. Le simple fait de changer le nom du service et le nom d'affichage du service ne fonctionnait pas pour moi, je recevais quand même l'erreur "le service existait déjà", ce qui, je crois, est dû au fait que j'utilise un projet de déploiement. Ce qui a finalement fonctionné pour moi, c'est dans les propriétés de mon projet de déploiement, il y a une propriété appelée "ProductCode" qui est un Guid.

entrez la description de l'image ici

Après cela, la reconstruction du projet d'installation vers un nouveau .exe ou .msi installé avec succès.

cmartin
la source
1

L'approche la plus simple est basée le nom du service sur le nom de la 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
la source