The Grimoire of Nonsense

個人的なメモを残すブログ

イベントログと戦う(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();
		}
	}
}

*1:関数からデータを受け取る時、まずバッファをnullにして必要なバイト数を受け取りもう一度関数を呼ぶ所

*2:ただ、動作を完全に保証するわけではありません