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

StackOverflow https://stackoverflow.com/questions/1279383

Вопрос

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

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

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

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

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

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

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

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

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

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

Это было полезно?

Решение

Вы пробовали утилиту sc/service Controller?Тип

sc create

в командной строке, и он предоставит вам справочную информацию.Кажется, я делал это раньше для Subversion и использовал Эта статья в качестве ссылки:

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

Другие советы

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

1) Скопируйте исполняемый файл Сервиса и конфигурацию в его собственную папку.

2) Скопируйте файл Install.Exe в папку service executable (из папки .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());
            }
        }
    }
}
  sc create [servicename] binpath= [path to your exe]

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

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

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

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

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

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

Учитывая это, я могу сделать следующее:Если я назвал службу «Потрясающая служба», то я могу установить версию службы UAT следующим образом:

InstallUtil.exe /ServiceSuffix="UAT" MyService.exe

Это создаст службу с именем «Awesome Service — UAT».Мы использовали это для запуска версий DEVINT, TESTING и ACCEPTANCE одной и той же службы, работающих параллельно на одной машине.Каждая версия имеет свой собственный набор файлов/конфигураций — я не пробовал устанавливать несколько служб, указывающих на один и тот же набор файлов.

ПРИМЕЧАНИЕ:ты должен использовать то же самое /ServiceSuffix параметр для удаления службы, поэтому для удаления необходимо выполнить следующее:

InstallUtil.exe /u /ServiceSuffix="UAT" MyService.exe

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

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

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

enter image description here

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

Самый простой подход заключается в том, чтобы имя службы основывалось на имени 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;
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top