イベントログと戦う(C#編)
このイベントログに対する執着心と来たら。
一応C#で動くイベントログクリーナを書き直してみた。
僕自身は頑張ったつもりだけど、今度もまた突っ込み所があると思うけど気にしない方向で。
仕様的には、レジストリから収集したイベントログリストを用いて、そのログが有効かどうか調べて、有効なら件数を取得する。
その後、有効かつ0件超過のレコードならイベントログをクリアする……みたいな。
ソースが多いからGitHubにでも上げた方がいいのかなぁ、と考えつつここに上げる。
一部.NETのソースコードを参考にしている部分がありますあります*1。
Visual Studio 2015 Community + Windows 8.1 (x64)で作成、動作を確認してます*2。
2015/07/31 22:33追記
DllImportの呼び出し規約が間違えていたので修正しました。
以下、明日使えないゴミ駄文。
EventLogs.cs
using System; using System.Collections; using System.Collections.Generic; using System.Runtime.InteropServices; using Microsoft.Win32; namespace EventLogCleaner { /// <summary>イベントログの最小単位</summary> class EventLogEntity : IComparable { private string _name; private ulong _records; private bool _enabled; /// <summary>イベントログの名前</summary> public string Name { get { return _name; } } /// <summary>イベントログのレコード件数</summary> public ulong Records { get { return _records; } } /// <summary>ログが有効かどうか</summary> public bool Enabled { get { return _enabled; } } /// <summary>超デフォルトコンストラクタ</summary> /// <param name="name"></param> public EventLogEntity( string name ) { this._name = name; this._records = 0; this._enabled = false; GetLogInfo(); } /// <summary>情報を取得します</summary> private void GetLogInfo() { GetIsEnabled(); if ( _enabled ) GetRecordsNum(); } /// <summary>イベントログの件数を取得します</summary> private void GetRecordsNum() { IntPtr hEvtLog = IntPtr.Zero; IntPtr buffer = IntPtr.Zero; uint bufferNeeded = 0; bool res = false; int w32Err = 0; ulong recordsNum = 0; try { hEvtLog = NativeMethods.EvtOpenLog( IntPtr.Zero, this._name, EVT_OPEN_LOG_FLAGS.EvtOpenChannelPath ); if ( hEvtLog == IntPtr.Zero ) throw WindowsErrors.MakeException( Marshal.GetLastWin32Error() ); res = NativeMethods.EvtGetLogInfo( hEvtLog, EVT_LOG_PROPERTY_ID.EvtLogNumberOfLogRecords, 0, IntPtr.Zero, out bufferNeeded ); w32Err = Marshal.GetLastWin32Error(); if ( !res && w32Err != NativeMethods.ERROR_INSUFFICIENT_BUFFER ) throw WindowsErrors.MakeException( w32Err ); buffer = Marshal.AllocHGlobal( (int)bufferNeeded ); res = NativeMethods.EvtGetLogInfo( hEvtLog, EVT_LOG_PROPERTY_ID.EvtLogNumberOfLogRecords, bufferNeeded, buffer, out bufferNeeded ); if ( !res ) throw WindowsErrors.MakeException( w32Err ); recordsNum = Marshal.PtrToStructure<ulong>( buffer ); } finally { if ( buffer != IntPtr.Zero ) Marshal.FreeHGlobal( buffer ); if ( hEvtLog != IntPtr.Zero ) NativeMethods.EvtClose( hEvtLog ); } this._records = recordsNum; } /// <summary>イベントログが有効かどうか取得します</summary> private void GetIsEnabled() { IntPtr hEvtLog = IntPtr.Zero; IntPtr buffer = IntPtr.Zero; uint bufferNeeded = 0; bool res = false; int w32Err = 0; bool isEnabled = false; try { hEvtLog = NativeMethods.EvtOpenChannelConfig( IntPtr.Zero, this._name, 0 ); if ( hEvtLog == IntPtr.Zero ) throw WindowsErrors.MakeException( Marshal.GetLastWin32Error() ); res = NativeMethods.EvtGetChannelConfigProperty( hEvtLog, EVT_CHANNEL_CONFIG_PROPERTY_ID.EvtChannelConfigEnabled, 0, 0, IntPtr.Zero, out bufferNeeded ); w32Err = Marshal.GetLastWin32Error(); if ( !res && w32Err != NativeMethods.ERROR_INSUFFICIENT_BUFFER ) throw WindowsErrors.MakeException( w32Err ); buffer = Marshal.AllocHGlobal( (int)bufferNeeded ); res = NativeMethods.EvtGetChannelConfigProperty( hEvtLog, EVT_CHANNEL_CONFIG_PROPERTY_ID.EvtChannelConfigEnabled, 0, bufferNeeded, buffer, out bufferNeeded ); if ( !res ) throw WindowsErrors.MakeException( w32Err ); isEnabled = Marshal.PtrToStructure<bool>( buffer ); } finally { if ( buffer != IntPtr.Zero ) Marshal.FreeHGlobal( buffer ); if ( hEvtLog != IntPtr.Zero ) NativeMethods.EvtClose( hEvtLog ); } this._enabled = isEnabled; } /// <summary>ログをクリアします</summary> /// <returns>関数実行の可否</returns> public bool ClearLog() { if ( _records > 0UL && _enabled ) { bool res; res = NativeMethods.EvtClearLog( IntPtr.Zero, _name, null, 0 ); if ( !res ) throw WindowsErrors.MakeException( Marshal.GetLastWin32Error() ); return true; } return false; } /// <summary>ToString()のオーバーライド</summary> /// <returns></returns> public override string ToString() { string str = string.Format( "{0}, {1}, {2}", _name, _enabled, _records ); return str; } /// <summary>Sort()用比較関数</summary> /// <param name="other">比較対象</param> /// <returns>比較結果</returns> public int CompareTo( object other ) { EventLogEntity castedOther = other as EventLogEntity; if ( castedOther == null ) throw new ArgumentException( "otherが正しくキャストできませんでした" ); string a = this._name.ToLower(); string b = castedOther._name.ToLower(); return a.CompareTo( b ); } } /// <summary>イベントログを束ねるクラス</summary> class EventLogs : IEnumerable, IEnumerable<EventLogEntity> { /// <summary>イベントログの収集先</summary> static readonly string[] EventLogEntries = new string[] { @"SYSTEM\CurrentControlSet\services\eventlog", @"SOFTWARE\Microsoft\Windows\CurrentVersion\WINEVT\Channels" }; /// <summary>イベントログ集</summary> private List<EventLogEntity> logs; /// <summary>超デフォルトコンストラクタ</summary> public EventLogs() { logs = new List<EventLogEntity>(); } /// <summary>IEnumeratorを返します</summary> /// <returns>イベントログのIEnumerator</returns> IEnumerator IEnumerable.GetEnumerator() { return logs.GetEnumerator(); } /// <summary>IEnumeratorを返します</summary> /// <returns>イベントログのIEnumerator</returns> IEnumerator<EventLogEntity> IEnumerable<EventLogEntity>.GetEnumerator() { return logs.GetEnumerator(); } /// <summary>イベントログエントリを収集します</summary> /// <returns>関数の成功可否</returns> public bool CollectLogs() { // レジストリから回収 for ( int i = 0; i < EventLogEntries.Length; i++ ) { using ( RegistryKey baseKey = RegistryKey.OpenBaseKey( RegistryHive.LocalMachine, RegistryView.Registry64 ) ) { using ( RegistryKey regKey = baseKey.OpenSubKey( EventLogEntries[ i ] ) ) { string[] subKeys = regKey.GetSubKeyNames(); StringArrayToList( subKeys ); } } } logs.Sort(); // Systemは末尾にしたい DeleteSystemArray(); logs.Add( new EventLogEntity( "System" ) ); return true; } /// <summary>文字列配列からEventLogEntityを生成し、配列に格納します</summary> /// <param name="keys">文字列配列</param> private void StringArrayToList( string[] keys ) { for ( int i = 0; i < keys.Length; i++ ) { EventLogEntity entity = new EventLogEntity( keys[ i ] ); logs.Add( entity ); } } /// <summary>配列からSystemを削除します</summary> /// <returns>関数実行の可否</returns> private bool DeleteSystemArray() { EventLogEntity system = logs.Find( x => x.Name == "System" ); if ( system == null ) return false; logs.Remove( system ); return true; } /// <summary>ログを削除します</summary> /// <returns></returns> public bool ClearLogs() { for ( int i = 0; i < logs.Count; i++ ) { EventLogEntity entity = logs[ i ]; if ( entity.Records > 0UL && entity.Enabled ) { bool res = false; try { Console.Write( "{0} -> ", entity.Name ); res = entity.ClearLog(); Console.WriteLine( "Success" ); } catch (Exception ex ) { Console.WriteLine( "Failed ({0})", ex.Message ); } } } return true; } } }
EventLogNative.cs
using System; using System.Runtime.InteropServices; namespace EventLogCleaner { /// <summary>EvtGetChannelConfigProperty()で呼び出すプロパティID</summary> public enum EVT_CHANNEL_CONFIG_PROPERTY_ID { EvtChannelConfigEnabled = 0, EvtChannelConfigIsolation = 1, EvtChannelConfigType = 2, EvtChannelConfigOwningPublisher = 3, EvtChannelConfigClassicEventlog = 4, EvtChannelConfigAccess = 5, EvtChannelLoggingConfigRetention = 6, EvtChannelLoggingConfigAutoBackup = 7, EvtChannelLoggingConfigMaxSize = 8, EvtChannelLoggingConfigLogFilePath = 9, EvtChannelPublishingConfigLevel = 10, EvtChannelPublishingConfigKeywords = 11, EvtChannelPublishingConfigControlGuid = 12, EvtChannelPublishingConfigBufferSize = 13, EvtChannelPublishingConfigMinBuffers = 14, EvtChannelPublishingConfigMaxBuffers = 15, EvtChannelPublishingConfigLatency = 16, EvtChannelPublishingConfigClockType = 17, EvtChannelPublishingConfigSidType = 18, EvtChannelPublisherList = 19, EvtChannelPublishingConfigFileMax = 20, EvtChannelConfigPropertyIdEND = 21 } /// <summary>EvtOpenLog()で指定するファイルの開き方</summary> public enum EVT_OPEN_LOG_FLAGS : uint { EvtOpenChannelPath = 0x1, EvtOpenFilePath = 0x2 } /// <summary>EvtGetLogInfo()で呼び出すプロパティID</summary> public enum EVT_LOG_PROPERTY_ID { EvtLogCreationTime = 0, EvtLogLastAccessTime = 1, EvtLogLastWriteTime = 2, EvtLogFileSize = 3, EvtLogAttributes = 4, EvtLogNumberOfLogRecords = 5, EvtLogOldestRecordNumber = 6, EvtLogFull = 7 } static class NativeMethods { /// <summary>メモリが足らんってエラーのエラーコード。一部判定で使う</summary> public const int ERROR_INSUFFICIENT_BUFFER = 122; /// <summary>チャンネル設定のプロパティを開きます</summary> /// <param name="Session">リモートセッションのハンドル。不要な場合はnull</param> /// <param name="ChannelPath">アクセスするチャンネル名</param> /// <param name="Flags">予約済み。0を指定</param> /// <returns></returns> [DllImport( "Wevtapi.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode )] public static extern IntPtr EvtOpenChannelConfig( IntPtr Session, string ChannelPath, uint Flags ); /// <summary>チャンネルのプロパティを取得します </summary> /// <param name="ChannelConfig">EvtOpenChannelConfig()で取得したチャンネルのハンドル</param> /// <param name="PropertyId">プロパティID</param> /// <param name="Flags">予約済み。常に0を指定</param> /// <param name="PropertyValueBufferSize">PropertyValueBufferのバッファサイズ</param> /// <param name="PropertyValueBuffer">受け取るバッファ。nullにすると必要サイズを返す</param> /// <param name="PropertyValueBufferUsed">使用したバッファサイズを受け取る変数</param> /// <returns></returns> [DllImport( "Wevtapi.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode )] public static extern bool EvtGetChannelConfigProperty( IntPtr ChannelConfig, EVT_CHANNEL_CONFIG_PROPERTY_ID PropertyId, uint Flags, uint PropertyValueBufferSize, IntPtr PropertyValueBuffer, out uint PropertyValueBufferUsed ); /// <summary>イベントログを開きます</summary> /// <param name="Session">リモートセッションのハンドル。不要な場合はnull</param> /// <param name="Path">アクセスするチャンネル名</param> /// <param name="Flags">チャンネルの開き方を指定</param> /// <returns></returns> [DllImport( "Wevtapi.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode )] public static extern IntPtr EvtOpenLog( IntPtr Session, string Path, EVT_OPEN_LOG_FLAGS Flags ); /// <summary>ログの情報を取得します</summary> /// <param name="Log">ログのハンドル</param> /// <param name="PropertyId">取得する情報</param> /// <param name="PropertyValueBufferSize">バッファのサイズ</param> /// <param name="PropertyValueBuffer">情報を受け取るバッファ。nullを指定すると必要なサイズを返します</param> /// <param name="PropertyValueBufferUsed">実際に使用したバッファのサイズを受け取る変数</param> /// <returns></returns> [DllImport( "Wevtapi.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode )] public static extern bool EvtGetLogInfo( IntPtr Log, EVT_LOG_PROPERTY_ID PropertyId, uint PropertyValueBufferSize, IntPtr PropertyValueBuffer, out uint PropertyValueBufferUsed ); /// <summary>開いたハンドルを閉じます</summary> /// <param name="Object">オブジェクトのハンドル</param> /// <returns></returns> [DllImport( "Wevtapi.dll", SetLastError = false, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode )] public static extern bool EvtClose( IntPtr Object ); /// <summary>指定したログをクリアします</summary> /// <param name="Session">リモートセッションのハンドル。不要な場合はnull</param> /// <param name="ChannelPath">アクセスするチャンネル名</param> /// <param name="TargetFilePath">対象ログのフルパスを指定。不要な場合はnull</param> /// <param name="Flags">予約済み。0を指定</param> /// <returns></returns> [DllImport( "Wevtapi.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode )] public static extern bool EvtClearLog( IntPtr Session, string ChannelPath, string TargetFilePath, uint Flags ); } }
WindowsErrors.cs
using System; using System.ComponentModel; using System.Runtime.InteropServices; using System.Text; namespace EventLogCleaner { public static class WindowsErrors { /// <summary>dwFlagsで指定する列挙型</summary> private enum Flags : uint { /// <summary> /// <para>FormatMessage 関数に、バッファの割り当てを要求します。</para> /// <para>このフラグを指定した場合は、lpBuffer パラメータで PVOID 型変数へのポインタを、nSize パラメータで出力メッセージバッファに割り当てる最小値を TCHAR 単位で指定してください。</para> /// <para>FormatMessage 関数は、書式化済みのメッセージを格納するのに十分なサイズのバッファを自動的に割り当て、そのバッファへのポインタを、lpBuffer で指定されたアドレスに格納します。</para> /// <para>呼び出し側は、このバッファが不要になったら、 関数を使ってバッファを解放するべきです。</para> /// </summary> FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100, /// <summary> /// <para>メッセージ定義の中の挿入シーケンスを無視して、何も変更を加えずに出力バッファへ渡すよう要求します。</para> /// <para>後で書式化を行うために、メッセージの取り出しを行う場合に役立ちます。このフラグをセットすると、Arguments パラメータは無視されます。</para> /// </summary> FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200, /// <summary> /// <para>pSource パラメータが、NULL で終わるメッセージ定義へのポインタであると指定します。</para> /// <para>メッセージテーブルリソース内のメッセージテキストと同様、このメッセージ定義が、挿入シーケンスを含んでいてもかまいません。</para> /// <para>FORMAT_MESSAGE_FROM_HMODULE や FORMAT_MESSAGE_FROM_SYSTEM の各フラグと同時に指定することはできません。</para> /// </summary> FORMAT_MESSAGE_FROM_STRING = 0x00000400, /// <summary> /// <para>lpSource が、検索対象のメッセージテーブルリソースを保持しているモジュールのハンドルであると指定します。</para> /// <para>lpSource パラメータに NULL を指定すると、現在のプロセスで動作しているアプリケーションのイメージファイルを検索対象にします。</para> /// <para>FORMAT_MESSAGE_FROM_STRING フラグと同時に指定することはできません。</para> /// </summary> FORMAT_MESSAGE_FROM_HMODULE = 0x00000800, /// <summary> /// <para>要求されたメッセージを、システムメッセージテーブルリソースから検索するよう指定します。</para> /// <para>FORMAT_MESSAGE_FROM_HMODULE フラグと同時に指定した場合は、lpSource で指定されたモジュール内にメッセージが見つからないと、システムメッセージテーブル内で検索を行います。</para> /// <para>このフラグを指定すると、アプリケーションは GetLastError 関数の結果を FormatMessage 関数に渡して、システム定義のエラーに対応するメッセージテキストを取得できます。</para> /// </summary> FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000, /// <summary> /// <para>Arguments パラメータが 1 個の va_list 構造体ではなく、複数の引数を表す値からなる 1 つの配列へのポインタであることを指定します。</para> /// </summary> FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000, /// <summary> /// <para>この関数は、メッセージ定義テキスト内の通常の改行記号を無視します。</para> /// <para>そして、メッセージ定義テキスト内に存在する、ハードコード化された改行指定記号を、改行記号として出力バッファへ出力します。</para> /// <para>この関数は、新しい改行記号を生成しません。</para> /// </summary> FORMAT_MESSAGE_MAX_WIDTH_MASK = 0x000000FF, }; /// <summary> /// <para>メッセージ文字列を書式化します(書式を割り当てます)。</para> /// <para>この関数は、入力としてメッセージ定義を受け取ります。</para> /// <para>メッセージ定義は、この関数に渡すバッファ経由で渡すことができます。</para> /// <para>代わりに、ロード済みのモジュール内のメッセージテーブルリソースを使うよう指示することもできます。</para> /// <para>または、システムのメッセージテーブルリソースからメッセージ定義を検索するよう指示することもできます。</para> /// <para>この関数は、メッセージ識別子と言語識別子に基づいて、メッセージテーブルリソース内のメッセージ定義を検索します。</para> /// <para>要求に応じて、埋め込まれた挿入シーケンスを処理し、書式化されたメッセージテキストを出力バッファへコピーします。</para> /// </summary> /// <param name="dwFlags">入力元と処理方法のオプション</param> /// <param name="lpSource">メッセージの入力元</param> /// <param name="dwMessageId">メッセージ識別子</param> /// <param name="dwLanguageId">言語識別子</param> /// <param name="lpBuffer">メッセージバッファ</param> /// <param name="nSize">メッセージバッファの最大サイズ</param> /// <param name="Arguments">複数のメッセージ挿入シーケンスからなる配列</param> /// <returns></returns> [DllImport( "kernel32.dll", CallingConvention = CallingConvention.Winapi, SetLastError = true )] private static extern uint FormatMessage( Flags dwFlags, IntPtr lpSource, int dwMessageId, uint dwLanguageId, StringBuilder lpBuffer, int nSize, IntPtr Arguments ); /// <summary>Windowsのエラーコードからメッセージを取得します</summary> /// <param name="lastError">Windowsのエラーコード</param> /// <returns>エラーの詳細</returns> public static string GetLastMessage( int lastError ) { StringBuilder result = new StringBuilder( 2048 ); FormatMessage( Flags.FORMAT_MESSAGE_FROM_SYSTEM, IntPtr.Zero, lastError, 0, result, result.Capacity, IntPtr.Zero ); return result.ToString().Trim(); } /// <summary>Windowsのエラーコードから例外クラスを生成します</summary> /// <param name="lastError">Windowsのエラーコード</param> /// <param name="additionalMsg">追加のメッセージ(string.Format形式)</param> /// <param name="args">出力用変数</param> /// <returns></returns> public static Win32Exception MakeException( int lastError, string additionalMsg = null, params object[] args ) { string message = string.Format( "{0}", GetLastMessage( lastError ) ); Win32Exception ex; if ( additionalMsg != null ) message += "\r\n" + string.Format( additionalMsg, args ); ex = new Win32Exception( lastError, message ); return ex; } /// <summary>エラーコードからWin32Exceptionクラスを生成し、送出します</summary> /// <param name="lastError">Windowsのエラーコード</param> /// <param name="additionalMsg">追加のメッセージ(string.Format形式)</param> /// <param name="args">出力用変数</param> public static void ThrowWin32Exception( int lastError, string additionalMsg = null, params object[] args ) { Win32Exception ex = MakeException( lastError, additionalMsg, args ); throw ex; } } }
Program.cs
using System; namespace EventLogCleaner { class Program { static void Main( string[] args ) { EventLogs logs = new EventLogs(); logs.CollectLogs(); logs.ClearLogs(); } } }