¿Cuál es la forma más confiable de crear un registro de eventos personalizado y una fuente de eventos durante la instalación de un servicio .Net?

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

Pregunta

Tengo dificultades para crear/eliminar fuentes de eventos de manera confiable durante la instalación de mi servicio .Net de Windows.

Aquí está el código de mi clase ProjectInstaller:

// Create Process Installer
ServiceProcessInstaller spi = new ServiceProcessInstaller();
spi.Account = ServiceAccount.LocalSystem;

// Create Service
ServiceInstaller si = new ServiceInstaller();
si.ServiceName = Facade.GetServiceName();
si.Description = "Processes ...";
si.DisplayName = "Auto Checkout";
si.StartType = ServiceStartMode.Automatic;

// Remove Event Source if already there
if (EventLog.SourceExists("AutoCheckout"))
    EventLog.DeleteEventSource("AutoCheckout");

// Create Event Source and Event Log     
EventLogInstaller log = new EventLogInstaller();
log.Source = "AutoCheckout";
log.Log = "AutoCheckoutLog";

Installers.AddRange(new Installer[] { spi, si, log });

Los métodos de fachada a los que se hace referencia simplemente devuelven las cadenas del nombre del registro, servicio, etc.

Este código funciona la mayor parte del tiempo, pero recientemente, después de la instalación, comencé a mostrar las entradas de mi registro en el Registro de la aplicación en lugar del registro personalizado.Y los siguientes errores también están en el registro:

No se puede encontrar la descripción del ID de evento ( 0 ) en Fuente ( AutoCheckout ).Es posible que la computadora local no tenga la información de registro o los archivos DLL de mensajes necesarios para mostrar mensajes desde una computadora remota.Es posible que pueda utilizar el indicador /AUXSOURCE= para recuperar esta descripción;consulte Ayuda y soporte técnico para obtener más detalles.

Por alguna razón, no elimina correctamente la fuente durante la desinstalación o no la crea durante la instalación.

Se agradece cualquier ayuda con las mejores prácticas aquí.

¡Gracias!

Además, aquí hay un ejemplo de cómo escribo excepciones en el registro:

// Write to Log
EventLog.WriteEntry(Facade.GetEventLogSource(), errorDetails, EventLogEntryType.Error, 99);

Respecto a la respuesta de stephbu: La ruta recomendada es un script de instalación y installutil, o una rutina de instalación de Windows.

Estoy usando un proyecto de instalación, que realiza la instalación del servicio y configura el registro.Ya sea que use installutil.exe o el proyecto de instalación de Windows, creo que ambos llaman a la misma clase ProjectInstaller que muestro arriba.

Veo cómo el estado de mi máquina de prueba podría estar causando el error si el registro no se elimina realmente hasta el reinicio.Experimentaré más para ver si eso resuelve el problema.

Editar:Estoy interesado en una forma segura de registrar la fuente y el nombre del registro durante la instalación del servicio.Entonces, si el servicio se hubiera instalado previamente, eliminaría la fuente o reutilizaría la fuente durante instalaciones posteriores.

Todavía no he tenido la oportunidad de aprender WiX para probar esa ruta.

¿Fue útil?

Solución

La mejor recomendación sería no utilizar el proyecto de instalación en Visual Studio.Tiene limitaciones muy severas.Tuve muy buenos resultados con WiX

Otros consejos

El ServiceInstaller La clase crea automáticamente un EventLogInstaller y lo coloca dentro de su propia colección de instaladores.

Pruebe este código:

ServiceProcessInstaller serviceProcessInstaller = new ServiceProcessInstaller();
serviceProcessInstaller.Password = null;
serviceProcessInstaller.Username = null;
serviceProcessInstaller.Account = ServiceAccount.LocalSystem;

// serviceInstaller
ServiceInstaller serviceInstaller = new ServiceInstaller();
serviceInstaller.ServiceName = "MyService";
serviceInstaller.DisplayName = "My Service";
serviceInstaller.StartType = ServiceStartMode.Automatic;
serviceInstaller.Description = "My Service Description";
// kill the default event log installer
serviceInstaller.Installers.Clear(); 

// Create Event Source and Event Log     
EventLogInstaller logInstaller = new EventLogInstaller();
logInstaller.Source = "MyService"; // use same as ServiceName
logInstaller.Log = "MyLog";

// Add all installers
this.Installers.AddRange(new Installer[] {
   serviceProcessInstaller, serviceInstaller, logInstaller
});

Un par de cosas aquí

La creación de registros de eventos y fuentes sobre la marcha está bastante mal vista.principalmente debido a los derechos necesarios para realizar la acción: realmente no deseas bendecir tus aplicaciones con ese poder.

Además, si elimina un registro de eventos o una fuente, la entrada solo es de verdad se elimina cuando el servidor se reinicia, por lo que puede entrar en estados extraños si elimina y vuelve a crear entradas sin rebotar el cuadro.También hay un montón de reglas no escritas sobre conflictos de nombres debido a la forma en que se almacenan los metadatos en el registro.

La ruta recomendada es un script de instalación y installutil, o una rutina de instalación de Windows.

Tengo que estar de acuerdo con stephbu sobre los "estados extraños" en los que entra el registro de eventos, ya me he encontrado con eso antes.Si tuviera que adivinar, algunas de tus dificultades radican ahí.

Sin embargo, la mejor manera que conozco de realizar el registro de eventos en la aplicación es con TraceListener.Puede configurarlos a través de app.config del servicio:

http://msdn.microsoft.com/en-us/library/system.diagnostics.eventlogtracelistener.aspx

Hay una sección cerca del centro de esa página que describe cómo usar la propiedad EventLog para especificar el EventLog en el que desea escribir.

Espero que ayude.

Yo también seguí helb sugerencia, excepto que básicamente utilicé las clases generadas por el diseñador estándar (los objetos predeterminados "ServiceProcessInstaller1" y "ServiceInstaller1").Decidí publicar esto ya que es una versión un poco más simple;y también porque estoy trabajando en VB y a la gente a veces le gusta ver el estilo VB.

Como tarteodo dicho, no debe modificar la clase ProjectInstaller generada por el diseñador en el ProjectInstaller.Designer.vb archivo, pero usted poder modificar el código en el ProyectoInstaller.vb archivo.Después de crear un ProjectInstaller normal (usando el mecanismo estándar 'Agregar instalador'), el único cambio que hice fue en New() de la clase ProjectInstaller.Después de la llamada normal "InitializeComponent()", inserté este código:

  ' remove the default event log installer 
  Me.ServiceInstaller1.Installers.Clear()

  ' Create an EventLogInstaller, and set the Event Source and Event Log      
  Dim logInstaller As New EventLogInstaller
  logInstaller.Source = "MyServiceName"
  logInstaller.Log = "MyCustomEventLogName"

  ' Add the event log installer
  Me.ServiceInstaller1.Installers.Add(logInstaller)

Esto funcionó como se esperaba, ya que el instalador hizo no no cree el origen del evento en el registro de la aplicación, sino que lo cree en el nuevo archivo de registro personalizado.

Sin embargo, me había equivocado lo suficiente como para tener un poco de desorden en un servidor.El problema con los registros personalizados es que si el nombre del origen del evento existe asociado al equivocado archivo de registro (p. ej.el registro de 'Aplicación' en lugar de su nuevo registro personalizado), primero se debe eliminar el nombre de la fuente;luego la máquina se reinició;entonces la fuente se puede crear con asociación al registro correcto.La Ayuda de Microsoft establece claramente (en el Descripción de la clase EventLogInstaller):

El método de instalación lanza una excepción si la propiedad de origen coincide con un nombre de origen registrado para un registro de eventos diferente en la computadora.

Por lo tanto, también tengo esta función en mi servicio, que se llama cuando se inicia el servicio:

   Private Function EventLogSourceNameExists() As Boolean
      'ensures that the EventSource name exists, and that it is associated to the correct Log 

      Dim EventLog_SourceName As String = Utility.RetrieveAppSetting("EventLog_SourceName")
      Dim EventLog_LogName As String = Utility.RetrieveAppSetting("EventLog_LogName")

      Dim SourceExists As Boolean = EventLog.SourceExists(EventLog_SourceName)
      If Not SourceExists Then
         ' Create the source, if it does not already exist.
         ' An event log source should not be created and immediately used.
         ' There is a latency time to enable the source, it should be created
         ' prior to executing the application that uses the source.
         'So pass back a False to cause the service to terminate.  User will have 
         'to re-start the application to make it work.  This ought to happen only once on the 
         'machine on which the service is newly installed

         EventLog.CreateEventSource(EventLog_SourceName, EventLog_LogName)  'create as a source for the SMRT event log
      Else
         'make sure the source is associated with the log file that we want
         Dim el As New EventLog
         el.Source = EventLog_SourceName
         If el.Log <> EventLog_LogName Then
            el.WriteEntry(String.Format("About to delete this source '{0}' from this log '{1}'.  You may have to kill the service using Task Manageer.  Then please reboot the computer; then restart the service two times more to ensure that this event source is created in the log {2}.", _
            EventLog_SourceName, el.Log, EventLog_LogName))

            EventLog.DeleteEventSource(EventLog_SourceName)
            SourceExists = False  'force a close of service
         End If
      End If
      Return SourceExists
   End Function

Si la función devuelve False, el código de inicio del servicio simplemente detiene el servicio.Esta función prácticamente garantiza que eventualmente obtendrá el nombre correcto de la fuente del evento asociado al archivo de registro de eventos correcto.Es posible que tengas que reiniciar la máquina una vez;y es posible que tengas que intentar iniciar el servicio más de una vez.

Estoy teniendo los mismos problemas.En mi caso, parece que el instalador de Windows agrega automáticamente la fuente del evento que tiene el mismo nombre que mi servicio y esto parece causar problemas.¿Está utilizando el mismo nombre para el servicio de Windows y para la fuente de registro?Intente cambiarlo para que la fuente del registro de eventos se llame de manera diferente al nombre del servicio.

Acabo de publicar una solución a esto en los foros de MSDN y logré solucionarlo utilizando un proyecto MSI de configuración estándar.Lo que hice fue agregar código a los eventos PreInstall y Committed, lo que significó que podía mantener todo lo demás exactamente como estaba:

SortedList<string, string> eventSources = new SortedList<string, string>();
private void serviceProcessInstaller_BeforeInstall(object sender, InstallEventArgs e)
{
  RemoveServiceEventLogs();
}

private void RemoveServiceEventLogs()
{
  foreach (Installer installer in this.Installers)
    if (installer is ServiceInstaller)
    {
      ServiceInstaller serviceInstaller = installer as ServiceInstaller;
      if (EventLog.SourceExists(serviceInstaller.ServiceName))
      {
        eventSources.Add(serviceInstaller.ServiceName, EventLog.LogNameFromSourceName(serviceInstaller.ServiceName, Environment.MachineName));
        EventLog.DeleteEventSource(serviceInstaller.ServiceName);
      }
    }
}

private void serviceProcessInstaller_Committed(object sender, InstallEventArgs e)
{
  RemoveServiceEventLogs();
  foreach (KeyValuePair<string, string> eventSource in eventSources)
  {
    if (EventLog.SourceExists(eventSource.Key))
      EventLog.DeleteEventSource(eventSource.Key);

    EventLog.CreateEventSource(eventSource.Key, eventSource.Value);
  }
}

El código podría modificarse un poco más para eliminar solo las fuentes de eventos que aún no existían o crearlas (aunque el nombre de registro debería almacenarse en algún lugar junto al instalador), pero dado que el código de mi aplicación en realidad crea las fuentes de eventos mientras se ejecuta entonces no tiene sentido para mí.Si ya hay eventos, entonces ya debería haber una fuente de eventos.Para asegurarse de que se creen, puede iniciar automáticamente el servicio.

Experimenté un comportamiento extraño similar porque intenté registrar una fuente de evento con el mismo nombre que el servicio que estaba iniciando.

Noto que también tiene DisplayName configurado con el mismo nombre que la Fuente de su evento.

Al iniciar el servicio, encontramos que Windows registró una entrada "El servicio se inició correctamente" en el registro de la aplicación, con la fuente como DisplayName.Esto pareció tener el efecto de registrar Nombre de la aplicación con el registro de la aplicación.

En mi clase de registro de eventos, luego intenté registrarme. Nombre de la aplicación como fuente con un registro de eventos diferente, pero cuando se trataba de agregar nuevas entradas de registro de eventos, siempre se agregaban al registro de la aplicación.

También recibí el mensaje "La descripción del ID de evento (0) en la fuente" varias veces.

Como solución alternativa, simplemente registré la fuente del mensaje con un nombre ligeramente diferente al DisplayName, y ha funcionado desde entonces.Valdría la pena probar esto si aún no lo has hecho.

El problema proviene de installutil que, de forma predeterminada, registra un origen de evento con el nombre de su servicio en el EventLog de "Aplicación".Todavía estoy buscando una manera de evitar que haga esta basura.Sería realmente bueno si se pudiera influir en el comportamiento de installutil :(

Siguiente helb La sugerencia resolvió el problema por mí.Eliminar el instalador del registro de eventos predeterminado, en el punto indicado en su ejemplo, impidió que el instalador registrara automáticamente mi servicio de Windows en el registro de eventos de la aplicación.

Se perdió demasiado tiempo intentando resolver esta frustrante peculiaridad.¡Un millón de gracias!

FWIW, no pude modificar el código dentro de mi clase ProjectInstaller generada por el diseñador sin que VS se quejara de las modificaciones.Deseché el código generado por el diseñador e ingresé manualmente a la clase.

Agregar una clave de registro vacía a HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\eventlog\Application\MY_CUSTOM_SOURCE_NAME_HERE parece funcionar bien.

Una manera fácil de cambiar el comportamiento predeterminado (es decir, que el instalador del proyecto cree una fuente de registro de eventos con el nombre de su servicio en el registro de la aplicación) es modificar fácilmente el constructor del instalador del proyecto de la siguiente manera:

[RunInstaller( true )]
public partial class ProjectInstaller : System.Configuration.Install.Installer
{
    public ProjectInstaller()
    {
        InitializeComponent();

        //Skip through all ServiceInstallers.
        foreach( ServiceInstaller ThisInstaller in Installers.OfType<ServiceInstaller>() )
        {
            //Find the first default EventLogInstaller.
            EventLogInstaller ThisLogInstaller = ThisInstaller.Installers.OfType<EventLogInstaller>().FirstOrDefault();
            if( ThisLogInstaller == null )
                continue;

            //Modify the used log from "Application" to the same name as the source name. This creates a source in the "Applications and Services log" which separates your service logs from the default application log.
            ThisLogInstaller.Log = ThisLogInstaller.Source;
        }
    }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top