lib/metadata/util/win32/Win32EventLog.rb
# encoding: US-ASCII
# TODO:
# Provide collection of custom log names?
# Specifically use the Platform mod used in MiqDisk.
require 'sys-uname'
# For message table resources.
require 'metadata/util/win32/peheader'
# For registry export on MiqFS.
require 'metadata/util/win32/remote-registry'
# Dev needs this.
require 'metadata/util/win32/system_path_win'
require 'digest/md5'
# Common utilities.
require 'binary_struct'
require 'miq_unicode'
require 'manageiq/gems/pending'
require 'util/miq-xml'
require 'util/miq-exception'
require 'metadata/util/event_log_filter'
class Win32EventLog
using ManageIQ::UnicodeString
# Standard file log names
SYSTEM_LOGS = %w(Application System Security)
BUFFER_READ_SIZE = 10485760 # 10 MB buffer
# Data definitions. (http://msdn.microsoft.com/en-gb/library/bb309024.aspx)
ELF_LOGFILE_HEADER = BinaryStruct.new([
'L', :header_size, # The size of the header structure. The size is always 0x30.
'a4', :signature, # The signature is always 0x654c664c, which is ASCII for eLfL.
'L', :majorVersion, # The major version number of the event log. The major version number is always set to 1.
'L', :minorVersion, # The minor version number of the event log. The minor version number is always set to 1.
'L', :start_offset, # The offset to the oldest record in the event log.
'L', :end_offset, # The offset to the ELF_EOF_RECORD in the event log.
'L', :current_record_number, # The number of the next record that will be added to the event log.
'L', :oldest_record_number, # The number of the oldest record in the event log. For an empty file, the oldest record number is set to 0.
'L', :max_size, # The maximum size, in bytes, of the event log. The maximum size is defined when the event log is created.
# The event-logging service does not typically update this value, it relies on the registry configuration.
# The reader of the event log can use normal file APIs to determine the size of the file.
'L', :flags, # See ELF_ below.
'L', :retention, # The retention value of the file when it is created.
# The event-logging service does not typically update this value, it relies on the registry configuration.
# For more information about registry configuration values, see Eventlog Key.
'L', :end_header_size # The ending size of the header structure. The size is always 0x30.
])
# Event Log header flags.
ELF_DIRTY = 0x00000001 # If set, don't rely on other values in the header.
ELF_WRAPPED = 0x00000002 # Indicates the log is wrapped.
ELF_LOGFULL = 0x00000004 # Set if log full (extended implications in EventLogFormat.txt).
ELF_LOGFILE_ARCHIVE_SET = 0x00000008 # Indicates that the archive attribute has been set for the file.
# Normal file APIs can also be used to determine the value of this flag.
# Data definitions. (http://msdn.microsoft.com/en-gb/library/bb309022(VS.85).aspx )
EVENTLOGEOF = BinaryStruct.new([
'L', :record_size_beginning, # The beginning size of the ELF_EOF_RECORD. The beginning size is always 0x28.
'a16', :magic, # Always \001\001\001\001\002\002\002\002\003\003\003\003\004\004\004\004
'L', :begin_record, # The offset to the oldest record. If the event log is empty, this is set to the start of this structure.
'L', :end_record, # The offset to the start of this structure.
'L', :current_record_number, # The record number of the next event that will be written to the event log.
'L', :oldest_record_number, # The record number of the oldest record in the event log. The record number will be 0 if the event log is empty.
'L', :record_size_end # The ending size of the ELF_EOF_RECORD. The ending size is always 0x28.
])
# Data definitions. (http://msdn.microsoft.com/en-gb/library/aa363646(VS.85).aspx)
EVENTRECORD = BinaryStruct.new([
'L', :record_length, # The size of this event record, in bytes. Note that this value is stored at both ends
# of the entry to ease moving forward or backward through the log. The length includes
# any pad bytes inserted at the end of the record for DWORD alignment.
'a4', :magic, # A DWORD value that is always set to ELF_LOG_SIGNATURE (the value is 0x654c664c), which is ASCII for eLfL.
'L', :record_num, # The number of the record.
'L', :generated, # The time at which this entry was submitted. This time is measured in the number of seconds elapsed since 00:00:00 January 1, 1970, Universal Coordinated Time.
'L', :written, # The time at which this entry was received by the service to be written to the log. This time is measured in the number of seconds elapsed since 00:00:00 January 1, 1970, Universal Coordinated Time.
'L', :event_id, # The event identifier. The value is specific to the event source for the event, and is used with source name to locate a description string in the message file for the event source
'S', :level, # See EVENTLOG_ below.
'S', :num_strings, # The number of strings present in the log (at the position indicated by StringOffset). These strings are merged into the message before it is displayed to the user.
'S', :category, # The category for this event. The meaning of this value depends on the event source.
'S', :reserved_flags, # Reserved.
'L', :closing_rec_num, # Reserved.
'L', :string_offset, # Offset from beginning of record to UTF-16 strings.
'L', :user_sid_length, # The size of the UserSid member, in bytes. This value can be zero if no security identifier was provided.
'L', :user_sid_offset, # Offset from beginning of record.
'L', :data_length, # Length of parameter data (0 if none).
'L', :data_offset, # The offset of the event-specific information within this event log record, in bytes.
# This information could be something specific (a disk driver might log the number of retries, for example),
# followed by binary information specific to the event being logged and to the source that generated the entry.
])
EVENTRECORDLENGTH = BinaryStruct.new([
'L', :record_length, # The size of this event record, in bytes. Note that this value is stored at both ends
])
# Event types.
EVENT_TYPES = {
0x0000 => :info, # EVENTLOG_SUCCESS
0x0001 => :error, # EVENTLOG_ERROR_TYPE
0x0002 => :warn, # EVENTLOG_WARNING_TYPE
0x0004 => :info, # EVENTLOG_INFORMATION_TYPE
0x0008 => :info, # VENTLOG_AUDIT_SUCCESS
0x0010 => :error, # EVENTLOG_AUDIT_FAILURE
}
# Magic numbers used by log record types.
MAGIC_HDR = "LfLe"
MAGIC_CSR = "\x11\x11\x11\x11\x22\x22\x22\x22\x33\x33\x33\x33\x44\x44\x44\x44"
# Registry constants.
HKLM = 0x80000002
KEY_READ = 0x00020019
REG_MULTI_SZ = 7
# Key name buffer size.
SIZE_BUF = 256
# Misc Windows constants.
INVALID_HANDLE_VALUE = -1
ERROR_SUCCESS = 0
ERROR_NO_MORE_ITEMS = 259
# Lookup object for translating the common %? sequences in the messages
FORMAT_TR = Hash.new { |_h, k| k }.merge(
'% ' => " ",
'%b' => " ",
'%.' => ".",
'%!' => "!",
'%n' => "\r\n",
'%r' => "\r",
'%t' => "\t",
'%0' => "",
'!s!' => ""
)
# Keys that will be in the final node record
NODE_REC_KEYS = [:generated, :event_id, :level, :category, :computer_name, :source, :message]
attr_reader :xmlDoc, :customFileName
attr_reader :readTimes
def initialize(vmMiqFs = nil)
# vmMiqFs is an MiqFS instance for the file system
# of the guest vm if guest logs or nil if host logs.
# If an MiqFS instance was not passed, then the OS has to be (or emulate) Win32.
# If an MiqFS instance *was* passed, then if the guest OS is not Windows then getSystemRoot will throw.
raise "#{self.class}::initialize: Filesystem is not MiqFS: cannot continue" if !vmMiqFs.class.to_s.include?('Miq')
# Get a file system instance if we don't already have one.
@fs = vmMiqFs
@fs = File if @fs.nil?
# Init times.
@readTimes = {}
@msgtbl_cache = {}
# Get root, system message tables & init messagetable cache.
if @fs == File
@systemRoot = 'c:/windows/system32'
@kernel32_fn = 'c:/windows/system32/kernel32.dll'
else
@systemRoot = Win32::SystemPath.systemRoot(@fs)
@kernel32_fn = "#{Win32::SystemPath.system32Path(@fs, @systemRoot)}/kernel32.dll"
end
end
def readAllLogs(options)
options = options.collect { |l| {:name => l, :filter => nil} } if options[0].kind_of?(String)
options.each do |o|
start = Time.now
readLog(o[:name], o[:filter])
@readTimes[o[:name]] = Time.now - start
end
@xmlDoc
end
def readLog(log, filter = nil)
filter ||= {}
EventLogFilter.prepare_filter!(filter)
# Get message source files. (This also caches the event log registry entries.)
sources = getEventSourceMessageFiles(log)
@f = @buf = nil
# Get event log file and validate it is a format we support
event_file = mkLogPath(log)
unless File.extname(event_file).downcase == ".evt"
raise MiqException::NtEventLogFormat, "#{self.class}: Unsupported Win32 Eventlog format [#{File.extname(event_file)}] for event log [#{log}]. File:[#{event_file}]"
end
# Start an XML document
recordsNode = mkXmlDoc(log, event_file)
getFileObj(event_file) do |f, filename|
st = Time.now
$log.info "#{self.class}: Opening file for [#{log}]" if $log
@f = f
@offset = BUFFER_READ_SIZE * -1
hdr = ELF_LOGFILE_HEADER.decode(read_buffer(0, ELF_LOGFILE_HEADER.size))
hdr[:wrapped] = !(hdr[:flags] & ELF_WRAPPED).zero?
@file_size = @fs == File ? File.size(filename) : @fs.fileSize(filename)
$log.info "#{self.class}: Opened file for [#{log}] in [#{Time.now - st}] seconds. Data Size:[#{@file_size}] Wrapped:[#{hdr[:wrapped]}]" if $log
parse_time = Time.now
recs_found = 0
recs_processed = 0
@dup_check = {}
each_record(hdr, log) do |rec|
recs_processed += 1
# Get log record components & filter on them
rec[:generated] = Time.at(rec[:generated]).utc.iso8601
break if EventLogFilter.filter_by_generated?(rec[:generated], filter)
rec[:level] = EVENT_TYPES[rec[:level]]
next if EventLogFilter.filter_by_level?(rec[:level], filter)
getSourceName(rec)
next if EventLogFilter.filter_by_source?(rec[:source], filter)
getStrings(rec)
getMessage(log, rec, sources)
next if EventLogFilter.filter_by_message?(rec[:message], filter)
# Get the rest of the record components
getComputerName(rec)
# There are not presently being used, so there is no need to collect them
# rec[:written] = Time.at(rec[:written]).utc.iso8601
# getSID(buf, pos, rec)
# getData(buf, pos, rec)
# Add the node to the XML
recs_found += 1 if addNodeRec(recordsNode, rec)
# Quit if we've found enough records
break if EventLogFilter.filter_by_rec_count?(recs_found, filter)
end
# Clean up
@dup_check = nil
# Store based on log.
recordsNode.add_attribute(:num_records, recs_found)
$log.info "#{self.class}: Parsed [#{recs_processed}] [#{log}] records in [#{Time.now - parse_time}] seconds. Collected [#{recs_found}] records. Total time [#{Time.now - st}] seconds." if $log
end
@f = nil
end
private
def mkLogPath(log)
unless @reg_source_xml.nil?
appKey = XmlFind.findElement("CurrentControlSet/Services/Eventlog/#{log}/File", @reg_source_xml)
logPath = appKey.text
else
logPath = Win32::SystemPath.registryPath(@fs, @systemRoot) + "/"
logPath = case log
when 'Application' then logPath + "appevent.evt"
when 'Security' then logPath + "secevent.evt"
when 'System' then logPath + "sysevent.evt"
else
raise "#{self.class}::mkLogPath: '#{log}' is not a path to an event log file." unless log.class.to_s == "String"
raise "#{self.class}::mkLogPath: File not found: '#{log}'" unless isFile?(log)
@customFileName = log
end
end
logPath
end
# These functions hide the differences for equivalent calls in the file instance.
def isFile?(fn)
meth = @fs.respond_to?(:fileExists?) ? :fileExists? : :exists?
@fs.send(meth, fn)
end
def getFileObj(fn)
# Determine what file open method to use
meth = @fs.respond_to?(:fileOpen) ? :fileOpen : :open
fn = fn.tr('\\', '/')
f = @fs.send(meth, fn, "rb")
# If we are passed a block, run it and close the file handle
return f unless block_given?
begin
yield(f, fn)
ensure
f.close rescue nil
end
end
def mkXmlDoc(log, event_file)
@xmlDoc ||= XmlHash.createDoc("<event_log/>")
@xmlDoc.root.add_element(:log, {:name => log, :path => event_file})
end
# This function finds the first event record in the buffer.
def getFirstRecordOffset(hdr)
# Find the cursor record and get first rec from there.
# hdr[:end_offset] should point to the offset of the EOF record. If the dirty flag
# is set this is likely to have moved, but should be in front of it, so start the search there.
pos = findCursorRecord(hdr[:end_offset])
pos = findCursorRecord(0) if pos.nil?
raise "Win32 Eventlog cursor record not found." if pos.nil?
EVENTLOGEOF.decode(read_buffer(pos - 4, EVENTLOGEOF.size))
end
# The last 4 bytes of a record hold the record length for that record.
# Grab it and set the new position to the top of that record.
def getNextRecordOffset(curr_pos, hdr)
# Called the first time
if curr_pos.nil?
@csr = getFirstRecordOffset(hdr)
curr_pos = @csr[:end_record]
end
# Check for wrapped messages
if curr_pos == ELF_LOGFILE_HEADER.size
curr_pos = findEndBuffer
end
offset = curr_pos - 4
prev_rec_length = read_buffer(offset, 4, -1)
rec_len = EVENTRECORDLENGTH.decode(prev_rec_length)[:record_length]
new_pos = (curr_pos - rec_len)
# Check for wrapped messages
if new_pos < ELF_LOGFILE_HEADER.size
copy_from_end = ELF_LOGFILE_HEADER.size - new_pos
new_pos = @file_size - copy_from_end
end
new_pos
end
# If the record header cannot fit at the end of the file when the log file wraps
# the end of the file is padded with 0x27 markers after the record length. So
# walk backwards until a non-0x27 marker is found.
def findEndBuffer
offset = @file_size - 4
while EVENTRECORDLENGTH.decode(read_buffer(offset, 4, -1))[:record_length] == 0x27
offset -= 4
end
offset + 4
end
def findCursorRecord(search_offset)
pos = nil
while pos.nil?
pos = read_buffer(search_offset, BUFFER_READ_SIZE).index(MAGIC_CSR)
search_offset += BUFFER_READ_SIZE if pos.nil?
break if search_offset >= @file_size
end
pos += search_offset unless pos.nil?
pos
end
def each_record(hdr, log)
# Check for an empty event log file
return if hdr[:oldest_record_number].zero?
last_pos = pos = getNextRecordOffset(nil, hdr)
loop do
# Get this record.
rec = EVENTRECORD.decode(read_buffer(pos, EVENTRECORD.size, -1))
# If record wraps around to the start of the buffer
if pos + rec[:record_length] > @file_size
# If we get to the end of the file make sure the event log is marked as wrapped
# before trying to process data from the begin of the file.
break if (hdr[:flags] & ELF_WRAPPED).zero?
# If the record header fits then in the remaining bytes the header
# and data is written upto the end of the file. The remaining data
# is writting at the top of the file after the file header.
remaining_bytes = @file_size - pos
wrapped_byte_count = rec[:record_length] - remaining_bytes
wrapped_bytes = read_buffer(ELF_LOGFILE_HEADER.size, wrapped_byte_count)
read_buffer(pos, rec[:record_length], -1)
@buf << wrapped_bytes
end
# Verify record synchronization.
if rec[:magic] != MAGIC_HDR
csr = EVENTLOGEOF.decode(read_buffer(pos, EVENTLOGEOF.size))
break if csr[:magic] == MAGIC_CSR
# Check if the Cursor record appears anywhere in the buffer data for this mis-aligned record.
break unless read_buffer(pos, last_pos - pos).index(MAGIC_CSR).nil?
# When the log is wrapped if we find a mis-aligned record it is on the cursor record missing, likely due
# to the log actively being updated when we read it.
break if hdr[:wrapped] == true
if $log
$log.error "MIQ(#{self.class}-readLog) Log synchronization for {#{log}} is broken - rec:[#{rec.inspect}] csr:[#{csr.inspect}] header:[#{hdr.inspect}] pos:[#{pos}] buf length:[#{@buf.length}]"
$log.error "MIQ(#{self.class}-readLog) 4K buf < pos:"
read_buffer((pos < 4096 ? 0 : pos - 4096), 4096).hex_dump(:obj => $log, :meth => :error, :newline => false)
$log.error "MIQ(#{self.class}-readLog) 4K buf >= pos:"
read_buffer(pos, 4096).hex_dump(:obj => $log, :meth => :error, :newline => false)
end
raise "MIQ(#{self.class}-readLog) Log synchronization is broken."
end
rec[:data] = read_buffer(pos, rec[:record_length], -1)
yield(rec)
break if rec[:record_num] == @csr[:oldest_record_number]
last_pos = pos
pos = getNextRecordOffset(pos, hdr)
end
end
def read_buffer(offset, length, direction = 1)
# puts "[#{@offset}] -- [#{@offset+BUFFER_READ_SIZE}], O:[#{offset}] L:[#{length}]"
if (offset < @offset) || (offset + length > @offset + BUFFER_READ_SIZE)
read_offset = offset
if direction < 0
# When adjusting the read offset backwards account for the length of the data
# being read plus an extra 4K which should cover the data portion of a record
# since we have to read the record header ahead of the data.
read_offset = offset - BUFFER_READ_SIZE + length + 4096
read_offset = 0 if read_offset < 0
end
# puts "***Loading from offset [#{read_offset}]"
@f.seek(read_offset)
@buf = @f.read(BUFFER_READ_SIZE)
@offset = read_offset
end
@buf[offset - @offset, length]
end
def getSourceName(rec)
str = rec[:data][EVENTRECORD.size..-1]
if str
str = weirdFixString(str)
str.UnicodeToUtf8!
rec[:source] = str
end
end
def getComputerName(rec)
str = rec[:data][(EVENTRECORD.size + rec[:source].length * 2 + 2)..-1]
if str
str = weirdFixString(str)
str.UnicodeToUtf8!
rec[:computer_name] = str
end
end
def getSID(buf, pos, rec)
if rec[:user_sid_length] > 0
rec[:user_sid] = decodeSid(buf[pos + rec[:user_sid_offset], rec[:user_sid_length]])
end
end
def decodeSid(data)
sid = "S-"
# BYTE Revision
sid << data[0].to_s << "-"
# BYTE SubAuthorityCount
subCount = data[1]
# WORD Authority[3]
0.upto(2) {|i|
auth = data[2 + i * 2, 2].unpack('n')[0]
sid << auth.to_s << "-" if auth != 0
}
# DWORD SubAuthority[*]
0.upto(subCount - 1) {|i|
subAuth = data[8 + i * 4, 4].unpack('L')[0]
sid << subAuth.to_s << "-"
}
sid.chop!
sid
end
def getStrings(rec)
rec[:strings] = []
return if rec[:num_strings] <= 0
offset = rec[:string_offset]
(rec[:num_strings] - 1).times do
str = rec[:data][offset..-1]
if str
str = weirdFixString(str)
# Compensate for nil strings.
if str == "\000"
rec[:strings] << ""
offset += 2
else
offset += str.length + 2
rec[:strings] << str.UnicodeToUtf8!
end
end
end
end
def getData(buf, pos, rec)
if rec[:data_length] > 0
rec[:data] = buf[pos + rec[:data_offset], rec[:data_length]]
end
end
# The standard conversion doesn't terminate a string at \000\000 so use this.
def weirdFixString(str)
idx = str.index("\000\000")
idx.nil? ? str : str[0..idx]
end
def addNodeRec(node, rec)
node_rec = {}
# Put the needed fields in the node_rec, and collect them for md5 hashing to
# verify that this record is unique
md5 = NODE_REC_KEYS.collect { |k| node_rec[k] = rec[k] }.join(' ')
md5 = Digest::MD5.hexdigest(md5)
return false if @dup_check.key?(md5)
@dup_check[md5] = nil
node_rec[:uid] = md5
node.add_element(:record, node_rec)
true
end
# Given a record, turn it's event id into a log message.
def getMessage(log, rec, sources)
src = rec[:source].downcase
unless sources[:message].key?(src)
# TODO: Use the Windows message from els.dll
rec[:message] = "#{self.class}::getMessage: The source '#{rec[:source]}' is not listed under HKLM\\System\\CurrentControlSet\\Services\\EventLog\\#{log}"
return
end
msgfiles = sources[:message][src].split(";")
paramfiles = sources[:param][src].split(";") if sources[:param].key?(src)
msg = errMsg = nil
id = rec[:event_id]
msgfiles.each do |fn|
msgtbls = getMessageTables(fn)
unless msgtbls.kind_of?(Hash)
errMsg ||= ""
errMsg << "#{msgtbls}\n"
next
end
str = getString(id, msgtbls)
next if str.nil?
fmtSub(str)
msg = str.dup
strSub(msg, rec, msgtbls, paramfiles)
break
end
msg = errMsg.nil? ? "#{self.class}::getMessage: Couldn't find message id in any listed source" : errMsg if msg.nil?
rec[:message] = msg.chomp!
end
def getParamMessage(id, paramfiles)
return "" if paramfiles.nil?
paramfiles.each do |fn|
msgtbls = getMessageTables(fn)
return "" unless msgtbls.kind_of?(Hash)
str = getString(id, msgtbls)
return str.dup unless str.nil?
end
""
end
# Search for id in messagetables.
def getString(id, msgtbls)
return msgtbls unless msgtbls.kind_of?(Hash)
msgtbls[id]
end
def getMessageTables(fn)
# Check cache for this file's messagetables.
return @msgtbl_cache[fn] if @msgtbl_cache.key?(fn)
# Get file & read messagetable resources.
peh = nil
begin
getFileObj(fn) do |f, _fn2|
begin
peh = PEheader.new(f)
# Stick this table in the cache.
@msgtbl_cache[fn] = peh.messagetables
rescue
@msgtbl_cache[fn] = "#{self.class}::getMessageTables: Invalid message table in file: #{fn}"
end
end
rescue
@msgtbl_cache[fn] = "#{self.class}::getMessageTables: File not found: #{fn}"
end
@msgtbl_cache[fn]
end
def fmtSub(msg)
msg.gsub!(/%[b\.!nrt0]|!s!/) { |s| FORMAT_TR[s] }
end
# String substitution for Win32 FormatMessage (%1, %2 & so on).
def strSub(msg, rec, msgtbls, paramfiles)
# Replace occurances of %%n[n...] with the value from the parameter message file
# Replace occurances of %n[n...] with (in this order):
# 1. A string from the record's Strings array.
# 2. A string from a messagetable whose id is n[n...]
# 3. A string from the system messagetable whose id is n[n...]
msg.gsub!(/(%%?)([1-9][0-9]*)/) do
percents, id = $1, $2.to_i
if percents.length == 1
param = rec[:strings][id - 1] if id <= rec[:strings].size
param = getString(id, msgtbls) if param.nil?
param = getString(id, getMessageTables(@kernel32_fn)) if param.nil?
else
param = getParamMessage(id, paramfiles)
end
param = "NO PARAM: #{id}" if param.nil?
param
end
end
# Given a log name, get event sources & message files in a hash.
def getEventSourceMessageFiles(log)
return getSourcesFromMiqFS(log) if Object.const_defined?(:MiqFS) && @fs.kind_of?(MiqFS)
getSourcesFromWin32(log)
end
def getSourcesFromMiqFS(log)
# Initialize the message source hash object
sources = {:message => {}, :param => {}, :category => {}}
# Load registry section where we find the NT event log message source files.
if @reg_source_xml.nil?
reg = RemoteRegistry.new(@fs, true)
@reg_source_xml = reg.loadHive("system", [{:key => 'CurrentControlSet/Services/Eventlog', :value => ['CategoryMessageFile', 'EventMessageFile', 'ParameterMessageFile', 'File']}])
end
appKey = XmlFind.findElement("CurrentControlSet/Services/Eventlog/#{log}", @reg_source_xml)
appKey.each_element(:key) do |src|
keyName = src.attributes[:keyname].downcase
[['EventMessageFile', :message],
['ParameterMessageFile', :param],
['CategoryMessageFile', :category]].each do |msg_file, type|
src.each_element_with_attribute(:name, msg_file) do |e|
fn = e.text.to_s
fn.gsub!(/%SystemRoot%/i, @systemRoot)
sources[type][keyName] = fn
end
end
end
sources
end
def getSourcesFromWin32(log)
require 'win32/registry'
sources = {:message => {}, :param => {}, :category => {}}
types = {'EventMessageFile' => sources[:message], 'ParameterMessageFile' => sources[:param], 'CategoryMessageFile' => sources[:category]}
src = "system\\currentcontrolset\\services\\eventlog\\#{log}"
Win32::Registry::HKEY_LOCAL_MACHINE.open(src) do |reg|
reg.each_key do |subKey, _wtime|
subpath = "#{src}\\#{subKey}"
subKey.downcase!
Win32::Registry::HKEY_LOCAL_MACHINE.open(subpath) do |subreg|
subreg.each_value do |name, _type, data|
case name
when 'EventMessageFile', 'ParameterMessageFile', 'CategoryMessageFile' then
fn = data.to_s
fn.gsub!(/%SystemRoot%/i, @systemRoot)
types[name][subKey] = fn
end
end
end
end
end
sources
end
def getEvtMsgFile(hKey)
buf = ""
len = [0].pack('L')
type = [0].pack('L')
res = @@RegQueryValueEx.call(hKey, "EventMessageFile", 0, type, buf, len)
# Beware: this MAY come up at some point.
raise "#{self.class}::getEvtMsgFile: Got REG_MULTI_SZ" if type.unpack('L')[0] == REG_MULTI_SZ
len = len.unpack('L')[0]
buf = " " * len
len = [len].pack('L')
res = @@RegQueryValueEx.call(hKey, "EventMessageFile", 0, type, buf, len)
if res != ERROR_SUCCESS
buf = ""
len = [0].pack('L')
end
return buf, len
end
def fixFileList(buf, len)
buf = buf[0...(len.unpack('L')[0] - 1)]
buf = buf.split("\\").join("/")
buf.gsub!(/%SystemRoot%/i, @systemRoot)
buf
end
end
# If invoked from command line.
if __FILE__ == $0
puts "Reading logs..."
start = Time.now
log = Win32EventLog.new
filter = {:level => :warn}
log.readLog("Application", filter)
log.readLog("Security", filter)
log.readLog("System", filter)
puts "Read logs completed in #{Time.now - start} seconds"
end