Instalación de varias instancias del mismo servicio de Windows en un servidor

96

Así que hemos creado un servicio de Windows para enviar datos a la aplicación de nuestro cliente y todo va muy bien. El cliente ha presentado una solicitud de configuración divertida que requiere dos instancias de este servicio ejecutándose en el mismo servidor y configuradas para apuntar a bases de datos separadas.

Hasta ahora no he podido hacer que esto suceda y esperaba que mis compañeros miembros de stackoverflow pudieran dar algunas pistas de por qué.

Configuración actual:

Configuré el proyecto que contiene el servicio de Windows, lo llamaremos AppService de ahora en adelante, y el archivo ProjectInstaller.cs que maneja los pasos de instalación personalizados para establecer el nombre del servicio basado en una clave en App.config así :

this.serviceInstaller1.ServiceName = Util.ServiceName;
this.serviceInstaller1.DisplayName = Util.ServiceName;
this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.LocalSystem;

En este caso, Util es solo una clase estática que carga el nombre del servicio del archivo de configuración.

De aquí en adelante, he probado dos formas diferentes de instalar ambos servicios y ambos han fallado de manera idéntica.

La primera forma fue simplemente instalar la primera copia del servicio, copiar el directorio instalado y renombrarlo, y luego ejecutar el siguiente comando después de modificar la configuración de la aplicación para cambiar el nombre del servicio deseado:

InstallUtil.exe /i AppService.exe

Cuando eso no funcionó, intenté crear un segundo proyecto de instalación, edité el archivo de configuración y construí el segundo instalador. Cuando ejecuté el instalador, funcionó bien, pero el servicio no aparecía en services.msc, así que ejecuté el comando anterior contra el segundo código base instalado.

En ambas ocasiones recibí el siguiente resultado de InstallUtil (solo partes relevantes):

Ejecutando una instalación transaccional.

Comenzando la fase de instalación de la instalación.

Instalando el servicio App Service Two ... Service App Service Two se ha instalado correctamente. Creando el servicio de aplicación de origen de EventLog dos en la aplicación de registro ...

Se produjo una excepción durante la fase de instalación. System.NullReferenceException: referencia de objeto no establecida en una instancia de un objeto.

Está comenzando la fase de reversión de la instalación.

Restaurando el registro de eventos al estado anterior para el App Service Two de origen. Aplicación de servicio Servicio dos se está eliminando del sistema ... Aplicación de servicio Servicio dos se eliminó correctamente del sistema.

La fase de reversión se completó con éxito.

La instalación de la transacción se completó. La instalación falló y se realizó la reversión.

Lo siento por la publicación tan larga, quería asegurarme de que haya suficiente información relevante. La pieza que hasta ahora me ha dejado perplejo es que afirma que la instalación del servicio se completa con éxito y solo después de que va a crear la fuente EventLog parece que se lanza la NullReferenceException. Entonces, si alguien sabe lo que estoy haciendo mal o tiene un mejor enfoque, sería muy apreciado.

Conmutadores
fuente

Respuestas:

81

¿Ha probado la utilidad sc / service controller? Tipo

sc create

en una línea de comando, y le dará la entrada de ayuda. Creo que hice esto en el pasado para Subversion y usé este artículo como referencia:

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

jamesaharvey
fuente
5
He encontrado esta página para ser útiles: http://journalofasoftwaredev.wordpress.com/2008/07/16/multiple-instances-of-same-windows-service/. Puede insertar código en el instalador para obtener el nombre de servicio que desea cuando ejecuta installutil.
Vivian River
9
El enlace al blog de wordpress se ha cambiado a: journalofasoftwaredev.wordpress.com/2008/07
STLDev
21
  sc create [servicename] binpath= [path to your exe]

Esta solución funcionó para mí.

Rajesh Kumar
fuente
5
solo para señalar; [path to your exe]tiene que ser la ruta completa y no olvides el espacio despuésbinpath=
mkb
2
De hecho, esto permite instalar un servicio varias veces. Sin embargo, toda la información proporcionada por el instalador del servicio. Se ignora la descripción de Fe, el tipo de inicio de sesión, etc.
Noel Widmer
20

Puede ejecutar varias versiones del mismo servicio haciendo lo siguiente:

1) Copie el archivo ejecutable y la configuración del servicio en su propia carpeta.

2) Copie Install.Exe a la carpeta ejecutable del servicio (desde la carpeta .net framework)

3) Cree un archivo de configuración llamado Install.exe.config en la carpeta ejecutable del servicio con el siguiente contenido (nombres de servicio únicos):

<?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) Cree un archivo por lotes para instalar el servicio con el siguiente contenido:

REM Install
InstallUtil.exe YourService.exe
pause

5) Mientras esté allí, cree un archivo por lotes de desinstalación

REM Uninstall
InstallUtil.exe -u YourService.exe
pause

EDITAR:

Tenga en cuenta que si me perdí algo, aquí está la clase ServiceInstaller (ajuste según sea necesario):

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());
            }
        }
    }
}
Mark Redman
fuente
Creo que lo que está describiendo es más o menos lo que hice al permitir que ServiceName y DisplayName se establecieran desde mi aplicación de servicios.config Intenté lo que describe, pero desafortunadamente resultó en el mismo problema que aparece en mi pregunta.
Switters
Tengo una plantilla que uso, que he usado durante mucho tiempo, así que tal vez me perdí algo, ¿cómo se ve su clase de ServiceInstaller? ¿Publicará una copia de trabajo de una que uso, avíseme si esto ayuda?
Mark Redman
Nuestros instaladores de servicios son prácticamente idénticos. Utilizo una clase estática para cargar el servicio y mostrar los nombres del archivo de configuración, pero aparte de eso, son muy similares. Supongo que por qué no me funciona es que puede haber algo un poco peculiar en nuestro código de servicio. Desafortunadamente, muchas manos han estado en él. Sin embargo, por lo que entiendo, su respuesta debería funcionar en la mayoría de los casos, gracias por la ayuda.
Switters
2
Gracias enorme ayuda. Creo que el archivo de configuración de instalación debe llamarse InstallUtil.exe.confg no Install.exe.config para InstallUtil.exe
NullReference
Un buen enfoque que funciona totalmente. Eso es si sabe qué InstallUtil.exe copiar en su carpeta de instalación (personalmente tengo toneladas de versiones de framework instaladas, lo que se ve agravado por las copias de 64 bits). Esto haría bastante difícil explicarle al equipo de Helpdesk si hacen las instalaciones. Pero para una instalación dirigida por un desarrollador, es muy elegante.
timmi4sa
11

Antigua pregunta, lo sé, pero tuve suerte al usar la opción / servicename en InstallUtil.exe. Sin embargo, no lo veo en la lista de ayuda incorporada.

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

No estoy completamente seguro de dónde leí por primera vez sobre esto, pero no lo he visto desde entonces. YMMV.

Jonathon Watney
fuente
3
Devuelve este error:An exception occurred during the Install phase. System.ComponentModel.Win32Exception: The specified service already exists
mkb
@mkb ¿Tiene otro servicio llamado "Mi servicio"?
Jonathon Watney
Sí, como en la pregunta, tengo un servicio, el mismo ejecutable, pero quiero instalar dos instancias, cada una con una configuración diferente. Copio y pego el exe de servicio, pero este no funcionó.
mkb
1
/ servicename = "My Service InstanceOne" y / servicename = "My Service InstanceTwo" Los nombres deben ser únicos.
granadaCoder
11

Otra forma rápida de especificar un valor personalizado para ServiceNamey DisplayNamees usando installutilparámetros de línea de comando.

  1. En su ProjectInstallerclase, anule los métodos virtuales Install(IDictionary stateSaver)yUninstall(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. Construye tu proyecto
  3. Instale el servicio installutilagregando su nombre personalizado usando el /servicenameparámetro:

    installutil.exe /servicename="CustomServiceName" "c:\pathToService\SrvcExecutable.exe"
    

Tenga en cuenta que si no especifica /servicenameen la línea de comando, el servicio se instalará con los valores ServiceName y DisplayName especificados en ProjectInstaller properties / config

Andrea
fuente
2
¡¡Brillante!! Gracias, esto era exactamente lo que se necesitaba y al grano.
Iofacture
7

No tuve mucha suerte con los métodos anteriores cuando usé nuestro software de implementación automatizada para instalar / desinstalar con frecuencia servicios de Windows en paralelo, pero finalmente se me ocurrió lo siguiente que me permite pasar un parámetro para especificar un sufijo al nombre del servicio en la línea de comando. También permite que el diseñador funcione correctamente y podría adaptarse fácilmente para anular el nombre completo si es necesario.

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

Con esto en mente, puedo hacer lo siguiente: Si llamé al servicio "Awesome Service", entonces puedo instalar una versión UAT del servicio de la siguiente manera:

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

Esto creará el servicio con el nombre "Awesome Service - UAT". Lo hemos utilizado para ejecutar las versiones DEVINT, TESTING y ACCEPTANCE del mismo servicio que se ejecutan en paralelo en una sola máquina. Cada versión tiene su propio conjunto de archivos / configuraciones; no lo he intentado para instalar varios servicios que apuntan al mismo conjunto de archivos.

NOTA: debe usar el mismo /ServiceSuffixparámetro para desinstalar el servicio, por lo que debe ejecutar lo siguiente para desinstalar:

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

tristankoffee
fuente
Eso es genial, pero eso es solo para el instalador. Una vez que tenga un nuevo nombre de instancia, ¿cómo sabrá el servicio de Windows sobre este nuevo nombre? ¿Tiene que transmitirlo durante la construcción del servicio de Windows?
progLearner
¡Gracias! El instalador establecerá el nombre en el servicio de Windows mientras lo instala usando los valores establecidos en el método SetNames () anterior.
tristankoffee
Claro, pero ¿cómo puedes establecer este nombre desde el mundo exterior?
progLearner
En mi respuesta, está el comando que se usa en la línea de comandos para instalar (y desinstalar) el servicio en el mundo exterior. El /ServiceSuffix="UAT"instalador utiliza el valor que ingresa para establecer el sufijo en el servicio. En mi ejemplo, el valor pasado es UAT. En mi escenario, solo quería agregar un sufijo al nombre existente del servicio, pero no hay ninguna razón por la que no pueda adaptar esto para reemplazar el nombre por completo con el valor que se ha pasado.
tristankoffee
Gracias, pero esa es una entrada de línea de comando (= entrada manual), no un código. Según la pregunta original: una vez que tenga un nuevo nombre de instancia, ¿cómo sabrá el servicio de Windows sobre este nuevo nombre? ¿Tiene que transmitirlo durante la construcción del servicio de Windows?
progLearner
4

Lo que hice para que esto funcione es almacenar el nombre del servicio y el nombre para mostrar en una aplicación.config para mi servicio. Luego, en mi clase de instalador, cargo app.config como un XmlDocument y uso xpath para obtener los valores y aplicarlos a ServiceInstaller.ServiceName y ServiceInstaller.DisplayName, antes de llamar a InitializeComponent (). Esto supone que aún no está configurando estas propiedades en InitializeComponent (), en cuyo caso, la configuración de su archivo de configuración será ignorada. El siguiente código es lo que estoy llamando desde mi constructor de clase de instalador, antes de 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;
          }
      }

No creo que leer el archivo de configuración directamente desde ConfigurationManager.AppSettings o algo similar funcione, ya que cuando se ejecuta el instalador, se ejecuta en el contexto de InstallUtil.exe, no en el .exe de su servicio. Es posible que pueda hacer algo con ConfigurationManager.OpenExeConfiguration, sin embargo, en mi caso, esto no funcionó porque estaba tratando de acceder a una sección de configuración personalizada que no estaba cargada.

chris.house.00
fuente
¡Hola Chris House! Me encontré con su respuesta porque estoy construyendo una API web basada en OWIN autohospedada alrededor del programador Quartz.NET y la estoy pegando en un servicio de Windows. ¡Muy hábil! ¡Esperando que estés bien!
NovaJoe
¡Hola Chris House! Me encontré con su respuesta porque estoy construyendo una API web basada en OWIN autohospedada alrededor del programador Quartz.NET y la estoy pegando en un servicio de Windows. ¡Muy hábil! ¡Esperando que estés bien!
NovaJoe
4

Solo para mejorar la respuesta perfecta de @ chris.house.00 esto , puede considerar la siguiente función para leer desde la configuración de su aplicación:

 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";
            }
        }
Teoman shipahi
fuente
2

Tuve una situación similar, en la que necesitaba tener un servicio anterior y un servicio actualizado que se ejecutaba en paralelo en el mismo servidor. (Fue más que un simple cambio de base de datos, también fueron cambios de código). Así que no podía ejecutar el mismo .exe dos veces. Necesitaba un nuevo .exe compilado con nuevas DLL pero del mismo proyecto. El simple hecho de cambiar el nombre del servicio y el nombre para mostrar del servicio no funcionó para mí, todavía recibí el "error del servicio ya existía" que creo que se debe a que estoy usando un Proyecto de implementación. Lo que finalmente funcionó para mí es que dentro de las Propiedades de mi proyecto de implementación hay una propiedad llamada "ProductCode", que es un Guid.

ingrese la descripción de la imagen aquí

Después de eso, reconstruir el proyecto de instalación a un nuevo .exe o .msi instalado correctamente.

cmartin
fuente
1

El enfoque más simple es basar el nombre del servicio en el nombre de la 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;
}
Igor Krupitsky
fuente