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


96

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

Ответы:


81

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

sc create

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

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


5
Я нашел эту страницу , чтобы быть полезным: http://journalofasoftwaredev.wordpress.com/2008/07/16/multiple-instances-of-same-windows-service/. Вы можете вставить код в программу установки, чтобы получить нужное имя службы при запуске installutil.
— Vivian River

9
Ссылка на блог wordpress изменена на: journalofasoftwaredev.wordpress.com/2008/07
— STLDev

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

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


5
просто указать; [path to your exe]должен быть полный путь и не забывайте пробел послеbinpath=
— mkb

2
Это действительно позволяет устанавливать службу несколько раз. Однако вся информация предоставлена ​​установщиком сервиса. Описание Fe, тип входа в систему и т. Д. Игнорируются
— Ноэль Видмер

20

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

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());
            }
        }
    }
}

Я думаю, что то, что вы описываете, является более или менее тем, что я сделал, позволив установить ServiceName и DisplayName из моих сервисов app.config. Я попытался сделать то, что вы описываете, но, к сожалению, это привело к той же проблеме, что и в моем вопросе.
— Switters,

У меня вроде есть шаблон, который я использую, который я использовал в течение многих лет, поэтому, возможно, я что-то пропустил, как выглядит ваш класс ServiceInstaller, опубликует рабочую копию того, который я использую, дайте мне знать, что это поможет?
— Марк Редман,

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

2
Огромное спасибо за помощь. Я думаю, что файл конфигурации установки должен называться InstallUtil.exe.confg, а не Install.exe.config для InstallUtil.exe
— NullReference

Хороший подход, который полностью работает. То есть, если вы знаете, какой InstallUtil.exe скопировать в папку установки (у меня лично установлено множество версий фреймворка, что усугубляется 64-битными копиями). Из-за этого было бы довольно сложно объяснить команде службы поддержки, если они выполняют установку. Но для установки под руководством разработчика это очень элегантно.
— timmi4sa

11

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

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

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


3
Возвращает эту ошибку:An exception occurred during the Install phase. System.ComponentModel.Win32Exception: The specified service already exists
— mkb

@mkb У вас есть еще одна служба под названием «Моя служба»?
— Джонатон Уотни

Да, как и в вопросе, у меня есть одна служба, тот же исполняемый файл, но я хочу установить два ее экземпляра, каждый с разной конфигурацией. Я копирую и вставляю служебный exe, но этот не сработал.
— mkb

1
/ servicename = "My Service InstanceOne" и / servicename = "My Service InstanceTwo" Имена должны быть уникальными.
— granadaCoder

11

Еще один быстрый способ указать настраиваемое значение для 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.


2
Великолепно !! Спасибо, это было именно то, что нужно и по существу.
— Iofacture

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


Это здорово, но это только для установщика. Если у вас будет новое имя экземпляра, как служба Windows узнает об этом новом имени? Это нужно передать при построении сервиса Windows?
— progLearner

Спасибо! Программа установки установит имя для службы Windows во время ее установки, используя значения, установленные в методе SetNames () выше.
— тристанкоффи

Конечно, но как вы можете установить это имя из внешнего мира?
— progLearner

В моем ответе есть команда, используемая в командной строке для установки (и удаления) службы во внешнем мире. Значение, которое вы передаете, /ServiceSuffix="UAT"используется установщиком для установки суффикса службы. В моем примере передано значение UAT. В моем сценарии я просто хотел добавить суффикс к существующему имени службы, но нет никаких причин, по которым вы не могли бы адаптировать это, чтобы полностью заменить имя переданным значением.
— tristankoffee

Спасибо, но это ввод из командной строки (= ручной ввод), а не код. Согласно исходному вопросу: если у вас будет новое имя экземпляра, как служба Windows узнает об этом новом имени? Это нужно передать при построении сервиса Windows?
— progLearner

4

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


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

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

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";
            }
        }

2

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

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

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


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;
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.