在服务器上安装同一Windows服务的多个实例


96

因此,我们已经制作了一个Windows服务,可将数据提供给客户端应用程序,并且一切进展顺利。客户端提出了一个有趣的配置请求,该请求要求该服务的两个实例在同一服务器上运行,并配置为指向单独的数据库。

到目前为止,我还没有做到这一点,并希望我的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只是一个静态类,可以从配置文件中加载服务名称。

从这里开始,我尝试了两种不同的方法来安装两个服务,并且都以相同的方式失败了。

第一种方法是简单地安装服务的第一个副本,复制安装的目录并重命名它,然后在修改应用程序配置以更改所需的服务名称后运行以下命令:

InstallUtil.exe /i AppService.exe

如果不起作用,我尝试创建第二个安装程序项目,编辑配置文件并构建第二个安装程序。当我运行安装程序时,它运行良好,但该服务未显示在services.msc中,因此我针对第二个已安装的代码库运行了先前的命令。

两次我都从InstallUtil收到以下输出(仅相关部分):

运行事务处理的安装。

开始安装的安装阶段。

正在安装service App Service Two ... Service App Service Two已成功安装。在日志应用程序中创建EventLog源App Service二...

在安装阶段发生异常。System.NullReferenceException:对象引用未设置为对象的实例。

安装的回滚阶段开始。

将事件日志恢复到源应用程序服务二的先前状态。正在从系统中删除Service App服务二...已成功从系统中删除Service App服务二。

回滚阶段已成功完成。

事务处理安装已完成。安装失败,并且已执行回滚。

很抱歉,冗长的帖子,希望确保有足够的相关信息。到目前为止,我感到很困惑的一点是,它指出该服务的安装成功完成,并且只有在它创建EventLog源之后才抛出NullReferenceException。因此,如果有人知道我在做什么错或有更好的方法,将不胜感激。

Answers:


81

您是否尝试过sc / service controller util?类型

sc create

在命令行上,它将为您提供帮助。我认为我过去在Subversion中已经做到这一点,并将本文用作参考:

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


5
我发现此页面非常有用:http://journalofasoftwaredev.wordpress.com/2008/07/16/multiple-instances-of-same-windows-service/。您可以将代码插入安装程序中,以获取运行installutil时所需的服务名称。
维维安河

9
指向wordpress博客的链接已更改为:journalofasoftwaredev.wordpress.com/2008/07
STLDev

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

这个解决方案对我有用。


5
只是指出;[path to your exe]必须是完整的路径并且不要忘记之后的空间binpath=
mkb

2
实际上,这确实允许多次安装服务。但是,服务安装程序提供的所有信息。Fe描述,登录类型等
均被

20

您可以通过执行以下操作来运行同一服务的多个版本:

1)将服务可执行文件和配置复制到其自己的文件夹中。

2)将Install.Exe复制到服务可执行文件夹(来自.net framework文件夹)

3)在服务可执行文件文件夹中创建一个名为Install.exe.config的配置文件,其中包含以下内容(唯一的服务名称):

<?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)创建一个批处理文件以安装服务,其中包含以下内容:

REM Install
InstallUtil.exe YourService.exe
pause

5)在您在那里时,创建一个卸载批处理文件

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

我认为您正在描述的内容或多或少是通过允许从我的服务app.config中设置ServiceName和DisplayName所做的事情,我确实尝试了您描述的内容,但不幸的是,这导致了我的问题中列出的相同问题。
2009年

我有一个我使用了很长时间的模板,所以也许我错过了一些东西,您的ServiceInstaller类是什么样的,会发布一个我使用的模板的工作副本,让我知道这对您有帮助吗?
Mark Redman

我们的服务安装程序实际上几乎相同。我使用静态类加载服务并从配置文件中显示名称,但除此之外,它们非常相似。我对它为什么对我不起作用的猜测是,我们的服务代码可能有些奇怪。不幸的是,已经有很多人这样做了。据我了解,在大多数情况下,您的回答应该有效,感谢您的帮助。
2009年

2
巨大的帮助,谢谢。我认为安装配置文件需要命名为InstallUtil.exe.confg而不是InstallUtil.exe的Install.exe.config
NullReference 2013年

完全有效的好方法。也就是说,如果您知道要将哪个InstallUtil.exe复制到您的安装文件夹中(我个人安装了许多框架版本,而64位副本会加剧该版本)。这将使得很难向服务台团队解释是否进行安装。但是对于开发人员主导的安装而言,它非常优雅。
timmi4sa 2014年

11

我知道有个老问题,但是我在InstallUtil.exe上使用/ servicename选项很幸运。我没有在内置帮助中看到它。

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

我不确定我是从哪里第一次读到这个的,但是从那以后我再也没有见过。YMMV。


3
返回此错误:An exception occurred during the Install phase. System.ComponentModel.Win32Exception: The specified service already exists
mkb

@mkb您是否还有另一个名为“我的服务”的服务?
乔纳森·沃特尼

是的,因为这个问题我有一个服务,相同的可执行文件,但是我想安装它的两个实例,每个实例具有不同的配置。我复制粘贴了服务exe,但是此exe不起作用。
mkb

1
/ servicename =“我的服务实例一”和/ servicename =“我的服务实例二”这些名称必须唯一。
granadaCoder '16

11

ServiceName和指定自定义值的另一种快速方法DisplayName是使用installutil命令行参数。

  1. 在您的ProjectInstaller类中重写虚拟方法Install(IDictionary stateSaver)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;
        }
    }
  2. 建立你的项目
  3. installutil使用/servicename参数添加您的自定义名称来安装服务:

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

请注意,如果未/servicename在命令行中指定,则将使用ProjectInstaller properties / config中指定的ServiceName和DisplayName值来安装服务。


2
辉煌!!谢谢-这正是需要的重点。
Iofacture

7

使用自动部署软件频繁安装/卸载并排Windows服务时,我对上述方法不太满意,但最终我想到了以下方法,该方法允许我传递参数以指定后缀命令行上的服务名称。它还使设计人员能够正常运行,并且在必要时可以轻松地改编为覆盖整个名称。

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

这将创建名为“ Awesome Service-UAT”的服务。我们使用它来在同一台机器上并行运行同一服务的DEVINT,TESTING和ACCEPTANCE版本。每个版本都有自己的一组文件/配置-我没有尝试过安装指向同一组文件的多个服务。

注意:您必须使用相同的/ServiceSuffix参数来卸载服务,因此您将执行以下操作来卸载:

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


很好,但这仅适用于安装程序。一旦有了新的实例名称,Windows服务将如何知道该新名称?您必须在构建Windows服务时将其传递吗?
progLearner

谢谢!安装程序将使用上面SetNames()方法中设置的值在Windows Service上设置名称。
tristankoffee

当然可以,但是如何从外界设置这个名称?
progLearner

我的回答是在命令行上用于在外部安装(和卸载)服务的命令。您传递的价值/ServiceSuffix="UAT"安装程序使用来设置服务的后缀。在我的示例中,传入的值为UAT。在我的情况我只是想添加一个后缀到服务的现有名称,但没有理由你无法适应这与在传递的值完全替代名称。
tristankoffee

谢谢,但这是命令行输入(=手动输入),而不是代码。按照原始问题:一旦有了新的实例名称,Windows服务将如何知道该新名称?您必须在构建Windows服务时将其传递吗?
progLearner

4

我要做的就是将服务名称和显示名称存储在服务的app.config中。然后在安装程序类中,我将app.config加载为XmlDocument并使用xpath取出值,然后将它们应用于ServiceInstaller.ServiceName和ServiceInstaller.DisplayName,然后再调用InitializeComponent()。假设您尚未在InitializeComponent()中设置这些属性,在这种情况下,配置文件中的设置将被忽略。以下代码是我从安装程序类构造函数在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;
          }
      }

我不认为可以直接从ConfigurationManager.AppSettings读取配置文件,或者类似的安装程序可以在安装程序运行时运行,而是在InstallUtil.exe的上下文中运行,而不是在服务的.exe中运行。您可能可以使用ConfigurationManager.OpenExeConfiguration进行某些操作,但是在我的情况下,此操作不起作用,因为我试图获取未加载的自定义配置部分。


克里斯屋(Chris House),您好!偶然发现您的答案,因为我围绕Quartz.NET调度程序构建了一个基于OWIN的自托管Web API,并将其粘贴在Windows Service中。非常漂亮!希望你一切都好!
NovaJoe

克里斯屋(Chris House),您好!偶然发现您的答案,因为我围绕Quartz.NET调度程序构建了一个基于OWIN的自托管Web API,并将其粘贴在Windows Service中。非常漂亮!希望你一切都好!
NovaJoe 2015年

4

只是为了改善@ chris.house.00 this的完美答案,您可以考虑使用以下功能从您的应用设置中读取内容:

 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

我有类似的情况,我需要一个以前的服务,以及一个更新的服务在同一台服务器上并排运行。(这不仅是数据库更改,还包括代码更改)。所以我不能只运行相同的.exe两次。我需要一个用新DLL编译但来自同一项目的新.exe。只是更改服务名称和服务的显示名称对我不起作用,我仍然收到“服务已存在错误”,我相信这是因为我正在使用部署项目。最终为我工作的是在我的部署项目属性中,有一个名为“ ProductCode”的属性,它是Guid。

在此处输入图片说明

之后,将安装项目重建为成功安装的新.exe或.msi。


1

最简单的方法是基于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;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.