Installing multiple instances of the same windows

2019-01-20 23:22发布

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.

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.

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:

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.

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.

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

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.

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.

10条回答
霸刀☆藐视天下
2楼-- · 2019-01-20 23:35

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():

       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.

查看更多
ゆ 、 Hurt°
3楼-- · 2019-01-20 23:37

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

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

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

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

<?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:

REM Install
InstallUtil.exe YourService.exe
pause

5) While your there, create an uninstall batch file

REM Uninstall
InstallUtil.exe -u YourService.exe
pause

EDIT:

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

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());
            }
        }
    }
}
查看更多
Summer. ? 凉城
4楼-- · 2019-01-20 23:46
  sc create [servicename] binpath= [path to your exe]

This solution worked for me.

查看更多
乱世女痞
5楼-- · 2019-01-20 23:47

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

 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";
            }
        }
查看更多
登录 后发表回答