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

Итак, мы создали службу 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 (только соответствующие части):

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

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

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

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

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

Восстановление журнала событий до предыдущего состояния для исходной службы приложений 2. Служба приложений-служб 2 удаляется из системы ... Служба приложений-служб 2 успешно удалена из системы.

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

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

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


person Switters    schedule 14.08.2009    source источник


Ответы (10)


Вы пробовали использовать утилиту sc / service controller? Тип

sc create

в командной строке, и он предоставит вам запись справки. Думаю, я делал это раньше для Subversion и использовал эту статью для справки:

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

person jamesaharvey    schedule 14.08.2009
comment
Я нашел эту страницу полезной: http://journalofasoftwaredev.wordpress.com/2008/07/16/multiple-instances-of-same-windows-service/. Вы можете вставить код в программу установки, чтобы получить нужное имя службы при запуске installutil. - person Vivian River; 03.02.2010
comment
Ссылка на блог wordpress была изменена на: journalofasoftwaredev.wordpress.com/2008/07 - person STLDev; 01.03.2014

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

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

person Rajesh Kumar    schedule 14.06.2013
comment
просто указать; [path to your exe] должен быть полным путем и не забывайте пробел после binpath= - person mkb; 29.01.2016
comment
Это действительно позволяет устанавливать службу несколько раз. Однако вся информация предоставлена ​​установщиком сервиса. F.e. описание, тип входа и т. д. игнорируются - person Noel Widmer; 05.08.2016

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

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());
            }
        }
    }
}
person Mark Redman    schedule 14.08.2009
comment
Я думаю, что то, что вы описываете, более или менее похоже на то, что я сделал, позволив установить ServiceName и DisplayName из моих служб app.config. Я попытался сделать то, что вы описываете, но, к сожалению, это привело к той же проблеме, что и в моем вопросе. - person Switters; 14.08.2009
comment
У меня вроде есть шаблон, который я использую, который я использовал целую вечность, поэтому, возможно, я что-то пропустил, как выглядит ваш класс ServiceInstaller, опубликует рабочую копию того, который я использую, дайте мне знать, что это поможет? - person Mark Redman; 14.08.2009
comment
Наши установщики сервисов практически идентичны. Я использую статический класс для загрузки службы и отображения имен из файла конфигурации, но в остальном они очень похожи. Я предполагаю, что у меня это не работает, потому что в нашем сервисном коде может быть что-то необычное. К сожалению, к этому приложили много рук. Насколько я понимаю, ваш ответ должен работать в большинстве случаев, спасибо за помощь. - person Switters; 14.08.2009
comment
Огромное спасибо за помощь. Я думаю, что файл конфигурации установки должен называться InstallUtil.exe.confg, а не Install.exe.config для InstallUtil.exe - person NullReference; 11.02.2013
comment
Хороший подход, который полностью работает. То есть, если вы знаете, какой InstallUtil.exe скопировать в папку установки (у меня лично установлено множество версий фреймворка, что усугубляется 64-битными копиями). Из-за этого было бы довольно сложно объяснить команде службы поддержки, если они выполняют установку. Но для установки под руководством разработчика это очень элегантно. - person timmi4sa; 19.03.2014
comment
Мне пришлось добавить этот код в класс установщика проекта, но он работал без проблем. - person fizch; 20.08.2015
comment
ConfigurationManager.AppSettings [ServiceName] всегда возвращает мне значение null, даже если я установил их в app.config. Есть идеи, почему? Если я отлаживаю проект, он его читает, но если я запускаю из serviceutil, он всегда устанавливает имя службы как значение по умолчанию. (жестко запрограммированный) - person Teoman shipahi; 30.04.2016
comment
@MarkRedman Удаление и переустановка службы Windows вызывает ошибку как Ошибка: 1001 Указанная служба уже существует. Как решить эту проблему? - person Balagurunathan Marimuthu; 20.11.2018
comment
@BalagurunathanMarimuthu Вам необходимо сначала удалить службу, а затем повторно устанавливать ее. - person Mark Redman; 20.11.2018
comment
Вы уверены, что он удален из консоли служб? вы также можете попробовать: sc delete ‹Service_Name› и посмотреть, работает ли это? - person Mark Redman; 20.11.2018
comment
@MarkRedman Да, я проверил сервисную консоль, sc delete, regedit, путь установки и т. Д., Но это не работает ... - person Balagurunathan Marimuthu; 20.11.2018
comment
@BalagurunathanMarimuthu: Не смешно, но вы пробовали перезагрузку? иногда службы могут отключаться до перезагрузки? Может помочь? - person Mark Redman; 20.11.2018
comment
@MarkRedman Работает после того, как иногда без внесения каких-либо изменений в систему. Спасибо за ваше решение. - person Balagurunathan Marimuthu; 20.11.2018
comment
@MarkRedman Можно ли установить службу Windows как несколько экземпляров одной и той же оконной службы с помощью установочного пакета? Здесь мне нужно создать два разных ярлыка на рабочем столе с двумя разными именами ... и вышеупомянутый подход не делает никаких записей в программах и функциях. Следовательно, моя служба Windows также имеет приложение Windows, которое должно быть копией для каждого установленного экземпляра службы. - person Balagurunathan Marimuthu; 21.11.2018
comment
@BalagurunathanMarimuthu Да, вы можете установить одну и ту же службу несколько раз, вам нужно будет изменить имя и описание службы ... в идеале, они должны находиться в разных папках в зависимости от вашей конфигурации, убедитесь, что нет конфликта в том, что делает ваша служба. - person Mark Redman; 21.11.2018

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

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

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

person Jonathon Watney    schedule 22.04.2013
comment
Возвращает эту ошибку: An exception occurred during the Install phase. System.ComponentModel.Win32Exception: The specified service already exists - person mkb; 29.01.2016
comment
@mkb У вас есть другой сервис под названием My Service? - person Jonathon Watney; 30.01.2016
comment
Да, как и в вопросе, у меня есть одна служба, тот же исполняемый файл, но я хочу установить два ее экземпляра, каждый с разной конфигурацией. Я копирую и вставляю служебный exe, но этот не сработал. - person mkb; 31.01.2016
comment
/ servicename = My Service InstanceOne и / servicename = My Service InstanceTwo Имена должны быть уникальными. - person granadaCoder; 15.12.2016

Еще один быстрый способ указать пользовательское значение для 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 в командной строке, служба будет установлена ​​со значениями ServiceName и DisplayName, указанными в свойствах / config ProjectInstaller.

person Andrea    schedule 22.12.2017
comment
Великолепно !! Спасибо, это было именно то, что нужно и по существу. - person Iofacture; 30.10.2018

Мне не очень повезло с вышеуказанными методами при использовании нашего программного обеспечения для автоматического развертывания для частой установки / удаления параллельных служб 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

person tristankoffee    schedule 19.05.2016
comment
Это здорово, но это только для установщика. Если у вас есть новое имя экземпляра, как служба Windows узнает об этом новом имени? Это нужно передать при построении сервиса Windows? - person progLearner; 17.01.2020
comment
Спасибо! Программа установки установит имя для службы Windows во время ее установки с использованием значений, установленных в методе SetNames () выше. - person tristankoffee; 19.01.2020
comment
Конечно, но как вы можете установить это имя из внешнего мира? - person progLearner; 20.01.2020
comment
В моем ответе это команда, используемая в командной строке для установки (и удаления) службы во внешнем мире. Значение, которое вы передаете в /ServiceSuffix="UAT", используется установщиком для установки суффикса службы. В моем примере передано значение UAT. В моем сценарии я просто хотел добавить суффикс к существующему имени службы, но нет причин, по которым вы не могли бы адаптировать это, чтобы полностью заменить имя переданным значением. - person tristankoffee; 20.01.2020
comment
Спасибо, но это ввод из командной строки (= ручной ввод), а не код. Согласно исходному вопросу: если у вас есть новое имя экземпляра, как служба Windows узнает об этом новом имени? Это нужно передать при построении сервиса Windows? - person progLearner; 20.01.2020
comment
Я не уверен, что понимаю, какую проблему вы пытаетесь решить. Первоначальный вопрос заключался в том, как установить одну и ту же службу Windows дважды на одном компьютере - у OP возникла проблема при установке одной и той же службы из командной строки из двух разных мест - это не удалось, потому что обе службы будут иметь одинаковое имя, которое Винда не понравилась. В моем ответе показано, как это исправить, передав параметр в программу установки из командной строки для установки разных имен. Похоже, вы пытаетесь сделать что-то еще, и я не понимаю, что - не могли бы вы объяснить? - person tristankoffee; 21.01.2020
comment
Спасибо @tristankoffee. Просто ваш ответ неполон в том смысле, что передача имени установщику не заставляет службу работать с этим именем экземпляра по волшебству :-) Мне пришлось передать это имя экземпляра конструктору службы Windows (с установщиком) чтобы система действительно знала об этом конкретном экземпляре службы и могла ссылаться на него / использовать его. Иначе ничего не получится. Если у вас нет другого представления о том, как это сделать, о чем я и просил, так как не был уверен, что это лучший способ сделать это :-) - person progLearner; 21.01.2020
comment
@progLearner - к сожалению, я написал этот ответ почти 4 года назад, и у меня нет доступа к исходному коду, и я не могу вспомнить, как вы собираетесь связать имя службы с установщиком, поэтому извините, я не могу помочь вам с этим. Вероятно, поскольку OP уже ссылался на код установщика, я удалил лишний код для краткости. Удачи тебе с твоим проектом. - person tristankoffee; 22.01.2020
comment
Я понимаю и ценю, что вы попробовали, все равно спасибо :-) +1 к оригинальному ответу - person progLearner; 22.01.2020

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

person chris.house.00    schedule 24.08.2011
comment
Привет, Крис Хаус! Наткнулся на ваш ответ, потому что я создаю автономный веб-API на основе OWIN вокруг планировщика Quartz.NET и вставляю его в службу Windows. Довольно ловко! Надеюсь, у тебя все хорошо! - person NovaJoe; 08.01.2015
comment
Привет, Крис Хаус! Наткнулся на ваш ответ, потому что я создаю автономный веб-API на основе OWIN вокруг планировщика Quartz.NET и вставляю его в службу Windows. Довольно ловко! Надеюсь, у тебя все хорошо! - person NovaJoe; 08.01.2015

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

У меня была аналогичная ситуация, когда мне нужно было иметь предыдущую службу и обновленную службу, работающую бок о бок на одном сервере. (Это было больше, чем просто изменение базы данных, это были также изменения кода). Так что я не мог просто запустить один и тот же .exe дважды. Мне нужен был новый .exe, который был скомпилирован с новыми DLL, но из того же проекта. Простое изменение имени службы и отображаемого имени службы у меня не сработало, я все еще получал «ошибка службы уже существует», которая, как мне кажется, связана с тем, что я использую проект развертывания. Что в итоге сработало для меня, так это в моих свойствах проекта развертывания есть свойство под названием «ProductCode», которое является Guid.

введите здесь описание изображения

После этого перестройка проекта установки на новый .exe или .msi успешно установлена.

person cmartin    schedule 30.03.2018

Самый простой подход основан на имени службы на имени 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;
}
person Igor Krupitsky    schedule 29.06.2018