Nous avons donc créé un service Windows pour fournir des données à notre application cliente 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'ici, je n'ai pas réussi à faire en sorte que cela se produise et espérais que mes collègues membres de stackoverflow pourraient donner quelques indices sur les raisons.
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ée pour définir le nom du service en fonction d'une clé dans App.config, :
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 depuis le fichier de configuration.
À partir de maintenant, j'ai essayé deux méthodes différentes pour installer les deux services et les deux ont échoué de manière identique.
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 d'application pour modifier le nom du service souhaité:
InstallUtil.exe /i AppService.exe
Lorsque cela ne fonctionnait pas, j'ai essayé de créer un deuxième projet d'installation, édité le fichier de configuration et construit le deuxième programme d'installation. Lorsque j'ai exécuté le programme d'installation, cela a bien fonctionné, mais le service ne s'est pas affiché 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 de InstallUtil (parties pertinentes uniquement):
Exécuter une installation transactionnelle.
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 du service d’application source EventLog Two dans le journal Application ...
Une exception s'est produite pendant la phase d'installation. System.NullReferenceException: La référence à un 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 supprimé du système ... Service App Service Two a été supprimé du système.
La phase de restauration s'est terminée avec succès.
L'installation effectuée est terminée. L'installation a échoué et la restauration a été effectuée.
Désolé pour le long message, je voulais m'assurer qu'il y avait suffisamment d'informations pertinentes. La pièce qui m'a jusqu'ici laissé perplexe est qu'elle indique que l'installation du service se termine avec succès et que ce n'est qu'après la création de la source EventLog que l'exception NullReferenceException semble avoir été levée. Donc, si quelqu'un sait ce que je fais de mal ou a une meilleure approche, ce serait très apprécié.
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 déjà fait cela pour Subversion et avoir utilisé cet article comme référence:
http://svn.Apache.org/repos/asf/Subversion/trunk/notes/windows-service.txt
Vous pouvez exécuter plusieurs versions du même service en procédant comme suit:
1) Copiez l’exécutable du service et configurez-le 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
EDIT:
Notez bien que si j'ai manqué quelque chose, voici la classe ServiceInstaller (à ajuster 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());
}
}
}
}
sc create [servicename] binpath= [path to your exe]
Cette solution a fonctionné pour moi.
Vieille question, je le sais, mais j'ai eu de la chance en utilisant l'option/servicename sur InstallUtil.exe. Je ne le vois cependant pas répertorié dans l'aide intégrée.
InstallUtil.exe /servicename="My Service" MyService.exe
Je ne sais pas trop où j'ai lu pour la première fois à ce sujet, mais je ne l'ai pas vu depuis. YMMV.
Un autre moyen rapide de spécifier une valeur personnalisée pour ServiceName
et DisplayName
consiste à utiliser les paramètres de ligne de commande installutil
.
Dans votre classe ProjectInstaller
, remplacez les méthodes virtuelles Install(IDictionary stateSaver)
et Uninstall(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;
}
}
Installez le service avec installutil
en ajoutant votre nom personnalisé en utilisant /servicename
paramètre:
installutil.exe /servicename="CustomServiceName" "c:\pathToService\SrvcExecutable.exe"
Veuillez noter que si vous ne spécifiez pas /servicename
dans la ligne de commande, le service sera installé avec les valeurs ServiceName et DisplayName spécifiées dans ProjectInstaller properties/config
Je n’ai pas eu beaucoup de chance avec les méthodes ci-dessus lors de l’utilisation fréquente de notre logiciel de déploiement automatisé pour installer/désinstaller des services Windows côte à côte, mais j’ai finalement trouvé 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 complet 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;
}
}
En gardant cela à l'esprit, je peux effectuer les opérations suivantes: 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 s'exécutant côte à côte sur un seul ordinateur. Chaque version a son propre ensemble de fichiers/configurations - je n'ai pas essayé d'installer des services multiples pointant sur le même ensemble de fichiers.
NOTE: vous devez utiliser le même /ServiceSuffix
paramètre pour désinstaller le service, vous devez donc exécuter les opérations suivantes pour désinstaller:
InstallUtil.exe /u /ServiceSuffix="UAT" MyService.exe
Juste pour améliorer la réponse parfaite de @ chris.house.00 this , vous pouvez envisager de lire la fonction suivante à partir des 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";
}
}
Ce que j'ai fait pour que cela fonctionne est de stocker le nom du service et son nom d'affichage dans un fichier app.config pour mon service. Ensuite, dans ma classe d'installation, je charge le fichier app.config en tant que XmlDocument et j'utilise xpath pour extraire les valeurs et les appliquer à ServiceInstaller.ServiceName et ServiceInstaller.DisplayName avant d'appeler InitializeComponent (). Cela suppose que vous ne définissiez 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 correspond à ce que j'appelle à partir du constructeur de ma classe d'installateur, 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 lire le fichier de configuration directement à partir de ConfigurationManager.AppSettings ou de quelque chose de similaire fonctionnera comme lorsque le programme d'installation est exécuté, il s'exécute dans le contexte de InstallUtil.exe, pas du fichier .exe de votre service. Vous pourrez peut-être faire quelque chose avec ConfigurationManager.OpenExeConfiguration, mais dans mon cas, cela ne fonctionnait pas car j'essayais d'obtenir une section de configuration personnalisée qui n'était pas chargée.
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 un changement de code). Je ne pouvais donc pas exécuter le même fichier .exe deux fois. J'avais besoin d'un nouveau fichier .exe compilé avec de nouvelles DLL, mais du même projet. Changer juste le nom du service et le nom d'affichage du service ne fonctionnait pas pour moi, j'ai quand même reçu l'erreur "Le service existait déjà", ce qui, je pense, est dû au fait que j'utilise un projet de déploiement. Ce qui a finalement fonctionné pour moi est dans mes propriétés de projet de déploiement, une propriété appelée "ProductCode" qui est un Guid.
Après cela, reconstruisez le projet d'installation sur un nouveau fichier .exe ou .msi installé avec succès.
L'approche la plus simple consiste à baser 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;
}