Встановлення декількох екземплярів однієї і тієї ж служби 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 (лише відповідні частини):

Запуск встановленої транзакції.

Починаючи фазу встановлення інсталяції.

Встановлення служби App Service Two ... Служба App Service Two успішно встановлена. Створення джерела подій EventLog Служба додатків два у програмі журналу ...

Виняток стався на етапі встановлення. System.NullReferenceException: посилання на об'єкт не встановлено як екземпляр об'єкта.

Починається фаза відкоту установки.

Відновлення журналу подій до попереднього стану для вихідної Служби додатків Два. Служба App Service Two видаляється з системи ... Служба App Service Two була успішно видалена з системи.

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

Транзакційне встановлення завершено. Не вдалося встановити, і було виконано відкат.

Вибачте за довгий вітряний пост, хотів переконатися, що є достатньо відповідної інформації. Поки що мене це заплутало, це те, що він стверджує, що встановлення служби завершується успішно, і лише після того, як він переходить до створення джерела EventLog, яке, здається, NullReferenceException видається. Тож якщо хтось знає, що я роблю не так, або має кращий підхід, це буде вдячний.

Відповіді:


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/. Ви можете вставити код в інсталятор, щоб отримати ім'я послуги, яке ви хочете під час запуску installlutil.
річка Вівіан

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. Я намагався описати вас, але, на жаль, це призвело до тієї самої проблеми, перерахованої в моєму запитанні.
Світтери

У мене начебто є шаблон, який я використовую віком, тому, можливо, я щось пропустив, як виглядає ваш клас ServiceInstaller, опублікує робочу копію того, що я використовую, повідомте мене, що це допоможе?
Марк Редман

Наші сервісні установники насправді майже однакові. Я використовую статичний клас для завантаження служби та відображення імен з конфігураційного файлу, але крім того, що вони дуже схожі. Я гадаю, чому це не працює для мене, полягає в тому, що в коді нашої послуги може бути дещо своєрідне. На жаль, на цьому було багато рук. Однак, наскільки я зрозумів, ваша відповідь має спрацювати в більшості випадків, дякую за допомогу.
Світтери

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, вказаними у властивостях ProjectInstaller / config


2
Блискуче !! Дякую - це було саме те, що було потрібно і до речі.
Іофактура

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

З огляду на це, я можу зробити наступне: Якщо я зателефонував службі "Чудова послуга", тоді я можу встановити версію служби UAT наступним чином:

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

Це створить службу з назвою "Чудова послуга - UAT". Ми використовували це для запуску версій DEVINT, TESTING і ACCEPTANCE тієї самої служби, що працюють паралельно на одній машині. Кожна версія має власний набір файлів / конфігурацій - я не пробував цього, щоб встановити кілька служб, що вказують на один і той же набір файлів.

ПРИМІТКА. Ви повинні використовувати той самий /ServiceSuffixпараметр для видалення служби, тому для видалення потрібно виконати наступне:

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


Це чудово, але це лише для інсталятора. Як тільки ви отримаєте нову назву екземпляра, як служба Windows дізнається про це нову назву? Чи потрібно це передавати під час побудови служби Windows?
progLearner

Дякую! Інсталятор встановить ім'я в службі Windows під час її встановлення, використовуючи значення, встановлені в методі SetNames () вище.
tristankoffee

Звичайно, але як ви можете встановити це ім’я із зовнішнього світу?
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", яка є керівництвом.

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

Після цього відновлення проекту інсталяції до нового .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.