在安装.Net Windows服务期间,我无法可靠地创建/删除事件源。

以下是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 });

引用的facade方法只返回日志,服务等名称的字符串。

此代码大部分时间都有效,但最近安装后我开始在应用程序日志中显示日志条目,而不是自定义日志。并且日志中也存在以下错误:

  

无法找到Source(AutoCheckout)中事件ID(0)的说明。本地计算机可能没有必要的注册表信息或消息DLL文件来显示来自远程计算机的消息。您可以使用/ AUXSOURCE =标志来检索此描述;请参阅帮助和支持以获取详细信

由于某种原因,它在卸载过程中没有正确删除源,或者在安装过程中没有创建它。

欢迎任何有关最佳做法的帮助。

谢谢!

此外,以下是我如何编写日志异常的示例:

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

关于stephbu的回答:建议的路径是安装程序脚本和installutil,或Windows安装例程。

我正在使用安装项目,它执行服务的安装并设置日志。无论我使用的是installutil.exe还是windows安装项目,我相信他们都会调用上面显示的ProjectInstaller类。

如果在重新启动之前没有真正删除日志,我会看到我的测试计算机的状态如何导致错误。我将进行更多实验,看看是否能解决问题。

修改 我对在安装服务期间注册源和日志名称的确定方法感兴趣。因此,如果先前已安装该服务,它将删除源,或在后续安装期间重用该源。

我还没有机会学习WiX来尝试这条路线。

有帮助吗?

解决方案

最好的建议是不要在Visual Studio中使用安装项目。它有非常严重的局限性。 我用 WiX 获得了非常好的结果

其他提示

ServiceInstaller 类自动创建 EventLogInstaller 并将其放在自己的Installers集合中。

试试这段代码:

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

这里的一切事情

动态创建事件日志和来源非常不受欢迎。主要是因为执行操作所需的权利 - 你真的不想用这种力量祝福你的应用程序。

此外,如果删除事件日志或源,则在服务器重新启动时仅删除 条目,因此如果删除并重新创建条目而不弹出框,则可以进入奇怪的状态。由于元数据存储在注册表中的方式,还有一些关于命名冲突的不成文规则。

推荐的路径是安装程序脚本和installutil,或Windows安装例程。

我必须同意stephbu关于“怪异状态”的说法。事件日志进入,我之前遇到过。如果我猜,你的一些困难就在那里。

但是,我知道在应用程序中进行事件记录的最佳方式实际上是使用TraceListener。您可以通过服务的app.config配置它们:

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

该页面中间附近有一节描述如何使用EventLog属性指定要写入的EventLog。

希望有所帮助。

我还遵循 helb的建议,除了我基本上使用标准设计器生成的类(默认对象“ServiceProcessInstaller1”和“ServiceInstaller1”)。我决定发布这个,因为它是一个稍微简单的版本;而且因为我在VB工作,人们有时喜欢看VB方式。

正如 tartheode 所说,你不应该在 ProjectInstaller.Designer.vb 文件中修改设计器生成的ProjectInstaller类,但你可以修改 ProjectInstaller.vb 文件中的代码。在创建一个普通的ProjectInstaller之后(使用标准的“Add Installer”机制),我所做的唯一更改是在ProjectInstaller类的New()中。在正常的“InitializeComponent()”之后打电话,我插入了这段代码:

  ' 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)

这符合预期,因为安装程序在应用程序日志中创建事件源,而是在新的自定义日志文件中创建。

然而,我已经搞砸了,我在一台服务器上有点乱。自定义日志的问题是,如果事件源名称与错误日志文件(例如“应用程序”日志而不是新的自定义日志)相关联,则必须首先删除源名称;然后机器重新启动;然后可以创建与正确日志关联的源。 Microsoft帮助明确说明(在 EventLogInstaller类描述):

  

Install方法抛出异常   如果Source属性匹配   为a注册的源名称   不同的事件登录计算机。

因此,我的服务中也有这个功能,在服务启动时调用:

   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

如果函数返回False,则服务启动代码只会停止服务。此函数几乎可以确保您最终获得与正确的事件日志文件关联的正确事件源名称。您可能需要重启机器一次;并且您可能不得不多次尝试启动该服务。

我遇到了同样的问题。在我的情况下,似乎Windows安装程序正在添加与我的服务同名的事件源,这似乎会导致问题。您是否为Windows服务和日志源使用相同的名称?尝试更改它,以便以不同于服务名称的方式调用事件日志源。

我刚刚在MSDN论坛上发布了一个解决方案,我使用标准的安装MSI项目设法解决了这个问题。我所做的是为PreInstall和Committed事件添加代码,这意味着我可以完全按照原样保留其他所有内容:

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

代码可以进一步修改,只删除尚未存在的事件源或创建它们(尽管logname需要存储在安装程序的某处),但由于我的应用程序代码实际上创建了事件源因为它运行,那对我来说没有意义。如果已有事件,那么应该已经存在事件源。为确保创建它们,您可以自动启动该服务。

我遇到了一些类似的怪异行为,因为我试图注册一个与我正在启动的服务同名的事件源。

我注意到您还将DisplayName设置为与事件源相同的名称。

在启动服务时,我们发现Windows已记录“服务已成功启动”。应用程序日志中的条目,源为DisplayName。这似乎具有将应用程序名称注册到应用程序日志的效果。

在我的事件记录器类中,我后来尝试将 Application Name 注册为具有不同事件日志的源,但是当添加新的事件日志条目时,它们总是被添加到应用程序日志中。

我还得到了“Source中的事件ID(0)的描述”。消息多次。

作为一种解决方法,我只是使用与DisplayName略有不同的名称注册消息源,从那时起它就一直有效。如果你还没有,那就值得尝试。

问题来自installutil,默认情况下,在“应用程序”中注册一个带有服务名称的事件源。事件簿。我还在寻找一种方法来阻止它做这个垃圾。如果可以影响installutil的行为,那将是非常好的:(

按照 helb的建议解决了我的问题。在示例中指示的位置杀死默认事件日志安装程序会阻止安装程序在应用程序事件日志下自动注册我的Windows服务。

试图解决这个令人沮丧的怪癖,失去了太多时间。万分感谢!

FWIW,我无法修改设计器生成的ProjectInstaller类中的代码,而不会导致VS关于mods。我取消了设计器生成的代码并手动输入了类。

将空注册表项添加到HKEY_LOCAL_MACHINE \ SYSTEM \ CurrentControlSet \ services \ eventlog \ Application \ MY_CUSTOM_SOURCE_NAME_HERE似乎工作正常。

更改默认行为(即项目安装程序在应用程序日志中使用服务名称创建事件日志源)的简单方法是轻松修改项目安装程序的构造函数,如下所示:

[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;
        }
    }
}
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top