102

所以我们制作了一个 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 收到以下输出(仅相关部分):

运行事务安装。

开始安装的安装阶段。

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

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

安装的回滚阶段即将开始。

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

回滚阶段成功完成。

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

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

4

10 回答 10

84

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

sc create

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

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

于 2009-08-14T18:38:47.863 回答
22
  sc create [servicename] binpath= [path to your exe]

这个解决方案对我有用。

于 2013-06-14T07:24:41.467 回答
20

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

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

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

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());
            }
        }
    }
}
于 2009-08-14T18:37:18.727 回答
12

ServiceName为and指定自定义值的另一种快速方法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 属性/配置中指定的 ServiceName 和 DisplayName 值安装服务

于 2017-12-22T08:25:27.283 回答
11

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

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

我不完全确定我第一次在哪里读到这个,但从那以后我就再也没有见过它。YMMV。

于 2013-04-22T20:25:40.717 回答
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

于 2016-05-19T17:57:09.767 回答
4

我为完成这项工作所做的是将服务名称和显示名称存储在我的服务的 app.config 中。然后在我的安装程序类中,我将 app.config 作为 XmlDocument 加载,并在调用 InitializeComponent() 之前使用 xpath 获取值并将它们应用到 ServiceInstaller.ServiceName 和 ServiceInstaller.DisplayName。这假设您尚未在 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 执行某些操作,但是在我的情况下,这不起作用,因为我试图获取未加载的自定义配置部分。

于 2011-08-24T16:42:30.737 回答
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";
            }
        }
于 2016-04-29T21:39:31.040 回答
2

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

在此处输入图像描述

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

于 2018-03-30T14:19:15.020 回答
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;
}
于 2018-06-29T21:27:09.353 回答