reconoce - Instalar varias instancias del mismo servicio de Windows en un servidor
installutil no se reconoce como un comando interno o externo (9)
Así que hemos producido un servicio de Windows para alimentar los datos a nuestra aplicación 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 configurado para apuntar a bases de datos separadas.
Hasta ahora no he podido hacer que esto ocurra y esperaba que mis compañeros de stackoverflow pudieran dar algunas pistas sobre por qué.
Configuración actual:
Configuré el proyecto que contiene el servicio de Windows, lo llamaremos AppService a partir de ahora, y el archivo ProjectInstaller.cs que maneja los pasos de instalación personalizados para establecer el nombre del servicio basado en una clave en el App.config como tal :
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.
A partir de ahora, he intentado dos formas diferentes de instalar ambos servicios y ambos han fallado de manera idéntica.
La primera era 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ó, traté de crear un segundo proyecto de instalador, edité el archivo de configuración y construí el segundo instalador. Cuando ejecuté el instalador, funcionó bien, pero el servicio no apareció en services.msc, así que ejecuté el comando anterior contra la segunda base de código instalada.
Ambas veces recibí el siguiente resultado de InstallUtil (solo partes relevantes):
Ejecutando una instalación transaccionada.
Comenzando la fase de instalación de la instalación.
Instalación del servicio App Service Two ... Service App Service Two se ha instalado correctamente. Creación del servicio de aplicación de fuente 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.
La fase de restitución de la instalación está comenzando.
Restaurar el registro de eventos al estado anterior para el servicio de aplicaciones fuente dos. Service App Service Two se está eliminando del sistema ... Service App Service Dos se eliminó correctamente del sistema.
La fase de restauración se completó con éxito.
La instalación realizada ha finalizado. La instalación falló y se realizó la reversión.
Lo siento por la publicación larga aliento, quería asegurarse de que hay 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 es solo después de que se crea la fuente EventLog que parece que se lanza la NullReferenceException. Entonces, si alguien sabe lo que estoy haciendo mal o tiene un mejor enfoque, sería muy apreciado.
¿Has probado la utilidad sc / service controller? Tipo
sc create
en una línea de comando, y le dará la entrada de ayuda. Creo que ya 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
Lo que he hecho para que esto funcione es almacenar el nombre del servicio y el nombre para mostrar en un app.config para mi servicio. Luego, en mi clase de instalador, cargo el 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 ya no está configurando estas propiedades en InitializeComponent (), en cuyo caso, las configuraciones de su archivo de configuración serán ignoradas. El código siguiente es el que estoy llamando desde el constructor de mi 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 funcionará como 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ó ya que estaba tratando de acceder a una sección de configuración personalizada que no se cargó.
No tuve mucha suerte con los métodos anteriores cuando utilizamos nuestro software de implementación automatizado para instalar / desinstalar con frecuencia los servicios de Windows uno al lado del otro, pero finalmente obtuve 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 se puede adaptar 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 usado para ejecutar las versiones DEVINT, TESTING y ACCEPTANCE del mismo servicio que se ejecutan una al lado de la otra en una sola máquina. Cada versión tiene su propio conjunto de archivos / configuraciones. No he intentado esto para instalar varios servicios apuntando al mismo conjunto de archivos.
NOTA: debe usar el mismo parámetro /ServiceSuffix
para desinstalar el servicio, de modo que ejecute lo siguiente para desinstalar:
InstallUtil.exe /u /ServiceSuffix="UAT" MyService.exe
Otra forma rápida de especificar un valor personalizado para ServiceName
y DisplayName
es usar los parámetros de la línea de comando de installutil
.
En su clase
ProjectInstaller
anule los métodos virtualesInstall(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; } }
- Construye tu proyecto
Instale el servicio con
installutil
agregando su nombre personalizado usando el parámetro/servicename
:installutil.exe /servicename="CustomServiceName" "c:/pathToService/SrvcExecutable.exe"
Tenga en cuenta que si no especifica /servicename
en la línea de comando, el servicio se instalará con los valores ServiceName y DisplayName especificados en ProjectInstaller properties / config
Puede ejecutar varias versiones del mismo servicio haciendo lo siguiente:
1) Copie el archivo ejecutable del servicio y config en su propia carpeta.
2) Copie Install.Exe en 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 los siguientes contenidos (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 los siguientes contenidos:
REM Install
InstallUtil.exe YourService.exe
pause
5) Mientras estás allí, crea un archivo de lote de desinstalación
REM Uninstall
InstallUtil.exe -u YourService.exe
pause
EDITAR:
Tenga en cuenta que si pierdo algo, aquí está la clase ServiceInstaller (ajuste según se requiera):
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());
}
}
}
}
Solo para mejorar la respuesta perfecta de @ chris.house.00 this , 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";
}
}
Tuve una situación similar, donde necesité tener un servicio anterior y un servicio actualizado que se ejecutaba uno al lado del otro en el mismo servidor. (Fue más que solo un cambio de base de datos, también fue el cambio de código). Así que no podría simplemente ejecutar el mismo .exe dos veces. Necesitaba un nuevo .exe compilado con nuevas DLL pero del mismo proyecto. Simplemente cambiar el nombre del servicio y el nombre para mostrar del servicio no funcionó para mí, aún recibí el "error de servicio ya existente", que creo es porque estoy usando un Proyecto de implementación. Lo que finalmente funcionó para mí es dentro de mis propiedades de proyecto de implementación hay una propiedad llamada "ProductCode" que es un Guid.
Después de eso, reconstruir el proyecto de instalación a un nuevo .exe o .msi instalado correctamente.
Una vieja pregunta, lo sé, pero he tenido suerte usando la opción / servicename en InstallUtil.exe. Aunque no lo veo en la lista de la ayuda incorporada.
InstallUtil.exe /servicename="My Service" MyService.exe
No estoy muy seguro de dónde lo leí por primera vez, pero no lo he visto desde entonces. YMMV.
sc create [servicename] binpath= [path to your exe]
Esta solución funcionó para mí.