JNAからWindows Event Log APIを呼び出してイベントログを取得してみる(ブックマーク利用)

Windows Event Log API にはブックマークなる機能があるらしいので、これを JNA 経由で使ってみました。

Bookmarking Events (Windows)

長ったらしいですが、以下のような感じになりました。
ブックマークは XML としてファイルに保存するのですが、初回処理などでブックマークがなかった場合は「ハンドルの先頭にシークして処理、その後、ブックマークを作成する」、ブックマークがある場合は「ブックマークへシークして処理、その後、ブックマークを更新する」という感じになっています。いろいろまずい箇所がある気がしますが、とりあえずこんなところとしておきます・・・

・WindowsEventTest.java

package com.example.jna.winevent;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

import com.sun.jna.Library;
import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.WString;
import com.sun.jna.platform.win32.WinDef;
import com.sun.jna.platform.win32.WinNT.HANDLE;
import com.sun.jna.ptr.IntByReference;

public class WindowsEventTest {
	public interface Kernel32 extends Library {
		Kernel32 INSTANCE = (Kernel32) Native.loadLibrary("kernel32", Kernel32.class);

		int GetLastError();

		Pointer LocalFree(Pointer hMem);
	}

	public interface Wevtapi extends Library {
		Wevtapi INSTANCE = (Wevtapi) Native.loadLibrary("wevtapi", Wevtapi.class);

		// EVT_QUERY_FLAGS
		public final int EvtQueryChannelPath = 0x1;
		public final int EvtQueryFilePath = 0x2;
		public final int EvtQueryForwardDirection = 0x100;
		public final int EvtQueryReverseDirection = 0x200;
		public final int EvtQueryTolerateQueryErrors = 0x1000;

		// EVT_SEEK_FLAGS
		public final int EvtSeekRelativeToFirst = 1;
		public final int EvtSeekRelativeToLast = 2;
		public final int EvtSeekRelativeToCurrent = 3;
		public final int EvtSeekRelativeToBookmark = 4;
		public final int EvtSeekOriginMask = 7;
		public final int EvtSeekStrict = 0x10000;

		// EVT_RENDER_FLAGS
		public final int EvtRenderEventValues = 0;
		public final int EvtRenderEventXml = 1;
		public final int EvtRenderBookmark = 2;

		HANDLE EvtQuery(HANDLE Session, WString Path, WString Query, int Flags);

		boolean EvtSeek(HANDLE ResultSet, WinDef.LONGLONG Position, HANDLE Bookmark, int Timeout, int Flags);

		boolean EvtNext(HANDLE ResultSet, int EventArraySize, HANDLE[] EventArray, int Timeout, int Flags,
				IntByReference Returned);

		boolean EvtRender(HANDLE Context, HANDLE Fragment, int Flags, int BufferSize, Pointer Buffer,
				IntByReference BufferUsed, IntByReference PropertyCount);

		HANDLE EvtCreateBookmark(WString BookmarkXml);

		boolean EvtUpdateBookmark(HANDLE Bookmark, HANDLE Event);

		boolean EvtClose(HANDLE h);
	}

	private String fileName;

	public WindowsEventTest(String fileName) {
		this.fileName = fileName;
	}

	public static void main(String[] args) {
		String fileName = "bookmark.xml";
		String query = "<QueryList><Query><Select Path='Application'>*[System[(EventID=100)]]</Select><Select Path='System'>*[System[(EventID=100)]]</Select></Query></QueryList>";

		WindowsEventTest wet = new WindowsEventTest(fileName);

		String bookmarkString = wet.getBookmartString(fileName);

		String eventLog = wet.getEventLog(query, bookmarkString);
		System.out.println(eventLog);
	}

	private String getEventLog(String query, String bookmarkString) {
		StringBuffer eventLog = new StringBuffer();
		HANDLE hResults = null;
		HANDLE hBookmark = null;
		try {
			WString wQuery = new WString(query);
			hResults = Wevtapi.INSTANCE.EvtQuery(null, null, wQuery,
					Wevtapi.INSTANCE.EvtQueryChannelPath | Wevtapi.INSTANCE.EvtQueryTolerateQueryErrors);
			if (hResults == null)
				throw new Exception("EvtQuery Error errorCode=" + getLastErrorWrapper());

			//ブックマークがあればブックマークへシーク
			if (!bookmarkString.isEmpty()) {
				hBookmark = Wevtapi.INSTANCE.EvtCreateBookmark(new WString(bookmarkString));
				if (hBookmark == null)
					throw new Exception("EvtCreateBookmark Error errorCode=" + getLastErrorWrapper());

				boolean ret = Wevtapi.INSTANCE.EvtSeek(hResults, new WinDef.LONGLONG(0), hBookmark, 0,
						Wevtapi.INSTANCE.EvtSeekRelativeToBookmark);
				if (!ret)
					throw new Exception("EvtSeek Error errorCode=" + getLastErrorWrapper());
				
			//ブックマークがなければハンドルの先頭へシーク
			} else {
				boolean ret = Wevtapi.INSTANCE.EvtSeek(hResults, new WinDef.LONGLONG(0), null, 0,
						Wevtapi.INSTANCE.EvtSeekRelativeToFirst);
				if (!ret)
					throw new Exception("EvtSeek Error errorCode=" + getLastErrorWrapper());
			}

			HANDLE[] handles = new HANDLE[100];
			IntByReference intRef = new IntByReference(0);
			while (true) {
				if (!Wevtapi.INSTANCE.EvtNext(hResults, 100, handles, 0, 0, intRef)) {
					if (getLastErrorWrapper() == 259)
						break;
					else
						throw new Exception("EvtNext Error errorCode=" + getLastErrorWrapper());
				} else {
					//ブックマークへシークした場合、前回取得した分は読み飛ばすため1始まり
					for (int i = hBookmark != null ? 1 : 0; i < intRef.getValue(); i++) {
						eventLog.append(renderEvent(handles[i], Wevtapi.INSTANCE.EvtRenderEventXml));
						Wevtapi.INSTANCE.EvtClose(handles[i]);
						handles[i] = null;
					}
				}
			}

			//ブックマーク保存処理
			if (!Wevtapi.INSTANCE.EvtSeek(hResults, new WinDef.LONGLONG(0), null, 0,
					Wevtapi.INSTANCE.EvtSeekRelativeToLast))
				throw new Exception("EvtSeek Error errorCode=" + getLastErrorWrapper());

			HANDLE[] saveHandles = new HANDLE[1];
			if (!Wevtapi.INSTANCE.EvtNext(hResults, 1, saveHandles, 0, 0, intRef))
				throw new Exception("EvtNext Error errorCode=" + getLastErrorWrapper());

			if (hBookmark == null) {
				hBookmark = Wevtapi.INSTANCE.EvtCreateBookmark(null);
				if (hBookmark == null)
					throw new Exception("EvtCreateBookmark Error errorCode=" + getLastErrorWrapper());
			}

			if (!Wevtapi.INSTANCE.EvtUpdateBookmark(hBookmark, saveHandles[0]))
				throw new Exception("EvtUpdateBookmark Error errorCode=" + getLastErrorWrapper());

			String saveBookmarkString = renderEvent(hBookmark, Wevtapi.INSTANCE.EvtRenderBookmark);
			saveBookmarkFile(fileName, saveBookmarkString);

		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (hResults != null)
				Wevtapi.INSTANCE.EvtClose(hResults);
			if (hBookmark != null)
				Wevtapi.INSTANCE.EvtClose(hBookmark);
		}
		return eventLog.toString();
	}

	private String renderEvent(HANDLE h, int EvtRenderFlg) throws Exception {
		int bufferSize = 0;
		Pointer buffer = null;
		IntByReference bufferUsed = new IntByReference(0);
		IntByReference propertyCount = new IntByReference(0);
		if (!Wevtapi.INSTANCE.EvtRender(null, h, EvtRenderFlg, bufferSize, buffer, bufferUsed, propertyCount)) {
			if (getLastErrorWrapper() == 122) {
				bufferSize = bufferUsed.getValue();
				buffer = new Memory(bufferSize);
				if (buffer != null)
					Wevtapi.INSTANCE.EvtRender(null, h, EvtRenderFlg, bufferSize, buffer, bufferUsed, propertyCount);
				else
					throw new Exception("malloc Error errorCode=" + getLastErrorWrapper());
			}
			if (0 != getLastErrorWrapper())
				throw new Exception("EvtRender Error errorCode=" + getLastErrorWrapper());
		}

		return buffer.getWideString(0);
	}

	private String getBookmartString(String fileName) {
		StringBuffer sb = new StringBuffer();
		BufferedReader br = null;
		try {
			br = new BufferedReader(new FileReader(fileName));
			String line;
			while ((line = br.readLine()) != null) {
				sb.append(line);
			}
		} catch (IOException e) {
			System.err.println("getBookmartString Error msg=" + e.getMessage());
		} finally {
			try {
				if (br != null)
					br.close();
			} catch (IOException e) {
				System.err.println("getBookmartString Error msg=" + e.getMessage());
			}
		}
		return sb.toString();
	}

	private void saveBookmarkFile(String fileName, String bookmarkString) {
		BufferedWriter bw = null;
		try {
			bw = new BufferedWriter(new FileWriter("bookmark.xml"));
			bw.write(bookmarkString);
		} catch (IOException e) {
			System.err.println("saveBookmark Error msg=" + e.getMessage());
		} finally {
			try {
				if (bw != null)
					bw.close();
			} catch (IOException e) {
				System.err.println("saveBookmark Error msg=" + e.getMessage());
			}
		}
	}

	private int getLastErrorWrapper() {
		return Kernel32.INSTANCE.GetLastError();
	}
}


うまくいくと以下のような bookmark.xml が作成されるはずです。

<BookmarkList Direction='backward'>
  <Bookmark Channel='Application' RecordId='4885'/>
  <Bookmark Channel='System' RecordId='5784' IsCurrent='true'/>
</BookmarkList>


各関数のリファレンスは以下にあります。

Windows Event Log Functions (Windows)


以上です。

[環境情報]
Windows 10
Java SE 8 Update 92
JNA 4.2.2