在服务器上安装同一个Windows服务的多个实例
所以我们制作了一个windows服务来向我们的客户端应用程序提供数据,一切都很顺利。 客户端提出了一个有趣的configuration请求,要求在相同的服务器上运行此服务的两个实例,并将其configuration为指向不同的数据库。
到目前为止,我还没有能够得到这个发生,并希望我的同胞stackoverflow成员可能会提供一些提示为什么。
当前设置:
我已经设置了包含windows服务的项目,我们将从现在开始将其称为AppService,以及处理自定义安装步骤的ProjectInstaller.cs文件,以基于App.config中的键设置服务名称:
this.serviceInstaller1.ServiceName = Util.ServiceName; this.serviceInstaller1.DisplayName = Util.ServiceName; this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.LocalSystem;
在这种情况下,Util只是一个静态类,用于从configuration文件中加载服务名称。
从这里开始,我尝试了两种不同的方式来安装这两种服务,并且都以相同的方式失败。
第一种方法是简单地安装服务的第一个副本,复制已安装的目录并将其重命名,然后在修改应用configuration以更改所需的服务名称后运行以下命令:
InstallUtil.exe /i AppService.exe
当没有工作,我试图创build第二个安装程序项目,编辑configuration文件,并build立第二个安装程序。 当我运行安装程序时,它工作正常,但服务没有显示在services.msc,所以我跑了以前的命令对第二个安装的代码库。
两次我从InstallUtil(仅相关部分)收到以下输出:
运行交易安装。
开始安装的安装阶段。
安装服务应用程序服务二…服务应用程序服务二已成功安装。 创buildEventLog源应用程序服务两个日志中的应用程序…
安装阶段发生exception。 System.NullReferenceException:未将对象引用设置为对象的实例。
安装的回滚阶段开始。
将事件日志恢复到源应用服务二的先前状态。 服务应用程序服务两个正在从系统中删除…服务应用程序服务两个已成功从系统中删除。
回滚阶段已成功完成。
交易安装已完成。 安装失败,回滚已执行。
对不起,这个冗长的post,想确保有足够的相关信息。 到目前为止我已经坚持的一件事是,它表明,服务的安装成功完成,并且只有在创buildNullReferenceException似乎被引发的EventLog源之后。 所以如果有人知道我在做什么错误或有一个更好的办法,将不胜感激。
你有没有试过sc / service controller util? types
sc create
在命令行,它会给你帮助条目。 我认为我已经在Subversion的过去做过这个,并且使用这篇文章作为参考:
http://svn.apache.org/repos/asf/subversion/trunk/notes/windows-service.txt
通过执行以下操作,可以运行同一服务的多个版本:
1)将Service可执行文件和configuration文件复制到它自己的文件夹中。
2)将Install.Exe复制到服务可执行文件夹(从.net框架文件夹)
3)在服务可执行文件夹中创build一个名为Install.exe.config的configuration文件,其内容如下(唯一的服务名称):
<?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)创build一个batch file来安装具有以下内容的服务:
REM Install InstallUtil.exe YourService.exe pause
5)当你在那里,创build一个卸载batch file
REM Uninstall InstallUtil.exe -u YourService.exe pause
编辑:
注意如果我错过了一些东西,这里是ServiceInstaller类(根据需要调整):
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]
这个解决scheme为我工作。
老问题,我知道,但我运气使用InstallUtil.exe上的/ servicename选项。 我没有看到它在内置的帮助中列出。
InstallUtil.exe /servicename="My Service" MyService.exe
我不完全确定我在哪里读到这个,但是从那以后我还没有看到。 因人而异。
我所做的工作就是将服务名称和显示名称存储在我的服务的app.config中。 然后在我的安装程序类中,我将app.config作为XmlDocument加载,并使用xpath获取值并将其应用于ServiceInstaller.ServiceName和ServiceInstaller.DisplayName,然后调用InitializeComponent()。 这假定你还没有在InitializeComponent()中设置这些属性,在这种情况下,configuration文件中的设置将被忽略。 下面的代码就是我在InitializeComponent()之前从我的installer类构造函数调用的代码:
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; } }
我不相信直接从configuration文件ConfigurationManager.AppSettings读取configuration文件或类似的工作,当安装程序运行,它运行在InstallUtil.exe的上下文中,而不是您的服务的.exe。 你可能可以用ConfigurationManager.OpenExeConfiguration做些事情,但是在我的情况下,这不起作用,因为我试图得到一个未加载的自定义configuration节。
使用我们的自动化部署软件来频繁地安装/卸载并排的Windows服务时,我没有太多的运气,但是我最终想出了以下内容,它允许我传入一个参数来指定一个后缀到命令行上的服务名称。 它还允许devise人员正确运行,并可以根据需要轻松调整以覆盖整个名称。
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; } }
考虑到这一点,我可以做到以下几点:如果我已经调用了服务“Awesome Service”,那么我可以按如下方式安装服务的UAT版本:
InstallUtil.exe /ServiceSuffix="UAT" MyService.exe
这将创build名称为“Awesome Service – UAT”的服务。 我们已经使用它来运行同一个服务在同一台机器上并行运行的DEVINT,TESTING和ACCEPTANCE版本。 每个版本都有自己的一组文件/configuration – 我没有试过这个安装多个服务指向相同的一组文件。
注:您必须使用相同的/ServiceSuffix
参数来卸载服务,所以您需要执行以下操作来卸载:
InstallUtil.exe /u /ServiceSuffix="UAT" MyService.exe
只是为了提高@ chris.house.00 这个完美的答案,你可以考虑下面的函数来读取你的应用程序设置:
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"; } }