Установка нескольких экземпляров одной и той же службы Windows на сервере

Итак, мы создали службу Windows для подачи данных в наше клиентское приложение, и все идет отлично. Клиент придумал забавный запрос конфигурации, который требует двух экземпляров этой службы, работающих на одном сервере, и настроен для указания на отдельные базы данных.

До сих пор мне не удалось это сделать, и я надеялся, что мои коллеги-участники stackoverflow могут дать некоторые подсказки о том, почему.

Текущая настройка:

Я создал проект, содержащий службу Windows, с этого момента мы будем называть его AppService и файл ProjectInstaller.cs, который обрабатывает пользовательские шаги установки, чтобы установить имя службы на основе ключа в приложении. 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 (только для соответствующих частей):

Запуск транзакционной установки.

Начало этапа установки.

Установка службы обслуживания приложений... Служба обслуживания приложений. 2 успешно установлена. Создание источника EventLog для службы приложений Два в приложении журнала...

Исключение произошло на этапе установки. System.NullReferenceException: ссылка на объект не установлена ​​в экземпляр объекта.

Начало этапа отката установки.

Восстановление журнала событий в предыдущем состоянии для источника службы приложений Two. Служба обслуживания приложений Два из них удаляются из системы... Служба обслуживания приложений Два успешно удалены из системы.

Этап отката успешно завершен.

Транзактная установка завершена. Установка завершилась неудачно, и откат был выполнен.

Извините за длинный пост, хотел удостовериться, что есть достаточная релевантная информация. Часть, которая до сих пор меня озадачила, заключается в том, что она заявляет, что установка службы завершается успешно, и ее только после того, как она идет, чтобы создать источник EventLog, который, как представляется, вызывает NullReferenceException. Поэтому, если кто-то знает, что я делаю неправильно или у меня есть лучший подход, мы будем очень благодарны.

Ответ 2

Вы можете запускать несколько версий одной и той же службы, выполняя следующие действия:

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

EDIT:

Заметьте, что если я что-то пропустил, вот класс 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());
            }
        }
    }
}

Ответ 3

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

Это решение сработало для меня.

Ответ 4

Старый вопрос, я знаю, но мне посчастливилось использовать параметр /servicename на InstallUtil.exe. Однако я не вижу его в встроенной справке.

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

Я не совсем уверен, где я впервые прочитал об этом, но с тех пор я его не видел. YMMV.

Ответ 5

Что я сделал для этой работы, это сохранить имя службы и отображаемое имя в 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, но в моем случае это не сработало, поскольку я пытался попасть в пользовательский раздел конфигурации, который не был загружен.

Ответ 6

Мне не повезло с вышеупомянутыми методами при использовании нашего программного обеспечения для автоматического развертывания, чтобы часто устанавливать/удалять бок о бок окна, но в итоге я получил следующее, что позволяет мне передать параметр укажите суффикс имени службы в командной строке. Он также позволяет дизайнеру функционировать должным образом и может быть легко адаптирован для переопределения всего имени, если это необходимо.

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

Ответ 7

Чтобы улучшить идеальный ответ @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";
            }
        }

Ответ 8

Еще один быстрый способ указать настраиваемое значение для ServiceName и DisplayName использует параметры командной строки installutil.

  • В классе 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;
        }
    }
    
  • Создайте свой проект
  • Установите службу с помощью installutil, добавив свое настраиваемое имя, используя параметр /servicename:

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

Обратите внимание, что если вы не укажете /servicename в командной строке, служба будет установлена ​​с именами ServiceName и DisplayName, указанными в ProjectInstaller properties/config