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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1279383/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 06:39:35  来源:igfitidea点击:

Installing multiple instances of the same windows service on a server

windowswindows-serviceswindows-installerinstallutil

提问by Switters

So we've produced a windows service to feed data to our client application and everything is going great. The client has come up with a fun configuration request that requires two instances of this service running on the same server and configured to point at separate databases.

所以我们制作了一个 Windows 服务来向我们的客户端应用程序提供数据,一切都很好。客户端提出了一个有趣的配置请求,需要该服务的两个实例在同一服务器上运行并配置为指向不同的数据库。

So far I haven't been able to get this to happen and was hoping my fellow stackoverflow members might be able to give some hints as to why.

到目前为止,我还没有能够做到这一点,并希望我的其他 stackoverflow 成员能够就原因给出一些提示。

Current setup:

当前设置:

I've set up the project that contains the windows service, we'll call it AppService from now on, and the ProjectInstaller.cs file that handles custom installation steps to set the service name based on a key in the App.config like so:

我已经设置了包含 windows 服务的项目,从现在起我们将其称为 AppService,以及处理自定义安装步骤的 ProjectInstaller.cs 文件,以根据 App.config 中的键设置服务名称,如下所示:

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

In this case Util is just a static class tha tloads the service name from the config file.

在这种情况下,Util 只是一个静态类,它从配置文件加载服务名称。

From here forward I have tried two different ways to get both services installed and both have failed in an identical way.

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

The first way was to simply install the first copy of the service, copy the installed directory and renamed it, and then ran the following command after modifying the app config to change the desired service name:

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

InstallUtil.exe /i AppService.exe

When that didn't work I tried to create a second installer project, edited the config file and built the second installer. When I ran the installer it worked fine but the service did not show up in services.msc so I ran the previous command against the second installed code base.

当那不起作用时,我尝试创建第二个安装程序项目,编辑配置文件并构建第二个安装程序。当我运行安装程序时,它运行良好,但服务没有出现在 services.msc 中,所以我针对第二个已安装的代码库运行了前面的命令。

Both times i received the following output from InstallUtil (relevant parts only):

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

Running a transacted installation.

Beginning the Install phase of the installation.

Installing service App Service Two... Service App Service Two has been successfully installed. Creating EventLog source App Service Two in log Application...

An exception occurred during the Install phase. System.NullReferenceException: Object reference not set to an instance of an object.

The Rollback phase of the installation is beginning.

Restoring event log to previous state for source App Service Two. Service App Service Two is being removed from the system... Service App Service Two was successfully removed from the system.

The Rollback phase completed successfully.

The transacted install has completed. The installation failed, and the rollback has been performed.

运行事务安装。

开始安装的安装阶段。

正在安装服务应用服务二... 服务应用服务二已成功安装。在日志应用程序中创建 EventLog 源应用服务二...

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

安装的回滚阶段开始。

将源应用服务二的事件日志还原到以前的状态。正在从系统中删除服务应用程序服务二... 服务应用程序服务二已成功从系统中删除。

回滚阶段成功完成。

事务安装已完成。安装失败,已经执行回滚。

Sorry for the long winded post, wanted to make sure there is enough relevant information. The piece that so far has me stumped is that it states that the installation of the service completes successfully and its only after it goes to create the EventLog source that the NullReferenceException seems to get thrown. So if anyone knows what I'm doing wrong or has a better approach it would be much appreciated.

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

采纳答案by jamesaharvey

Have you tried the sc / service controller util? Type

您是否尝试过 sc / 服务控制器实用程序?类型

sc create

at a command line, and it will give you the help entry. I think I've done this in the past for Subversion and used this articleas a reference:

在命令行中,它将为您提供帮助条目。我想我过去为 Subversion 做过这件事,并使用这篇文章作为参考:

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

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

回答by Mark Redman

You can run multiple versions of the same service by doing the following:

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

1) Copy the Service executable and config to its own folder.

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

2) Copy Install.Exe to the service executable folder (from .net framework folder)

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

3) Create a config file called Install.exe.config in the service executable folder with the following contents (unique service names):

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) Create a batch file to install the service with the following contents:

4)创建一个批处理文件安装服务,内容如下:

REM Install
InstallUtil.exe YourService.exe
pause

5) While your there, create an uninstall batch file

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

REM Uninstall
InstallUtil.exe -u YourService.exe
pause

EDIT:

编辑:

Note sure if I missed something, here is the ServiceInstaller Class (adjust as required):

请注意,如果我错过了什么,这里是 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());
            }
        }
    }
}

回答by Rajesh Kumar

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

This solution worked for me.

这个解决方案对我有用。

回答by Jonathon Watney

Old question, I know, but I've had luck using the /servicename option on InstallUtil.exe. I don't see it listed in the built-in help though.

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

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

I'm not entirely sure where I first read about this but I haven't seen it since. YMMV.

我不完全确定我第一次读到这个的地方,但从那以后我就没有看到过。天啊。

回答by Andrea

Another quick way to specify a custom value for ServiceNameand DisplayNameis using installutilcommand line parameters.

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

  1. In your ProjectInstallerclass override virtual methods Install(IDictionary stateSaver)and 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. Build your project
  3. Install the service with installutiladding your custom name using /servicenameparameter:

    installutil.exe /servicename="CustomServiceName" "c:\pathToService\SrvcExecutable.exe"
    
  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"
    

Please note that if you do not specify /servicenamein the command line the service will be installed with ServiceName and DisplayName values specified in ProjectInstaller properties/config

请注意,如果您未/servicename在命令行中指定,服务将使用在 ProjectInstaller 属性/配置中指定的 ServiceName 和 DisplayName 值进行安装

回答by tristankoffee

I didn't have much luck with the above methods when using our automated deployment software to frequently install/uninstall side-by-side windows services, but I eventually came up with the following which allows me to pass in a parameter to specify a suffix to the service name on the command line. It also allows the designer to function properly and could easily be adapted to override the entire name if necessary.

使用我们的自动化部署软件经常安装/卸载并排的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;
  }
}

With this in mind, I can do the following: If I've called the service "Awesome Service" then I can install a UAT verison of the service as follows:

考虑到这一点,我可以执行以下操作:如果我将服务称为“Awesome Service”,那么我可以安装该服务的 UAT 版本,如下所示:

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

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

This will create the service with the name "Awesome Service - UAT". We've used this to run DEVINT, TESTING and ACCEPTANCE versions of the same service running side-by-side on a single machine. Each version has its own set of files/configs - I haven't tried this to install multiple services pointing at the same set of files.

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

NOTE: you have to use the same /ServiceSuffixparameter to uninstall the service, so you'd execute the following to uninstall:

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

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

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

回答by Teoman shipahi

Just to improve perfect answer of @chris.house.00 this, you can consider following function to read from your app settings:

只是为了提高@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";
            }
        }

回答by chris.house.00

What I've done to make this work is to store the service name and display name in an app.config for my service. Then in my installer class, I load the app.config as an XmlDocument and use xpath to get the values out and apply them to ServiceInstaller.ServiceName and ServiceInstaller.DisplayName, before calling InitializeComponent(). This assumes you're not already setting these properties in InitializeComponent(), in which case, the settings from your config file will be ignored. The following code is what I'm calling from my installer class constructor, before InitializeComponent():

为了完成这项工作,我所做的是将服务名称和显示名称存储在我的服务的 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;
          }
      }

I don't believe reading the configuration file directly from ConfigurationManager.AppSettings or something similar will work as when the installer runs, it's run in the context of InstallUtil.exe, not your service's .exe. You may be able to do something with ConfigurationManager.OpenExeConfiguration, however in my case, this didn't work as I was trying to get at a custom configuration section that was not loaded.

我不相信直接从 ConfigurationManager.AppSettings 或类似的东西读取配置文件会在安装程序运行时起作用,它在 InstallUtil.exe 的上下文中运行,而不是在您的服务的 .exe 中运行。您也许可以使用 ConfigurationManager.OpenExeConfiguration 做一些事情,但是在我的情况下,这不起作用,因为我试图获取未加载的自定义配置部分。

回答by cmartin

I had a similar situation, where i to needed have a previous service, and an updated service running side by side on the same server. (It was more than just a database change, it was code changes as well). So I couldn't just run the same .exe twice. I needed a new .exe that was compiled with new DLLs but from the same project. Just changing the service name and display name of the service did not work for me, I still received the "service already existed error" which I believe is because I am using a Deployment Project. What finally did work for me is within my Deployment Project Properties there is a property called "ProductCode" which is a Guid.

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

enter image description here

enter image description here

After that, rebuilding the Setup Project to a new .exe or .msi installed successfully.

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

回答by Igor Krupitsky

The simplest approach is is based the service name on the dll name:

最简单的方法是基于 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;
}