.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 });
参照されるファサードメソッドは、ログ、サービスなどの名前の文字列を返すだけです。
このコードはほとんどの場合機能しますが、最近インストールした後、カスタムログではなくアプリケーションログにログエントリが表示されるようになりました。また、次のエラーもログに記録されます。
ソース(AutoCheckout)のイベントID(0)の説明が見つかりません。ローカルコンピュータには、リモートコンピュータからのメッセージを表示するために必要なレジストリ情報またはメッセージDLLファイルがない可能性があります。/AUXSOURCE=フラグを使用してこの説明を取得できる場合があります。ヘルプとを参照してください。詳細のサポート。
何らかの理由で、アンインストール中にソースが適切に削除されていないか、インストール中にソースが作成されていません。
ここでのベストプラクティスに関するヘルプをいただければ幸いです。
ありがとう!
さらに、ログに例外を書き込む方法のサンプルを次に示します。
// Write to Log
EventLog.WriteEntry(Facade.GetEventLogSource(), errorDetails, EventLogEntryType.Error, 99);
stephbuの回答に関して:推奨されるパスは、インストーラースクリプトとinstallutil、またはWindowsセットアップルーチンです。
サービスのインストールを実行し、ログを設定するセットアッププロジェクトを使用しています。 installutil.exeを使用するかWindowsセットアッププロジェクトを使用するかにかかわらず、どちらも上記の同じProjectInstallerクラスを呼び出すと思います。
再起動するまでログが本当に削除されない場合、テストマシンの状態がエラーの原因になっている可能性があることがわかります。それが問題を解決するかどうかを確認するために、さらに実験を行います。
編集:サービスのインストール中にソースとログ名を登録する確実な方法に興味があります。したがって、サービスが以前にインストールされていた場合は、ソースを削除するか、後続のインストール中にソースを再利用します。
そのルートを試すためにWiXを学ぶ機会はまだありません。
VisualStudioでセットアッププロジェクトを使用しないことをお勧めします。非常に厳しい制限があります。 WiX で非常に良い結果が得られました
ServiceInstaller
クラスは自動的にEventLogInstaller
を作成し、それを独自のインストーラーコレクション内に配置します。
このコードを試してください:
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
});
ここにいくつかのことがあります
その場でイベントログとソースを作成することはかなり嫌われています。主にアクションを実行するために必要な権限のためです-あなたは本当にその力であなたのアプリケーションを祝福したくありません。
さらに、イベントログまたはソースを削除した場合、エントリはサーバーの再起動時にのみtrue削除されるため、ボックスをバウンスせずにエントリを削除して再作成すると、奇妙な状態になる可能性があります。メタデータがレジストリに保存される方法に起因する名前の競合に関する不文律もたくさんあります。
推奨されるパスは、インストーラースクリプトとinstallutil、またはWindowsセットアップルーチンです。
イベントログが入る「奇妙な状態」についてstephbuに同意する必要があります、私は以前にそれに遭遇しました。私が推測すると、あなたの困難のいくつかはそこにあります。
ただし、アプリケーションでイベントロギングを行うために私が知っている最善の方法は、実際にはTraceListenerを使用することです。これらは、サービスのapp.configを介して構成できます。
http://msdn.Microsoft.com/en-us/library/system.diagnostics.eventlogtracelistener.aspx
そのページの中央近くに、EventLogプロパティを使用して書き込みたいEventLogを指定する方法を説明するセクションがあります。
お役に立てば幸いです。
また、基本的に標準のデザイナー生成クラス(デフォルトオブジェクト「ServiceProcessInstaller1」および「ServiceInstaller1」)を使用したことを除いて、helbの提案に従いました。少しシンプルなバージョンなので、投稿することにしました。また、私はVBで働いており、VBウェイを見たいと思うことがあるからです。
tartheodeが言ったように、ProjectInstaller.Designer.vbでデザイナーが生成したProjectInstallerクラスを変更しないでください。 )ファイルですが、はProjectInstaller.vbファイルのコードを変更できます。通常のProjectInstallerを作成した後(標準の「インストーラーの追加」メカニズムを使用)、私が行った唯一の変更は、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)
これは、インストーラーがアプリケーションログにイベントソースを作成しなかったのではなく、新しいカスタムログファイルに作成したという点で、期待どおりに機能しました。
しかし、私は1台のサーバーで少し混乱するほど、めちゃくちゃになってしまいました。カスタムログの問題は、イベントソース名が間違ったログファイルに関連付けられている場合(たとえば、新しいカスタムではなく「アプリケーション」ログ)です。 log)、次にソース名を最初に削除する必要があります。その後、マシンが再起動しました。次に、正しいログに関連付けてソースを作成できます。 Microsoftヘルプには、( EventLogInstallerクラスの説明 )と明記されています。
Sourceプロパティが、コンピューター上の別のイベントログに登録されているソース名と一致する場合、Installメソッドは例外をスローします。
したがって、サービスの開始時に呼び出されるこの関数もサービスにあります。
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を返す場合、サービス起動コードは単にサービスを停止します。この関数は、正しいイベントログファイルに関連付けられた正しいイベントソース名を最終的に取得することをほぼ確実にします。マシンを一度再起動する必要があるかもしれません。また、サービスの開始を複数回試行する必要がある場合があります。
これに対する解決策を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);
}
}
コードをもう少し変更して、まだ存在していないイベントソースのみを削除するか、作成することができます(ただし、ログ名はインストーラーに対してどこかに保存する必要があります)が、アプリケーションコードは、実行時にイベントソースを実際に作成するためです。それなら私には意味がありません。すでにイベントがある場合は、イベントソースがすでに存在するはずです。それらが確実に作成されるようにするには、サービスを自動的に開始するだけです。
私も同じ問題を抱えています。私の場合、Windowsインストーラーが私のサービスと同じ名前のイベントソースを自動的に追加しているようで、これが問題を引き起こしているようです。 Windowsサービスとログソースに同じ名前を使用していますか?イベントログソースがサービスの名前とは異なる方法で呼び出されるように変更してみてください。
空のレジストリキーを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;
}
}
}
この問題は、デフォルトで「アプリケーション」EventLogにサービス名を使用してイベントソースを登録するinstallutilに起因します。私はまだそれがこのがらくたをするのを止める方法を探しています。 installutilの動作に影響を与えることができれば本当に素晴らしいでしょう:(
次のhelb's提案は、私にとって問題を解決しました。彼の例に示されている時点でデフォルトのイベントログインストーラーを強制終了すると、インストーラーはアプリケーションイベントログにWindowsサービスを自動的に登録できなくなりました。
この苛立たしい癖を解決しようとして、あまりにも多くの時間が失われました。どうもありがとう!
FWIW、デザイナーが生成したProjectInstallerクラス内のコードを変更するには、VSにmodについての鯉を振るわせないでください。デザイナーが生成したコードを破棄し、手動でクラスに参加しました。
開始したサービスと同じ名前でイベントソースを登録しようとしたため、同様の奇妙な動作が発生しました。
DisplayNameもイベントソースと同じ名前に設定されていることに気付きました。
サービスを起動すると、Windowsが「サービスが正常に開始されました」というエントリをアプリケーションログに記録し、ソースをDisplayNameとして記録していることがわかりました。これは、アプリケーションログにアプリケーション名を登録する効果があるようです。
私のイベントロガークラスでは、後で別のイベントログのソースとしてアプリケーション名を登録しようとしましたが、新しいイベントログエントリを追加する場合、それらは常にアプリケーションログに追加されました。
また、「ソース内のイベントID(0)の説明」というメッセージが何度か表示されました。
回避策として、メッセージソースをDisplayNameとは少し異なる名前で登録しただけで、それ以来機能しています。まだ行っていない場合は、これを試す価値があります。