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

chris.house.00
источник
Привет, Крис Хаус! Наткнулся на ваш ответ, потому что я создаю автономный веб-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;
}
Игорь Крупицкий
источник