win32-eventlog 0.5.3 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,24 +1,24 @@
1
- ############################################################################
2
- # example_notify.rb (win32-eventlog)
3
- #
4
- # A sample script for demonstrating the tail method. Start this in its own
5
- # terminal. Then, in another terminal, write an entry to the event log
6
- # (or force an entry via some other means) and watch the output.
7
- #
8
- # You can run this code via the 'example_notify' rake task.
9
- ############################################################################
10
- Thread.new { loop { sleep 0.01 } } # Allow Ctrl-C
11
-
12
- require 'win32/eventlog'
13
- include Win32
14
-
15
- log = EventLog.open
16
-
17
- # replace 'tail' with 'notify_change' to see the difference
18
- log.tail{ |struct|
19
- puts "Got something"
20
- p struct
21
- puts
22
- }
23
-
1
+ ############################################################################
2
+ # example_notify.rb (win32-eventlog)
3
+ #
4
+ # A sample script for demonstrating the tail method. Start this in its own
5
+ # terminal. Then, in another terminal, write an entry to the event log
6
+ # (or force an entry via some other means) and watch the output.
7
+ #
8
+ # You can run this code via the 'example_notify' rake task.
9
+ ############################################################################
10
+ Thread.new { loop { sleep 0.01 } } # Allow Ctrl-C
11
+
12
+ require 'win32/eventlog'
13
+ include Win32
14
+
15
+ log = EventLog.open
16
+
17
+ # replace 'tail' with 'notify_change' to see the difference
18
+ log.tail{ |struct|
19
+ puts "Got something"
20
+ p struct
21
+ puts
22
+ }
23
+
24
24
  log.close
@@ -1,84 +1,84 @@
1
- ############################################################################
2
- # example_read.rb (win32-eventlog)
3
- #
4
- # Test script for general futzing. This will attempt to read and backup
5
- # your Event Log. You can run this example via the 'rake example_read'
6
- # task.
7
- #
8
- # Modify as you see fit.
9
- ############################################################################
10
- require 'win32/eventlog'
11
- include Win32
12
-
13
- puts "VERSION: " + EventLog::VERSION
14
- sleep 1
15
-
16
- # A few different ways to read an event log
17
-
18
- el = EventLog.new("Application")
19
- el.read{ |log|
20
- p log
21
- }
22
- el.close
23
-
24
- EventLog.read("Application"){ |log|
25
- p log
26
- puts
27
- }
28
-
29
- EventLog.open("Application") do |log|
30
- log.read{ |struct|
31
- p struct
32
- puts
33
- }
34
- end
35
-
36
- backup_file = "C:\\event_backup1"
37
- File.delete(backup_file) if File.exists?(backup_file)
38
-
39
- e1 = EventLog.open("System")
40
- puts "System log opened"
41
-
42
- e2 = EventLog.open("Application")
43
- puts "Application log opened"
44
-
45
- e3 = EventLog.open("Security")
46
- puts "Security log opened"
47
-
48
- puts "=" * 40
49
-
50
- puts "Total system records: " + e1.total_records.to_s
51
- puts "Total application records: " + e2.total_records.to_s
52
- puts "Total security records: " + e3.total_records.to_s
53
-
54
- puts "=" * 40
55
-
56
- puts "Oldest system record number: " + e1.oldest_record_number.to_s
57
- puts "Oldest application record number: " + e2.oldest_record_number.to_s
58
- puts "Oldest security record number: " + e3.oldest_record_number.to_s
59
-
60
- puts "=" * 40
61
-
62
- e2.backup(backup_file)
63
- puts "Application log backed up to #{backup_file}"
64
-
65
- puts "=" * 40
66
-
67
- e1.close
68
- puts "System log closed"
69
-
70
- e2.close
71
- puts "Application log closed"
72
-
73
- e3.close
74
- puts "Security log closed"
75
-
76
- e4 = EventLog.open_backup(backup_file)
77
- e4.read{ |elr|
78
- p elr
79
- puts
80
- }
81
- puts "Finished reading backup file"
82
- e4.close
83
-
1
+ ############################################################################
2
+ # example_read.rb (win32-eventlog)
3
+ #
4
+ # Test script for general futzing. This will attempt to read and backup
5
+ # your Event Log. You can run this example via the 'rake example_read'
6
+ # task.
7
+ #
8
+ # Modify as you see fit.
9
+ ############################################################################
10
+ require 'win32/eventlog'
11
+ include Win32
12
+
13
+ puts "VERSION: " + EventLog::VERSION
14
+ sleep 1
15
+
16
+ # A few different ways to read an event log
17
+
18
+ el = EventLog.new("Application")
19
+ el.read{ |log|
20
+ p log
21
+ }
22
+ el.close
23
+
24
+ EventLog.read("Application"){ |log|
25
+ p log
26
+ puts
27
+ }
28
+
29
+ EventLog.open("Application") do |log|
30
+ log.read{ |struct|
31
+ p struct
32
+ puts
33
+ }
34
+ end
35
+
36
+ backup_file = "C:\\event_backup1"
37
+ File.delete(backup_file) if File.exists?(backup_file)
38
+
39
+ e1 = EventLog.open("System")
40
+ puts "System log opened"
41
+
42
+ e2 = EventLog.open("Application")
43
+ puts "Application log opened"
44
+
45
+ e3 = EventLog.open("Security")
46
+ puts "Security log opened"
47
+
48
+ puts "=" * 40
49
+
50
+ puts "Total system records: " + e1.total_records.to_s
51
+ puts "Total application records: " + e2.total_records.to_s
52
+ puts "Total security records: " + e3.total_records.to_s
53
+
54
+ puts "=" * 40
55
+
56
+ puts "Oldest system record number: " + e1.oldest_record_number.to_s
57
+ puts "Oldest application record number: " + e2.oldest_record_number.to_s
58
+ puts "Oldest security record number: " + e3.oldest_record_number.to_s
59
+
60
+ puts "=" * 40
61
+
62
+ e2.backup(backup_file)
63
+ puts "Application log backed up to #{backup_file}"
64
+
65
+ puts "=" * 40
66
+
67
+ e1.close
68
+ puts "System log closed"
69
+
70
+ e2.close
71
+ puts "Application log closed"
72
+
73
+ e3.close
74
+ puts "Security log closed"
75
+
76
+ e4 = EventLog.open_backup(backup_file)
77
+ e4.read{ |elr|
78
+ p elr
79
+ puts
80
+ }
81
+ puts "Finished reading backup file"
82
+ e4.close
83
+
84
84
  File.delete(backup_file)
@@ -1,65 +1,65 @@
1
- ##############################################################################
2
- # example_write.rb
3
- #
4
- # Tests both the creation of an Event Log source and writing to the Event
5
- # Log. You can run this via the 'rake example_write' task.
6
- #
7
- # Modify as you see fit.
8
- ##############################################################################
9
-
10
- # Prompt user to continue, or not...
11
- msg = <<TEXT
12
-
13
- This script will create an event source 'foo' in your registry and
14
- write an event to the 'Application' source.
15
- Is that ok [y/N]?
16
- TEXT
17
- print msg
18
-
19
- ans = STDIN.gets.chomp
20
- unless ans == "y" || ans == "Y"
21
- puts "Ok, exiting..."
22
- exit!
23
- end
24
-
25
- require "win32/eventlog"
26
- require "win32/mc"
27
- include Win32
28
-
29
- puts "EventLog VERSION: " + EventLog::VERSION
30
- puts "MC VERSION: " + MC::VERSION
31
- sleep 1
32
-
33
- m = MC.new("foo.mc")
34
- m.create_all
35
- puts ".dll created"
36
- sleep 1
37
-
38
- dll_file = File.expand_path(m.dll_file)
39
-
40
- EventLog.add_event_source(
41
- 'source' => "Application",
42
- 'key_name' => "foo",
43
- 'category_count' => 2,
44
- 'event_message_file' => dll_file,
45
- 'category_message_file' => dll_file
46
- )
47
-
48
- puts "Event source added to registry"
49
- sleep 1
50
-
51
- e1 = EventLog.open("Application")
52
-
53
- e1.report_event(
54
- :source => "foo",
55
- :event_type => EventLog::WARN,
56
- :category => "0x00000002L".hex,
57
- :event_id => "0xC0000003L".hex,
58
- :data => "Danger Will Robinson!"
59
- )
60
-
61
- puts "Event written to event log"
62
-
63
- e1.close
64
-
1
+ ##############################################################################
2
+ # example_write.rb
3
+ #
4
+ # Tests both the creation of an Event Log source and writing to the Event
5
+ # Log. You can run this via the 'rake example_write' task.
6
+ #
7
+ # Modify as you see fit.
8
+ ##############################################################################
9
+
10
+ # Prompt user to continue, or not...
11
+ msg = <<TEXT
12
+
13
+ This script will create an event source 'foo' in your registry and
14
+ write an event to the 'Application' source.
15
+ Is that ok [y/N]?
16
+ TEXT
17
+ print msg
18
+
19
+ ans = STDIN.gets.chomp
20
+ unless ans == "y" || ans == "Y"
21
+ puts "Ok, exiting..."
22
+ exit!
23
+ end
24
+
25
+ require "win32/eventlog"
26
+ require "win32/mc"
27
+ include Win32
28
+
29
+ puts "EventLog VERSION: " + EventLog::VERSION
30
+ puts "MC VERSION: " + MC::VERSION
31
+ sleep 1
32
+
33
+ m = MC.new("foo.mc")
34
+ m.create_all
35
+ puts ".dll created"
36
+ sleep 1
37
+
38
+ dll_file = File.expand_path(m.dll_file)
39
+
40
+ EventLog.add_event_source(
41
+ 'source' => "Application",
42
+ 'key_name' => "foo",
43
+ 'category_count' => 2,
44
+ 'event_message_file' => dll_file,
45
+ 'category_message_file' => dll_file
46
+ )
47
+
48
+ puts "Event source added to registry"
49
+ sleep 1
50
+
51
+ e1 = EventLog.open("Application")
52
+
53
+ e1.report_event(
54
+ :source => "foo",
55
+ :event_type => EventLog::WARN,
56
+ :category => "0x00000002L".hex,
57
+ :event_id => "0xC0000003L".hex,
58
+ :data => "Danger Will Robinson!"
59
+ )
60
+
61
+ puts "Event written to event log"
62
+
63
+ e1.close
64
+
65
65
  puts "Finished. Exiting..."
@@ -1,1157 +1,1146 @@
1
- require 'windows/error'
2
- require 'windows/eventlog'
3
- require 'windows/security'
4
- require 'windows/registry'
5
- require 'windows/system_info'
6
- require 'windows/library'
7
- require 'windows/synchronize'
8
- require 'windows/handle'
9
-
10
- class String
11
- # Return the portion of the string up to the first NULL character. This
12
- # was added for both speed and convenience.
13
- def nstrip
14
- if RUBY_VERSION.to_f >= 1.9
15
- unless self.ascii_only?
16
- self.force_encoding('BINARY')
17
- end
18
- end
19
- self[ /^[^\0]*/ ]
20
- end
21
- end
22
-
23
- # The Win32 module serves as a namespace only.
24
- module Win32
25
-
26
- # The EventLog class encapsulates an Event Log source and provides methods
27
- # for interacting with that source.
28
- class EventLog
29
-
30
- # The EventLog::Error is raised in cases where interaction with the
31
- # event log should happen to fail for any reason.
32
- class Error < StandardError; end
33
-
34
- include Windows::Error
35
- include Windows::EventLog
36
- include Windows::Security
37
- include Windows::Registry
38
- include Windows::SystemInfo
39
- include Windows::Library
40
- include Windows::Synchronize
41
- include Windows::Handle
42
- extend Windows::Error
43
- extend Windows::Registry
44
-
45
- # The version of the win32-eventlog library
46
- VERSION = '0.5.3'
47
-
48
- # The log is read in chronological order, i.e. oldest to newest.
49
- FORWARDS_READ = EVENTLOG_FORWARDS_READ
50
-
51
- # The log is read in reverse chronological order, i.e. newest to oldest.
52
- BACKWARDS_READ = EVENTLOG_BACKWARDS_READ
53
-
54
- # Begin reading from a specific record.
55
- SEEK_READ = EVENTLOG_SEEK_READ
56
-
57
- # Read the records sequentially. If this is the first read operation, the
58
- # EVENTLOG_FORWARDS_READ or EVENTLOG_BACKWARDS_READ flags determines
59
- # which record is read first.
60
- SEQUENTIAL_READ = EVENTLOG_SEQUENTIAL_READ
61
-
62
- # Event types
63
-
64
- # Information event, an event that describes the successful operation
65
- # of an application, driver or service.
66
- SUCCESS = EVENTLOG_SUCCESS
67
-
68
- # Error event, an event that indicates a significant problem such as
69
- # loss of data or functionality.
70
- ERROR = EVENTLOG_ERROR_TYPE
71
-
72
- # Warning event, an event that is not necessarily significant but may
73
- # indicate a possible future problem.
74
- WARN = EVENTLOG_WARNING_TYPE
75
-
76
- # Information event, an event that describes the successful operation
77
- # of an application, driver or service.
78
- INFO = EVENTLOG_INFORMATION_TYPE
79
-
80
- # Success audit event, an event that records an audited security attempt
81
- # that is successful.
82
- AUDIT_SUCCESS = EVENTLOG_AUDIT_SUCCESS
83
-
84
- # Failure audit event, an event that records an audited security attempt
85
- # that fails.
86
- AUDIT_FAILURE = EVENTLOG_AUDIT_FAILURE
87
-
88
- private
89
-
90
- # :stopdoc:
91
-
92
- BUFFER_SIZE = 1024 * 64
93
- MAX_SIZE = 256
94
- MAX_STRINGS = 16
95
- BASE_KEY = "SYSTEM\\CurrentControlSet\\Services\\EventLog\\"
96
-
97
- # :startdoc:
98
-
99
- public
100
-
101
- # The EventLogStruct encapsulates a single event log record.
102
- EventLogStruct = Struct.new('EventLogStruct', :record_number,
103
- :time_generated, :time_written, :event_id, :event_type, :category,
104
- :source, :computer, :user, :string_inserts, :description
105
- )
106
-
107
- # The name of the event log source. This will typically be
108
- # 'Application', 'System' or 'Security', but could also refer to
109
- # a custom event log source.
110
- #
111
- attr_reader :source
112
-
113
- # The name of the server which the event log is reading from.
114
- #
115
- attr_reader :server
116
-
117
- # The name of the file used in the EventLog.open_backup method. This is
118
- # set to nil if the file was not opened using the EventLog.open_backup
119
- # method.
120
- #
121
- attr_reader :file
122
-
123
- # Opens a handle to the new EventLog +source+ on +server+, or the local
124
- # machine if no host is specified. Typically, your source will be
125
- # 'Application, 'Security' or 'System', although you can specify a
126
- # custom log file as well.
127
- #
128
- # If a custom, registered log file name cannot be found, the event
129
- # logging service opens the 'Application' log file. This is the
130
- # behavior of the underlying Windows function, not my own doing.
131
- #
132
- def initialize(source = 'Application', server = nil, file = nil)
133
- @source = source || 'Application' # In case of explicit nil
134
- @server = server
135
- @file = file
136
-
137
- # Avoid potential segfaults from win32-api
138
- raise TypeError unless @source.is_a?(String)
139
- raise TypeError unless @server.is_a?(String) if @server
140
-
141
- function = 'OpenEventLog()'
142
-
143
- if @file.nil?
144
- @handle = OpenEventLog(@server, @source)
145
- else
146
- @handle = OpenBackupEventLog(@server, @file)
147
- function = 'OpenBackupEventLog()'
148
- end
149
-
150
- if @handle == 0
151
- error = "#{function} failed: " + get_last_error
152
- raise Error, error
153
- end
154
-
155
- # Ensure the handle is closed at the end of a block
156
- if block_given?
157
- begin
158
- yield self
159
- ensure
160
- close
161
- end
162
- end
163
- end
164
-
165
- # Class method aliases
166
- class << self
167
- alias :open :new
168
- end
169
-
170
- # Nearly identical to EventLog.open, except that the source is a backup
171
- # file and not an event source (and there is no default).
172
- #
173
- def self.open_backup(file, source = 'Application', server = nil, &block)
174
- @file = file
175
- @source = source
176
- @server = server
177
-
178
- # Avoid potential segfaults from win32-api
179
- raise TypeError unless @file.is_a?(String)
180
- raise TypeError unless @source.is_a?(String)
181
- raise TypeError unless @server.is_a?(String) if @server
182
-
183
- self.new(source, server, file, &block)
184
- end
185
-
186
- # Adds an event source to the registry. Returns the disposition, which
187
- # is either REG_CREATED_NEW_KEY (1) or REG_OPENED_EXISTING_KEY (2).
188
- #
189
- # The following are valid keys:
190
- #
191
- # * source # Source name. Set to "Application" by default
192
- # * key_name # Name stored as the registry key
193
- # * category_count # Number of supported (custom) categories
194
- # * event_message_file # File (dll) that defines events
195
- # * category_message_file # File (dll) that defines categories
196
- # * parameter_message_file # File (dll) that contains values for
197
- # variables in the event description.
198
- # * supported_types # See the 'event types' constants
199
- #
200
- # Of these keys, only +key_name+ is mandatory. An ArgumentError is
201
- # raised if you attempt to use an invalid key. If +supported_types+
202
- # is not specified then the following value is used:
203
- #
204
- # EventLog::ERROR | EventLog::WARN | EventLog::INFO
205
- #
206
- # The +event_message_file+ and +category_message_file+ are typically,
207
- # though not necessarily, the same file. See the documentation on .mc files
208
- # for more details.
209
- #
210
- def self.add_event_source(args)
211
- raise TypeError unless args.is_a?(Hash)
212
-
213
- valid_keys = %w/
214
- source
215
- key_name
216
- category_count
217
- event_message_file
218
- category_message_file
219
- parameter_message_file
220
- supported_types
221
- /
222
-
223
- key_base = "SYSTEM\\CurrentControlSet\\Services\\EventLog\\"
224
-
225
- # Default values
226
- hash = {
227
- 'source' => 'Application',
228
- 'supported_types' => ERROR | WARN | INFO
229
- }
230
-
231
- # Validate the keys, and convert symbols and case to lowercase strings.
232
- args.each{ |key, val|
233
- key = key.to_s.downcase
234
- unless valid_keys.include?(key)
235
- raise ArgumentError, "invalid key '#{key}'"
236
- end
237
- hash[key] = val
238
- }
239
-
240
- # The key_name must be specified
241
- unless hash['key_name']
242
- raise Error, 'no event_type specified'
243
- end
244
-
245
- hkey = [0].pack('L')
246
- key = key_base + hash['source']
247
-
248
- disposition = [0].pack('L')
249
-
250
- rv = RegCreateKeyEx(
251
- HKEY_LOCAL_MACHINE,
252
- key,
253
- 0,
254
- nil,
255
- REG_OPTION_NON_VOLATILE,
256
- KEY_WRITE,
257
- nil,
258
- hkey,
259
- disposition
260
- )
261
-
262
- if rv != ERROR_SUCCESS
263
- error = 'RegCreateKeyEx() failed: ' + get_last_error
264
- raise Error, error
265
- end
266
-
267
- hkey = hkey.unpack('L')[0]
268
- data = "%SystemRoot%\\System32\\config\\#{hash['source']}.evt"
269
-
270
- begin
271
- rv = RegSetValueEx(
272
- hkey,
273
- 'File',
274
- 0,
275
- REG_EXPAND_SZ,
276
- data,
277
- data.size
278
- )
279
-
280
- if rv != ERROR_SUCCESS
281
- error = 'RegSetValueEx() failed: ', get_last_error
282
- raise Error, error
283
- end
284
- ensure
285
- RegCloseKey(hkey)
286
- end
287
-
288
- hkey = [0].pack('L')
289
- key = key_base << hash['source'] << "\\" << hash['key_name']
290
-
291
- disposition = [0].pack('L')
292
-
293
- begin
294
- rv = RegCreateKeyEx(
295
- HKEY_LOCAL_MACHINE,
296
- key,
297
- 0,
298
- nil,
299
- REG_OPTION_NON_VOLATILE,
300
- KEY_WRITE,
301
- nil,
302
- hkey,
303
- disposition
304
- )
305
-
306
- if rv != ERROR_SUCCESS
307
- raise Error, 'RegCreateKeyEx() failed: ' + get_last_error
308
- end
309
-
310
- hkey = hkey.unpack('L')[0]
311
-
312
- if hash['category_count']
313
- data = [hash['category_count']].pack('L')
314
-
315
- rv = RegSetValueEx(
316
- hkey,
317
- 'CategoryCount',
318
- 0,
319
- REG_DWORD,
320
- data,
321
- data.size
322
- )
323
-
324
- if rv != ERROR_SUCCESS
325
- error = 'RegSetValueEx() failed: ' + get_last_error
326
- raise Error, error
327
- end
328
- end
329
-
330
- if hash['category_message_file']
331
- data = File.expand_path(hash['category_message_file'])
332
-
333
- rv = RegSetValueEx(
334
- hkey,
335
- 'CategoryMessageFile',
336
- 0,
337
- REG_EXPAND_SZ,
338
- data,
339
- data.size
340
- )
341
-
342
- if rv != ERROR_SUCCESS
343
- error = 'RegSetValueEx() failed: ' + get_last_error
344
- raise Error, error
345
- end
346
- end
347
-
348
- if hash['event_message_file']
349
- data = File.expand_path(hash['event_message_file'])
350
-
351
- rv = RegSetValueEx(
352
- hkey,
353
- 'EventMessageFile',
354
- 0,
355
- REG_EXPAND_SZ,
356
- data,
357
- data.size
358
- )
359
-
360
- if rv != ERROR_SUCCESS
361
- error = 'RegSetValueEx() failed: ' + get_last_error
362
- raise Error, error
363
- end
364
- end
365
-
366
- if hash['parameter_message_file']
367
- data = File.expand_path(hash['parameter_message_file'])
368
-
369
- rv = RegSetValueEx(
370
- hkey,
371
- 'ParameterMessageFile',
372
- 0,
373
- REG_EXPAND_SZ,
374
- data,
375
- data.size
376
- )
377
-
378
- if rv != ERROR_SUCCESS
379
- error = 'RegSetValueEx() failed: ' + get_last_error
380
- raise Error, error
381
- end
382
- end
383
-
384
- data = [hash['supported_types']].pack('L')
385
-
386
- rv = RegSetValueEx(
387
- hkey,
388
- 'TypesSupported',
389
- 0,
390
- REG_DWORD,
391
- data,
392
- data.size
393
- )
394
-
395
- if rv != ERROR_SUCCESS
396
- error = 'RegSetValueEx() failed: ' + get_last_error
397
- raise Error, error
398
- end
399
- ensure
400
- RegCloseKey(hkey)
401
- end
402
-
403
- disposition.unpack('L')[0]
404
- end
405
-
406
- # Backs up the event log to +file+. Note that you cannot backup to
407
- # a file that already exists or a Error will be raised.
408
- #
409
- def backup(file)
410
- raise TypeError unless file.is_a?(String)
411
- unless BackupEventLog(@handle, file)
412
- error = 'BackupEventLog() failed: ' + get_last_error
413
- raise Error, error
414
- end
415
- self
416
- end
417
-
418
- # Clears the EventLog. If +backup_file+ is provided, it backs up the
419
- # event log to that file first.
420
- #
421
- def clear(backup_file = nil)
422
- raise TypeError unless backup_file.is_a?(String) if backup_file
423
- backup_file = 0 unless backup_file
424
-
425
- unless ClearEventLog(@handle, backup_file)
426
- error = 'ClearEventLog() failed: ' + get_last_error
427
- raise Error
428
- end
429
-
430
- self
431
- end
432
-
433
- # Closes the EventLog handle. The handle is automatically closed for you
434
- # if you use the block form of EventLog.new.
435
- #
436
- def close
437
- CloseEventLog(@handle)
438
- end
439
-
440
- # Indicates whether or not the event log is full.
441
- #
442
- def full?
443
- buf = [0].pack('L') # dwFull
444
- needed = [0].pack('L')
445
-
446
- unless GetEventLogInformation(@handle, 0, buf, buf.size, needed)
447
- raise Error, 'GetEventLogInformation() failed: ' + get_last_error
448
- end
449
-
450
- buf[0,4].unpack('L')[0] != 0
451
- end
452
-
453
- # Returns the absolute record number of the oldest record. Note that
454
- # this is not guaranteed to be 1 because event log records can be
455
- # overwritten.
456
- #
457
- def oldest_record_number
458
- rec = [0].pack('L')
459
-
460
- unless GetOldestEventLogRecord(@handle, rec)
461
- error = 'GetOldestEventLogRecord() failed: ' + get_last_error
462
- raise Error, error
463
- end
464
-
465
- rec.unpack('L')[0]
466
- end
467
-
468
- # Returns the total number of records for the given event log.
469
- #
470
- def total_records
471
- total = [0].pack('L')
472
-
473
- unless GetNumberOfEventLogRecords(@handle, total)
474
- error = 'GetNumberOfEventLogRecords() failed: ' + get_last_error
475
- raise Error, error
476
- end
477
-
478
- total.unpack('L')[0]
479
- end
480
-
481
- # Yields an EventLogStruct every time a record is written to the event
482
- # log. Unlike EventLog#tail, this method breaks out of the block after
483
- # the event.
484
- #
485
- # Raises an Error if no block is provided.
486
- #
487
- def notify_change(&block)
488
- unless block_given?
489
- raise Error, 'block missing for notify_change()'
490
- end
491
-
492
- # Reopen the handle because the NotifyChangeEventLog() function will
493
- # choke after five or six reads otherwise.
494
- @handle = OpenEventLog(@server, @source)
495
-
496
- if @handle == 0
497
- error = 'OpenEventLog() failed: ' + get_last_error
498
- raise Error, error
499
- end
500
-
501
- event = CreateEvent(0, 0, 0, 0)
502
-
503
- unless NotifyChangeEventLog(@handle, event)
504
- error = 'NotifyChangeEventLog() failed: ' + get_last_error
505
- raise Error, error
506
- end
507
-
508
- wait_result = WaitForSingleObject(event, INFINITE)
509
-
510
- begin
511
- if wait_result == WAIT_FAILED
512
- error = 'WaitForSingleObject() failed: ' + get_last_error
513
- raise Error, error
514
- else
515
- last = read_last_event
516
- block.call(last)
517
- end
518
- ensure
519
- CloseHandle(event)
520
- end
521
-
522
- self
523
- end
524
-
525
- # Yields an EventLogStruct every time a record is written to the event
526
- # log, once every +frequency+ seconds. Unlike EventLog#notify_change,
527
- # this method does not break out of the block after the event. The read
528
- # +frequency+ is set to 5 seconds by default.
529
- #
530
- # Raises an Error if no block is provided.
531
- #
532
- # The delay between reads is due to the nature of the Windows event log.
533
- # It is not really designed to be tailed in the manner of a Unix syslog,
534
- # for example, in that not nearly as many events are typically recorded.
535
- # It's just not designed to be polled that heavily.
536
- #
537
- def tail(frequency = 5)
538
- unless block_given?
539
- raise Error, 'block missing for tail()'
540
- end
541
-
542
- old_total = total_records()
543
- flags = FORWARDS_READ | SEEK_READ
544
- rec_num = read_last_event.record_number
545
-
546
- while true
547
- new_total = total_records()
548
- if new_total != old_total
549
- rec_num = oldest_record_number() if full?
550
- read(flags, rec_num).each{ |log| yield log }
551
- old_total = new_total
552
- rec_num = read_last_event.record_number + 1
553
- end
554
- sleep frequency
555
- end
556
- end
557
-
558
- # Iterates over each record in the event log, yielding a EventLogStruct
559
- # for each record. The offset value is only used when used in
560
- # conjunction with the EventLog::SEEK_READ flag. Otherwise, it is
561
- # ignored. If no flags are specified, then the default flags are:
562
- #
563
- # EventLog::SEQUENTIAL_READ | EventLog::FORWARDS_READ
564
- #
565
- # Note that, if you're performing a SEEK_READ, then the offset must
566
- # refer to a record number that actually exists. The default of 0
567
- # may or may not work for your particular event log.
568
- #
569
- # The EventLogStruct struct contains the following members:
570
- #
571
- # * record_number # Fixnum
572
- # * time_generated # Time
573
- # * time_written # Time
574
- # * event_id # Fixnum
575
- # * event_type # String
576
- # * category # String
577
- # * source # String
578
- # * computer # String
579
- # * user # String or nil
580
- # * description # String or nil
581
- # * string_inserts # An array of Strings or nil
582
- #
583
- # If no block is given the method returns an array of EventLogStruct's.
584
- #
585
- def read(flags = nil, offset = 0)
586
- buf = 0.chr * BUFFER_SIZE # 64k buffer
587
- read = [0].pack('L')
588
- needed = [0].pack('L')
589
- array = []
590
- lkey = HKEY_LOCAL_MACHINE
591
-
592
- unless flags
593
- flags = FORWARDS_READ | SEQUENTIAL_READ
594
- end
595
-
596
- if @server
597
- hkey = [0].pack('L')
598
- if RegConnectRegistry(@server, HKEY_LOCAL_MACHINE, hkey) != 0
599
- raise Error, get_last_error
600
- end
601
- lkey = hkey.unpack('L').first
602
- end
603
-
604
- while ReadEventLog(@handle, flags, offset, buf, buf.size, read, needed) ||
605
- GetLastError() == ERROR_INSUFFICIENT_BUFFER
606
-
607
- if GetLastError() == ERROR_INSUFFICIENT_BUFFER
608
- buf = (0.chr * buf.size) + (0.chr * needed.unpack('L')[0])
609
- unless ReadEventLog(@handle, flags, offset, buf, buf.size, read, needed)
610
- raise Error, get_last_error
611
- end
612
- end
613
-
614
- dwread = read.unpack('L')[0]
615
-
616
- while dwread > 0
617
- struct = EventLogStruct.new
618
- event_source = buf[56..-1].nstrip
619
- computer = buf[56 + event_source.length + 1..-1].nstrip
620
-
621
- user = get_user(buf)
622
- strings, desc = get_description(buf, event_source, lkey)
623
-
624
- struct.source = event_source
625
- struct.computer = computer
626
- struct.record_number = buf[8,4].unpack('L')[0]
627
- struct.time_generated = Time.at(buf[12,4].unpack('L')[0])
628
- struct.time_written = Time.at(buf[16,4].unpack('L')[0])
629
- struct.event_id = buf[20,4].unpack('L')[0] & 0x0000FFFF
630
- struct.event_type = get_event_type(buf[24,2].unpack('S')[0])
631
- struct.user = user
632
- struct.category = buf[28,2].unpack('S')[0]
633
- struct.string_inserts = strings
634
- struct.description = desc
635
-
636
- struct.freeze # This is read-only information
637
-
638
- if block_given?
639
- yield struct
640
- else
641
- array.push(struct)
642
- end
643
-
644
- if flags & EVENTLOG_BACKWARDS_READ > 0
645
- offset = buf[8,4].unpack('L')[0] - 1
646
- else
647
- offset = buf[8,4].unpack('L')[0] + 1
648
- end
649
-
650
- length = buf[0,4].unpack('L')[0] # Length
651
-
652
- dwread -= length
653
- buf = buf[length..-1]
654
- end
655
-
656
- buf = 0.chr * BUFFER_SIZE
657
- read = [0].pack('L')
658
- end
659
-
660
- block_given? ? nil : array
661
- end
662
-
663
- # This class method is nearly identical to the EventLog#read instance
664
- # method, except that it takes a +source+ and +server+ as the first two
665
- # arguments.
666
- #
667
- def self.read(source='Application', server=nil, flags=nil, offset=0)
668
- self.new(source, server){ |log|
669
- if block_given?
670
- log.read(flags, offset){ |els| yield els }
671
- else
672
- return log.read(flags, offset)
673
- end
674
- }
675
- end
676
-
677
- # Writes an event to the event log. The following are valid keys:
678
- #
679
- # * source # Event log source name. Defaults to "Application"
680
- # * event_id # Event ID (defined in event message file)
681
- # * category # Event category (defined in category message file)
682
- # * data # String that is written to the log
683
- # * event_type # Type of event, e.g. EventLog::ERROR, etc.
684
- #
685
- # The +event_type+ keyword is the only mandatory keyword. The others are
686
- # optional. Although the +source+ defaults to "Application", I
687
- # recommend that you create an application specific event source and use
688
- # that instead. See the 'EventLog.add_event_source' method for more
689
- # details.
690
- #
691
- # The +event_id+ and +category+ values are defined in the message
692
- # file(s) that you created for your application. See the tutorial.txt
693
- # file for more details on how to create a message file.
694
- #
695
- # An ArgumentError is raised if you attempt to use an invalid key.
696
- #
697
- def report_event(args)
698
- raise TypeError unless args.is_a?(Hash)
699
-
700
- valid_keys = %w/source event_id category data event_type/
701
- num_strings = 0
702
-
703
- # Default values
704
- hash = {
705
- 'source' => @source,
706
- 'event_id' => 0,
707
- 'category' => 0,
708
- 'data' => 0
709
- }
710
-
711
- # Validate the keys, and convert symbols and case to lowercase strings.
712
- args.each{ |key, val|
713
- key = key.to_s.downcase
714
- unless valid_keys.include?(key)
715
- raise ArgumentError, "invalid key '#{key}'"
716
- end
717
- hash[key] = val
718
- }
719
-
720
- # The event_type must be specified
721
- unless hash['event_type']
722
- raise Error, 'no event_type specified'
723
- end
724
-
725
- handle = RegisterEventSource(@server, hash['source'])
726
-
727
- if handle == 0
728
- error = 'RegisterEventSource() failed: ' + get_last_error
729
- raise Error, error
730
- end
731
-
732
- if hash['data'].is_a?(String)
733
- data = hash['data'] << 0.chr
734
- data = [data].pack('p*')
735
- num_strings = 1
736
- else
737
- data = 0
738
- num_strings = 0
739
- end
740
-
741
- bool = ReportEvent(
742
- handle,
743
- hash['event_type'],
744
- hash['category'],
745
- hash['event_id'],
746
- 0,
747
- num_strings,
748
- 0,
749
- data,
750
- 0
751
- )
752
-
753
- unless bool
754
- error = 'ReportEvent() failed: ' + get_last_error
755
- raise Error, error
756
- end
757
-
758
- self
759
- end
760
-
761
- alias :write :report_event
762
-
763
- private
764
-
765
- # A private method that reads the last event log record.
766
- #
767
- def read_last_event(handle=@handle, source=@source, server=@server)
768
- buf = 0.chr * BUFFER_SIZE # 64k buffer
769
- read = [0].pack('L')
770
- needed = [0].pack('L')
771
- lkey = HKEY_LOCAL_MACHINE
772
-
773
- flags = EVENTLOG_BACKWARDS_READ | EVENTLOG_SEQUENTIAL_READ
774
-
775
- unless ReadEventLog(@handle, flags, 0, buf, buf.size, read, needed)
776
- error = GetLastError()
777
- if error == ERROR_INSUFFICIENT_BUFFER
778
- buf = (0.chr * buf.size) + (0.chr * needed.unpack('L')[0])
779
- unless ReadEventLog(@handle, flags, 0, buf, buf.size, read, needed)
780
- raise Error, get_last_error
781
- end
782
- else
783
- raise Error, get_last_error(error)
784
- end
785
- end
786
-
787
- if @server
788
- hkey = [0].pack('L')
789
- if RegConnectRegistry(@server, HKEY_LOCAL_MACHINE, hkey) != 0
790
- raise Error, get_last_error
791
- end
792
- lkey = hkey.unpack('L').first
793
- end
794
-
795
- event_source = buf[56..-1].nstrip
796
- computer = buf[56 + event_source.length + 1..-1].nstrip
797
- event_type = get_event_type(buf[24,2].unpack('S')[0])
798
- user = get_user(buf)
799
- strings, desc = get_description(buf, event_source, lkey)
800
-
801
- struct = EventLogStruct.new
802
- struct.source = event_source
803
- struct.computer = computer
804
- struct.record_number = buf[8,4].unpack('L')[0]
805
- struct.time_generated = Time.at(buf[12,4].unpack('L')[0])
806
- struct.time_written = Time.at(buf[16,4].unpack('L')[0])
807
- struct.event_id = buf[20,4].unpack('L')[0] & 0x0000FFFF
808
- struct.event_type = event_type
809
- struct.user = user
810
- struct.category = buf[28,2].unpack('S')[0]
811
- struct.string_inserts = strings
812
- struct.description = desc
813
-
814
- struct.freeze # This is read-only information
815
-
816
- struct
817
- end
818
-
819
- # Private method that retrieves the user name based on data in the
820
- # EVENTLOGRECORD buffer.
821
- #
822
- def get_user(buf)
823
- return nil if buf[40,4].unpack('L')[0] <= 0 # UserSidLength
824
-
825
- name = 0.chr * MAX_SIZE
826
- name_size = [name.size].pack('L')
827
- domain = 0.chr * MAX_SIZE
828
- domain_size = [domain.size].pack('L')
829
- snu = 0.chr * 4
830
-
831
- offset = buf[44,4].unpack('L')[0] # UserSidOffset
832
-
833
- val = LookupAccountSid(
834
- @server,
835
- [buf].pack('P').unpack('L')[0] + offset,
836
- name,
837
- name_size,
838
- domain,
839
- domain_size,
840
- snu
841
- )
842
-
843
- # Return nil if the lookup failed
844
- return val ? name.nstrip : nil
845
- end
846
-
847
- # Private method that converts a numeric event type into a human
848
- # readable string.
849
- #
850
- def get_event_type(event)
851
- case event
852
- when EVENTLOG_ERROR_TYPE
853
- 'error'
854
- when EVENTLOG_WARNING_TYPE
855
- 'warning'
856
- when EVENTLOG_INFORMATION_TYPE, EVENTLOG_SUCCESS
857
- 'information'
858
- when EVENTLOG_AUDIT_SUCCESS
859
- 'audit_success'
860
- when EVENTLOG_AUDIT_FAILURE
861
- 'audit_failure'
862
- else
863
- nil
864
- end
865
- end
866
-
867
- # Private method that gets the string inserts (Array) and the full
868
- # event description (String) based on data from the EVENTLOGRECORD
869
- # buffer.
870
- #
871
- def get_description(rec, event_source, lkey)
872
- str = rec[rec[36,4].unpack('L')[0] .. -1]
873
- num = rec[26,2].unpack('S')[0] # NumStrings
874
- hkey = [0].pack('L')
875
- key = BASE_KEY + "#{@source}\\#{event_source}"
876
- buf = 0.chr * 8192
877
- va_list = va_list0 = (num == 0) ? [] : str.unpack('Z*' * num)
878
-
879
- begin
880
- if defined? Wow64DisableWow64FsRedirection
881
- old_wow_val = 0.chr * 4
882
- Wow64DisableWow64FsRedirection(old_wow_val)
883
- end
884
-
885
- param_exe = nil
886
- message_exe = nil
887
-
888
- if RegOpenKeyEx(lkey, key, 0, KEY_READ, hkey) == 0
889
- hkey = hkey.unpack('L')[0]
890
-
891
- value = 'providerGuid'
892
- guid = 0.chr * MAX_SIZE
893
- size = [ guid.length].pack('L')
894
-
895
- if RegQueryValueEx(hkey, value, 0, 0, guid, size) == 0
896
- guid = guid.nstrip
897
- hkey2 = [0].pack('L')
898
- key = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\"
899
- key << "WINEVT\\Publishers\\#{guid}"
900
-
901
- if RegOpenKeyEx(lkey, key, 0, KEY_READ|0x100, hkey2) == 0
902
- hkey2 = hkey2.unpack('L')[0]
903
- value = 'ParameterMessageFile'
904
- file = 0.chr * MAX_SIZE
905
- size = [ file.length].pack('L')
906
-
907
- if RegQueryValueEx(hkey2, value, 0, 0, file, size) == 0
908
- file = file.nstrip
909
- exe = 0.chr * MAX_SIZE
910
- ExpandEnvironmentStrings(file, exe, exe.size)
911
- param_exe = exe.nstrip
912
- end
913
-
914
- value = 'MessageFileName'
915
- file = 0.chr * MAX_SIZE
916
- size = [file.length].pack('L')
917
-
918
- if RegQueryValueEx(hkey2, value, 0, 0, file, size) == 0
919
- file = file.nstrip
920
- exe = 0.chr * MAX_SIZE
921
- ExpandEnvironmentStrings(file, exe, exe.size)
922
- message_exe = exe.nstrip
923
- end
924
-
925
- RegCloseKey(hkey2)
926
- end
927
- else
928
- value = 'ParameterMessageFile'
929
- file = 0.chr * MAX_SIZE
930
- size = [ file.length].pack('L')
931
-
932
- if RegQueryValueEx(hkey, value, 0, 0, file, size) == 0
933
- file = file.nstrip
934
- exe = 0.chr * MAX_SIZE
935
- ExpandEnvironmentStrings(file, exe, exe.size)
936
- param_exe = exe.nstrip
937
- end
938
-
939
- value = 'EventMessageFile'
940
- file = 0.chr * MAX_SIZE
941
- size = [file.length].pack('L')
942
-
943
- if RegQueryValueEx(hkey, value, 0, 0, file, size) == 0
944
- file = file.nstrip
945
- exe = 0.chr * MAX_SIZE
946
- ExpandEnvironmentStrings(file, exe, exe.size)
947
- message_exe = exe.nstrip
948
- end
949
- end
950
-
951
- RegCloseKey(hkey)
952
- elsif defined? EvtOpenPublisherMetadata # Vista or later
953
- pubMetadata = EvtOpenPublisherMetadata(
954
- 0,
955
- multi_to_wide(event_source),
956
- 0,
957
- 1024, # Default LCID
958
- 0
959
- )
960
-
961
- if pubMetadata > 0
962
- buf2 = 0.chr * 8192
963
- val = 0.chr*4
964
-
965
- EvtGetPublisherMetadataProperty(
966
- pubMetadata,
967
- 2, # EvtPublisherMetadataParameterFilePath
968
- 0,
969
- 8192,
970
- buf2,
971
- val
972
- )
973
-
974
- file = wide_to_multi(buf2[16..-1])
975
- exe = 0.chr * MAX_SIZE
976
- ExpandEnvironmentStrings(file, exe, exe.size)
977
- param_exe = exe.nstrip
978
-
979
- buf2 = 0.chr * 8192
980
- val = 0.chr*4
981
-
982
- EvtGetPublisherMetadataProperty(
983
- pubMetadata,
984
- 3, # EvtPublisherMetadataMessageFilePath
985
- 0,
986
- 8192,
987
- buf2,
988
- val
989
- )
990
-
991
- file = wide_to_multi(buf2[16..-1])
992
- exe = 0.chr * MAX_SIZE
993
- ExpandEnvironmentStrings(file, exe, exe.size)
994
- message_exe = exe.nstrip
995
- EvtClose(pubMetadata)
996
- end
997
- end
998
-
999
- if param_exe != nil
1000
- va_list = va_list0.map{ |v|
1001
- va = v
1002
-
1003
- v.scan(/%%(\d+)/).uniq.each{ |x|
1004
- param_exe.split(';').each{ |file|
1005
- hmodule = LoadLibraryEx(
1006
- file,
1007
- 0,
1008
- DONT_RESOLVE_DLL_REFERENCES |
1009
- LOAD_LIBRARY_AS_DATAFILE
1010
- )
1011
-
1012
- if hmodule != 0
1013
- res = FormatMessage(
1014
- FORMAT_MESSAGE_FROM_HMODULE |
1015
- FORMAT_MESSAGE_ARGUMENT_ARRAY,
1016
- hmodule,
1017
- x.first.to_i,
1018
- 0,
1019
- buf,
1020
- buf.size,
1021
- v
1022
- )
1023
-
1024
- if res == 0
1025
- event_id = 0xB0000000 | event_id
1026
- res = FormatMessage(
1027
- FORMAT_MESSAGE_FROM_HMODULE |
1028
- FORMAT_MESSAGE_IGNORE_INSERTS,
1029
- hmodule,
1030
- event_id,
1031
- 0,
1032
- buf,
1033
- buf.size,
1034
- nil
1035
- )
1036
- end
1037
-
1038
- FreeLibrary(hmodule)
1039
- break if buf.nstrip != ""
1040
- end
1041
- }
1042
- va = va.gsub("%%#{x.first}", buf.nstrip)
1043
- }
1044
- va
1045
- }
1046
- end
1047
-
1048
- if message_exe != nil
1049
- buf = 0.chr * 8192 # Reset the buffer
1050
-
1051
- # Try to retrieve message *without* expanding the inserts yet
1052
- message_exe.split(';').each{ |file|
1053
- hmodule = LoadLibraryEx(
1054
- file,
1055
- 0,
1056
- DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE
1057
- )
1058
-
1059
- event_id = rec[20,4].unpack('L')[0]
1060
-
1061
- if hmodule != 0
1062
- res = FormatMessage(
1063
- FORMAT_MESSAGE_FROM_HMODULE |
1064
- FORMAT_MESSAGE_IGNORE_INSERTS,
1065
- hmodule,
1066
- event_id,
1067
- 0,
1068
- buf,
1069
- buf.size,
1070
- nil
1071
- )
1072
-
1073
- if res == 0
1074
- event_id = 0xB0000000 | event_id
1075
-
1076
- res = FormatMessage(
1077
- FORMAT_MESSAGE_FROM_HMODULE |
1078
- FORMAT_MESSAGE_IGNORE_INSERTS,
1079
- hmodule,
1080
- event_id,
1081
- 0,
1082
- buf,
1083
- buf.size,
1084
- nil
1085
- )
1086
- end
1087
-
1088
- FreeLibrary(hmodule)
1089
- break if buf.nstrip != "" # All messages read
1090
- end
1091
- }
1092
-
1093
- # Determine higest %n insert number
1094
- max_insert = [num, buf.nstrip.scan(/%(\d+)/).map{ |x| x[0].to_i }.max].compact.max
1095
-
1096
- # Insert dummy strings not provided by caller
1097
- ((num+1)..(max_insert)).each{ |x| va_list.push("%#{x}") }
1098
-
1099
- if num == 0
1100
- va_list_ptr = 0.chr * 4
1101
- else
1102
- va_list_ptr = va_list.map{ |x|
1103
- [x + 0.chr].pack('P').unpack('L')[0]
1104
- }.pack('L*')
1105
- end
1106
-
1107
- message_exe.split(';').each{ |file|
1108
- hmodule = LoadLibraryEx(
1109
- file,
1110
- 0,
1111
- DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE
1112
- )
1113
-
1114
- event_id = rec[20,4].unpack('L')[0]
1115
-
1116
- if hmodule != 0
1117
- res = FormatMessage(
1118
- FORMAT_MESSAGE_FROM_HMODULE |
1119
- FORMAT_MESSAGE_ARGUMENT_ARRAY,
1120
- hmodule,
1121
- event_id,
1122
- 0,
1123
- buf,
1124
- buf.size,
1125
- va_list_ptr
1126
- )
1127
-
1128
- if res == 0
1129
- event_id = 0xB0000000 | event_id
1130
-
1131
- res = FormatMessage(
1132
- FORMAT_MESSAGE_FROM_HMODULE |
1133
- FORMAT_MESSAGE_ARGUMENT_ARRAY,
1134
- hmodule,
1135
- event_id,
1136
- 0,
1137
- buf,
1138
- buf.size,
1139
- va_list_ptr
1140
- )
1141
- end
1142
-
1143
- FreeLibrary(hmodule)
1144
- break if buf.nstrip != "" # All messages read
1145
- end
1146
- }
1147
- end
1148
- ensure
1149
- if defined? Wow64RevertWow64FsRedirection
1150
- Wow64RevertWow64FsRedirection(old_wow_val.unpack('L')[0])
1151
- end
1152
- end
1153
-
1154
- [va_list0, buf.nstrip]
1155
- end
1156
- end
1157
- end
1
+ require File.join(File.dirname(__FILE__), 'windows', 'constants')
2
+ require File.join(File.dirname(__FILE__), 'windows', 'structs')
3
+ require File.join(File.dirname(__FILE__), 'windows', 'functions')
4
+
5
+ # The Win32 module serves as a namespace only.
6
+ module Win32
7
+
8
+ # The EventLog class encapsulates an Event Log source and provides methods
9
+ # for interacting with that source.
10
+ class EventLog
11
+ include Windows::Constants
12
+ include Windows::Structs
13
+ include Windows::Functions
14
+ extend Windows::Functions
15
+
16
+ # The EventLog::Error is raised in cases where interaction with the
17
+ # event log should happen to fail for any reason.
18
+ class Error < StandardError; end
19
+
20
+ # The version of the win32-eventlog library
21
+ VERSION = '0.6.0'
22
+
23
+ # The log is read in chronological order, i.e. oldest to newest.
24
+ FORWARDS_READ = EVENTLOG_FORWARDS_READ
25
+
26
+ # The log is read in reverse chronological order, i.e. newest to oldest.
27
+ BACKWARDS_READ = EVENTLOG_BACKWARDS_READ
28
+
29
+ # Begin reading from a specific record.
30
+ SEEK_READ = EVENTLOG_SEEK_READ
31
+
32
+ # Read the records sequentially. If this is the first read operation, the
33
+ # EVENTLOG_FORWARDS_READ or EVENTLOG_BACKWARDS_READ flags determines
34
+ # which record is read first.
35
+ SEQUENTIAL_READ = EVENTLOG_SEQUENTIAL_READ
36
+
37
+ # Event types
38
+
39
+ # Information event, an event that describes the successful operation
40
+ # of an application, driver or service.
41
+ SUCCESS = EVENTLOG_SUCCESS
42
+
43
+ # Error event, an event that indicates a significant problem such as
44
+ # loss of data or functionality.
45
+ ERROR_TYPE = EVENTLOG_ERROR_TYPE
46
+
47
+ # Warning event, an event that is not necessarily significant but may
48
+ # indicate a possible future problem.
49
+ WARN_TYPE = EVENTLOG_WARNING_TYPE
50
+
51
+ # Information event, an event that describes the successful operation
52
+ # of an application, driver or service.
53
+ INFO_TYPE = EVENTLOG_INFORMATION_TYPE
54
+
55
+ # Success audit event, an event that records an audited security attempt
56
+ # that is successful.
57
+ AUDIT_SUCCESS = EVENTLOG_AUDIT_SUCCESS
58
+
59
+ # Failure audit event, an event that records an audited security attempt
60
+ # that fails.
61
+ AUDIT_FAILURE = EVENTLOG_AUDIT_FAILURE
62
+
63
+ # The EventLogStruct encapsulates a single event log record.
64
+ EventLogStruct = Struct.new('EventLogStruct', :record_number,
65
+ :time_generated, :time_written, :event_id, :event_type, :category,
66
+ :source, :computer, :user, :string_inserts, :description
67
+ )
68
+
69
+ # The name of the event log source. This will typically be
70
+ # 'Application', 'System' or 'Security', but could also refer to
71
+ # a custom event log source.
72
+ #
73
+ attr_reader :source
74
+
75
+ # The name of the server which the event log is reading from.
76
+ #
77
+ attr_reader :server
78
+
79
+ # The name of the file used in the EventLog.open_backup method. This is
80
+ # set to nil if the file was not opened using the EventLog.open_backup
81
+ # method.
82
+ #
83
+ attr_reader :file
84
+
85
+ # Opens a handle to the new EventLog +source+ on +server+, or the local
86
+ # machine if no host is specified. Typically, your source will be
87
+ # 'Application, 'Security' or 'System', although you can specify a
88
+ # custom log file as well.
89
+ #
90
+ # If a custom, registered log file name cannot be found, the event
91
+ # logging service opens the 'Application' log file. This is the
92
+ # behavior of the underlying Windows function, not my own doing.
93
+ #
94
+ def initialize(source = 'Application', server = nil, file = nil)
95
+ @source = source || 'Application' # In case of explicit nil
96
+ @server = server
97
+ @file = file
98
+
99
+ # Avoid potential segfaults from win32-api
100
+ raise TypeError unless @source.is_a?(String)
101
+ raise TypeError unless @server.is_a?(String) if @server
102
+
103
+ if file.nil?
104
+ function = 'OpenEventLog'
105
+ @handle = OpenEventLog(@server, @source)
106
+ else
107
+ function = 'OpenBackupEventLog'
108
+ @handle = OpenBackupEventLog(@server, @file)
109
+ end
110
+
111
+ if @handle == 0
112
+ raise SystemCallError.new(function, FFI.errno)
113
+ end
114
+
115
+ # Ensure the handle is closed at the end of a block
116
+ if block_given?
117
+ begin
118
+ yield self
119
+ ensure
120
+ close
121
+ end
122
+ end
123
+ end
124
+
125
+ # Class method aliases
126
+ class << self
127
+ alias :open :new
128
+ end
129
+
130
+ # Nearly identical to EventLog.open, except that the source is a backup
131
+ # file and not an event source (and there is no default).
132
+ #
133
+ def self.open_backup(file, source = 'Application', server = nil, &block)
134
+ @file = file
135
+ @source = source
136
+ @server = server
137
+
138
+ # Avoid potential segfaults from win32-api
139
+ raise TypeError unless @file.is_a?(String)
140
+ raise TypeError unless @source.is_a?(String)
141
+ raise TypeError unless @server.is_a?(String) if @server
142
+
143
+ self.new(source, server, file, &block)
144
+ end
145
+
146
+ # Adds an event source to the registry. Returns the disposition, which
147
+ # is either REG_CREATED_NEW_KEY (1) or REG_OPENED_EXISTING_KEY (2).
148
+ #
149
+ # The following are valid keys:
150
+ #
151
+ # * source # Source name. Set to "Application" by default
152
+ # * key_name # Name stored as the registry key
153
+ # * category_count # Number of supported (custom) categories
154
+ # * event_message_file # File (dll) that defines events
155
+ # * category_message_file # File (dll) that defines categories
156
+ # * parameter_message_file # File (dll) that contains values for variables in the event description.
157
+ # * supported_types # See the 'event types' constants
158
+ #
159
+ # Of these keys, only +key_name+ is mandatory. An ArgumentError is
160
+ # raised if you attempt to use an invalid key. If +supported_types+
161
+ # is not specified then the following value is used:
162
+ #
163
+ # EventLog::ERROR_TYPE | EventLog::WARN_TYPE | EventLog::INFO_TYPE
164
+ #
165
+ # The +event_message_file+ and +category_message_file+ are typically,
166
+ # though not necessarily, the same file. See the documentation on .mc files
167
+ # for more details.
168
+ #
169
+ # You will need administrative privileges to use this method.
170
+ #
171
+ def self.add_event_source(args)
172
+ raise TypeError unless args.is_a?(Hash)
173
+
174
+ valid_keys = %w[
175
+ source
176
+ key_name
177
+ category_count
178
+ event_message_file
179
+ category_message_file
180
+ parameter_message_file
181
+ supported_types
182
+ ]
183
+
184
+ key_base = "SYSTEM\\CurrentControlSet\\Services\\EventLog\\"
185
+
186
+ # Default values
187
+ hash = {
188
+ 'source' => 'Application',
189
+ 'supported_types' => ERROR_TYPE | WARN_TYPE | INFO_TYPE
190
+ }
191
+
192
+ # Validate the keys, and convert symbols and case to lowercase strings.
193
+ args.each{ |key, val|
194
+ key = key.to_s.downcase
195
+ unless valid_keys.include?(key)
196
+ raise ArgumentError, "invalid key '#{key}'"
197
+ end
198
+ hash[key] = val
199
+ }
200
+
201
+ # The key_name must be specified
202
+ unless hash['key_name']
203
+ raise ArgumentError, 'no event_type specified'
204
+ end
205
+
206
+ hkey = FFI::MemoryPointer.new(:uintptr_t)
207
+ disposition = FFI::MemoryPointer.new(:ulong)
208
+
209
+ key = key_base + hash['source']
210
+
211
+ rv = RegCreateKeyEx(
212
+ HKEY_LOCAL_MACHINE,
213
+ key,
214
+ 0,
215
+ nil,
216
+ REG_OPTION_NON_VOLATILE,
217
+ KEY_WRITE,
218
+ nil,
219
+ hkey,
220
+ disposition
221
+ )
222
+
223
+ if rv != ERROR_SUCCESS
224
+ raise SystemCallError.new('RegCreateKeyEx', rv)
225
+ end
226
+
227
+ hkey = hkey.read_pointer.to_i
228
+ data = "%SystemRoot%\\System32\\config\\#{hash['source']}.evt"
229
+
230
+ begin
231
+ rv = RegSetValueEx(
232
+ hkey,
233
+ 'File',
234
+ 0,
235
+ REG_EXPAND_SZ,
236
+ data,
237
+ data.size
238
+ )
239
+
240
+ if rv != ERROR_SUCCESS
241
+ raise SystemCallError.new('RegSetValueEx', rv)
242
+ end
243
+ ensure
244
+ RegCloseKey(hkey)
245
+ end
246
+
247
+ hkey = FFI::MemoryPointer.new(:uintptr_t)
248
+ disposition = FFI::MemoryPointer.new(:ulong)
249
+
250
+ key = key_base << hash['source'] << "\\" << hash['key_name']
251
+
252
+ begin
253
+ rv = RegCreateKeyEx(
254
+ HKEY_LOCAL_MACHINE,
255
+ key,
256
+ 0,
257
+ nil,
258
+ REG_OPTION_NON_VOLATILE,
259
+ KEY_WRITE,
260
+ nil,
261
+ hkey,
262
+ disposition
263
+ )
264
+
265
+ if rv != ERROR_SUCCESS
266
+ raise SystemCallError.new('RegCreateKeyEx', rv)
267
+ end
268
+
269
+ hkey = hkey.read_pointer.to_i
270
+
271
+ if hash['category_count']
272
+ data = FFI::MemoryPointer.new(:ulong).write_ulong(hash['category_count'])
273
+
274
+ rv = RegSetValueEx(
275
+ hkey,
276
+ 'CategoryCount',
277
+ 0,
278
+ REG_DWORD,
279
+ data,
280
+ data.size
281
+ )
282
+
283
+ if rv != ERROR_SUCCESS
284
+ raise SystemCallError.new('RegSetValueEx', rv)
285
+ end
286
+ end
287
+
288
+ if hash['category_message_file']
289
+ data = File.expand_path(hash['category_message_file'])
290
+ data = FFI::MemoryPointer.from_string(data)
291
+
292
+ rv = RegSetValueEx(
293
+ hkey,
294
+ 'CategoryMessageFile',
295
+ 0,
296
+ REG_EXPAND_SZ,
297
+ data,
298
+ data.size
299
+ )
300
+
301
+ if rv != ERROR_SUCCESS
302
+ raise SystemCallError.new('RegSetValueEx', rv)
303
+ end
304
+ end
305
+
306
+ if hash['event_message_file']
307
+ data = File.expand_path(hash['event_message_file'])
308
+ data = FFI::MemoryPointer.from_string(data)
309
+
310
+ rv = RegSetValueEx(
311
+ hkey,
312
+ 'EventMessageFile',
313
+ 0,
314
+ REG_EXPAND_SZ,
315
+ data,
316
+ data.size
317
+ )
318
+
319
+ if rv != ERROR_SUCCESS
320
+ raise SystemCallError.new('RegSetValueEx', rv)
321
+ end
322
+ end
323
+
324
+ if hash['parameter_message_file']
325
+ data = File.expand_path(hash['parameter_message_file'])
326
+ data = FFI::MemoryPointer.from_string(data)
327
+
328
+ rv = RegSetValueEx(
329
+ hkey,
330
+ 'ParameterMessageFile',
331
+ 0,
332
+ REG_EXPAND_SZ,
333
+ data,
334
+ data.size
335
+ )
336
+
337
+ if rv != ERROR_SUCCESS
338
+ raise SystemCallError.new('RegSetValueEx', rv)
339
+ end
340
+ end
341
+
342
+ data = FFI::MemoryPointer.new(:ulong).write_ulong(hash['supported_types'])
343
+
344
+ rv = RegSetValueEx(
345
+ hkey,
346
+ 'TypesSupported',
347
+ 0,
348
+ REG_DWORD,
349
+ data,
350
+ data.size
351
+ )
352
+
353
+ if rv != ERROR_SUCCESS
354
+ raise SystemCallError.new('RegSetValueEx', rv)
355
+ end
356
+ ensure
357
+ RegCloseKey(hkey)
358
+ end
359
+
360
+ disposition.read_ulong
361
+ end
362
+
363
+ # Backs up the event log to +file+. Note that you cannot backup to
364
+ # a file that already exists or a Error will be raised.
365
+ #
366
+ def backup(file)
367
+ raise TypeError unless file.is_a?(String)
368
+ unless BackupEventLog(@handle, file)
369
+ raise SystemCallError.new('BackupEventLog', FFI.errno)
370
+ end
371
+ end
372
+
373
+ # Clears the EventLog. If +backup_file+ is provided, it backs up the
374
+ # event log to that file first.
375
+ #
376
+ def clear(backup_file = nil)
377
+ raise TypeError unless backup_file.is_a?(String) if backup_file
378
+
379
+ unless ClearEventLog(@handle, backup_file)
380
+ raise SystemCallError.new('ClearEventLog', FFI.errno)
381
+ end
382
+ end
383
+
384
+ # Closes the EventLog handle. The handle is automatically closed for you
385
+ # if you use the block form of EventLog.new.
386
+ #
387
+ def close
388
+ CloseEventLog(@handle)
389
+ end
390
+
391
+ # Indicates whether or not the event log is full.
392
+ #
393
+ def full?
394
+ ptr = FFI::MemoryPointer.new(:ulong, 1)
395
+ needed = FFI::MemoryPointer.new(:ulong)
396
+
397
+ unless GetEventLogInformation(@handle, 0, ptr, ptr.size, needed)
398
+ raise SystemCallError.new('GetEventLogInformation', FFI.errno)
399
+ end
400
+
401
+ ptr.read_ulong != 0
402
+ end
403
+
404
+ # Returns the absolute record number of the oldest record. Note that
405
+ # this is not guaranteed to be 1 because event log records can be
406
+ # overwritten.
407
+ #
408
+ def oldest_record_number
409
+ rec = FFI::MemoryPointer.new(:ulong)
410
+
411
+ unless GetOldestEventLogRecord(@handle, rec)
412
+ raise SystemCallError.new('GetOldestEventLogRecord', FFI.errno)
413
+ end
414
+
415
+ rec.read_ulong
416
+ end
417
+
418
+ # Returns the total number of records for the given event log.
419
+ #
420
+ def total_records
421
+ total = FFI::MemoryPointer.new(:ulong)
422
+
423
+ unless GetNumberOfEventLogRecords(@handle, total)
424
+ raise SystemCallError.new('GetNumberOfEventLogRecords', FFI.errno)
425
+ end
426
+
427
+ total.read_ulong
428
+ end
429
+
430
+ # Yields an EventLogStruct every time a record is written to the event
431
+ # log. Unlike EventLog#tail, this method breaks out of the block after
432
+ # the event.
433
+ #
434
+ # Raises an Error if no block is provided.
435
+ #
436
+ def notify_change(&block)
437
+ unless block_given?
438
+ raise ArgumentError, 'block missing for notify_change'
439
+ end
440
+
441
+ # Reopen the handle because the NotifyChangeEventLog() function will
442
+ # choke after five or six reads otherwise.
443
+ @handle = OpenEventLog(@server, @source)
444
+
445
+ if @handle == 0
446
+ raise SystemCallError.new('OpenEventLog', FFI.errno)
447
+ end
448
+
449
+ event = CreateEvent(nil, false, false, nil)
450
+
451
+ unless NotifyChangeEventLog(@handle, event)
452
+ raise SystemCallError.new('NotifyChangeEventLog', FFI.errno)
453
+ end
454
+
455
+ wait_result = WaitForSingleObject(event, INFINITE)
456
+
457
+ begin
458
+ if wait_result == WAIT_FAILED
459
+ raise SystemCallError.new('WaitForSingleObject', FFI.errno)
460
+ else
461
+ last = read_last_event
462
+ block.call(last)
463
+ end
464
+ ensure
465
+ CloseHandle(event)
466
+ end
467
+
468
+ self
469
+ end
470
+
471
+ # Yields an EventLogStruct every time a record is written to the event
472
+ # log, once every +frequency+ seconds. Unlike EventLog#notify_change,
473
+ # this method does not break out of the block after the event. The read
474
+ # +frequency+ is set to 5 seconds by default.
475
+ #
476
+ # Raises an Error if no block is provided.
477
+ #
478
+ # The delay between reads is due to the nature of the Windows event log.
479
+ # It is not really designed to be tailed in the manner of a Unix syslog,
480
+ # for example, in that not nearly as many events are typically recorded.
481
+ # It's just not designed to be polled that heavily.
482
+ #
483
+ def tail(frequency = 5)
484
+ unless block_given?
485
+ raise ArgumentError, 'block missing for tail'
486
+ end
487
+
488
+ old_total = total_records()
489
+ flags = FORWARDS_READ | SEEK_READ
490
+ rec_num = read_last_event.record_number
491
+
492
+ while true
493
+ new_total = total_records()
494
+ if new_total != old_total
495
+ rec_num = oldest_record_number() if full?
496
+ read(flags, rec_num).each{ |log| yield log }
497
+ old_total = new_total
498
+ rec_num = read_last_event.record_number + 1
499
+ end
500
+ sleep frequency
501
+ end
502
+ end
503
+
504
+ # Iterates over each record in the event log, yielding a EventLogStruct
505
+ # for each record. The offset value is only used when used in
506
+ # conjunction with the EventLog::SEEK_READ flag. Otherwise, it is
507
+ # ignored. If no flags are specified, then the default flags are:
508
+ #
509
+ # EventLog::SEQUENTIAL_READ | EventLog::FORWARDS_READ
510
+ #
511
+ # Note that, if you're performing a SEEK_READ, then the offset must
512
+ # refer to a record number that actually exists. The default of 0
513
+ # may or may not work for your particular event log.
514
+ #
515
+ # The EventLogStruct struct contains the following members:
516
+ #
517
+ # * record_number # Fixnum
518
+ # * time_generated # Time
519
+ # * time_written # Time
520
+ # * event_id # Fixnum
521
+ # * event_type # String
522
+ # * category # String
523
+ # * source # String
524
+ # * computer # String
525
+ # * user # String or nil
526
+ # * description # String or nil
527
+ # * string_inserts # An array of Strings or nil
528
+ #
529
+ # If no block is given the method returns an array of EventLogStruct's.
530
+ #
531
+ def read(flags = nil, offset = 0)
532
+ buf = FFI::MemoryPointer.new(:char, BUFFER_SIZE)
533
+ read = FFI::MemoryPointer.new(:ulong)
534
+ needed = FFI::MemoryPointer.new(:ulong)
535
+ array = []
536
+ lkey = HKEY_LOCAL_MACHINE
537
+
538
+ unless flags
539
+ flags = FORWARDS_READ | SEQUENTIAL_READ
540
+ end
541
+
542
+ if @server
543
+ hkey = FFI::MemoryPointer.new(:uintptr_t)
544
+ if RegConnectRegistry(@server, HKEY_LOCAL_MACHINE, hkey) != 0
545
+ raise SystemCallError.new('RegConnectRegistry', FFI.errno)
546
+ end
547
+ lkey = hkey.read_pointer.to_i
548
+ end
549
+
550
+ while ReadEventLog(@handle, flags, offset, buf, buf.size, read, needed) ||
551
+ FFI.errno == ERROR_INSUFFICIENT_BUFFER
552
+
553
+ if FFI.errno == ERROR_INSUFFICIENT_BUFFER
554
+ needed = needed.read_ulong / EVENTLOGRECORD.size
555
+ buf = FFI::MemoryPointer.new(EVENTLOGRECORD, needed)
556
+ unless ReadEventLog(@handle, flags, offset, buf, buf.size, read, needed)
557
+ raise SystemCallError.new('ReadEventLog', FFI.errno)
558
+ end
559
+ end
560
+
561
+ dwread = read.read_ulong
562
+
563
+ while dwread > 0
564
+ struct = EventLogStruct.new
565
+ record = EVENTLOGRECORD.new(buf)
566
+
567
+ event_source = buf.read_bytes(buf.size)[56..-1][/^[^\0]*/]
568
+ computer = buf.read_bytes(buf.size)[56 + event_source.length + 1..-1][/^[^\0]*/]
569
+ user = get_user(record)
570
+
571
+ strings, desc = get_description(buf, event_source, lkey)
572
+
573
+ struct.source = event_source
574
+ struct.computer = computer
575
+ struct.record_number = record[:RecordNumber]
576
+ struct.time_generated = Time.at(record[:TimeGenerated])
577
+ struct.time_written = Time.at(record[:TimeWritten])
578
+ struct.event_id = record[:EventID] & 0x0000FFFF
579
+ struct.event_type = get_event_type(record[:EventType])
580
+ struct.user = user
581
+ struct.category = record[:EventCategory]
582
+ struct.string_inserts = strings
583
+ struct.description = desc
584
+
585
+ struct.freeze # This is read-only information
586
+
587
+ if block_given?
588
+ yield struct
589
+ else
590
+ array.push(struct)
591
+ end
592
+
593
+ if flags & EVENTLOG_BACKWARDS_READ > 0
594
+ offset = record[:RecordNumber] - 1
595
+ else
596
+ offset = record[:RecordNumber] + 1
597
+ end
598
+
599
+ length = record[:Length]
600
+
601
+ dwread -= length
602
+ buf += length
603
+ end
604
+
605
+ buf = FFI::MemoryPointer.new(:char, BUFFER_SIZE)
606
+ end
607
+
608
+ block_given? ? nil : array
609
+ end
610
+
611
+ # This class method is nearly identical to the EventLog#read instance
612
+ # method, except that it takes a +source+ and +server+ as the first two
613
+ # arguments.
614
+ #
615
+ def self.read(source='Application', server=nil, flags=nil, offset=0)
616
+ self.new(source, server){ |log|
617
+ if block_given?
618
+ log.read(flags, offset){ |els| yield els }
619
+ else
620
+ return log.read(flags, offset)
621
+ end
622
+ }
623
+ end
624
+
625
+ # Writes an event to the event log. The following are valid keys:
626
+ #
627
+ # * source # Event log source name. Defaults to "Application".
628
+ # * event_id # Event ID (defined in event message file).
629
+ # * category # Event category (defined in category message file).
630
+ # * data # String, or array of strings, that is written to the log.
631
+ # * event_type # Type of event, e.g. EventLog::ERROR_TYPE, etc.
632
+ #
633
+ # The +event_type+ keyword is the only mandatory keyword. The others are
634
+ # optional. Although the +source+ defaults to "Application", I
635
+ # recommend that you create an application specific event source and use
636
+ # that instead. See the 'EventLog.add_event_source' method for more
637
+ # details.
638
+ #
639
+ # The +event_id+ and +category+ values are defined in the message
640
+ # file(s) that you created for your application. See the tutorial.txt
641
+ # file for more details on how to create a message file.
642
+ #
643
+ # An ArgumentError is raised if you attempt to use an invalid key.
644
+ #
645
+ def report_event(args)
646
+ raise TypeError unless args.is_a?(Hash)
647
+
648
+ valid_keys = %w[source event_id category data event_type]
649
+ num_strings = 0
650
+
651
+ # Default values
652
+ hash = {
653
+ 'source' => @source,
654
+ 'event_id' => 0,
655
+ 'category' => 0,
656
+ 'data' => 0
657
+ }
658
+
659
+ # Validate the keys, and convert symbols and case to lowercase strings.
660
+ args.each{ |key, val|
661
+ key = key.to_s.downcase
662
+ unless valid_keys.include?(key)
663
+ raise ArgumentError, "invalid key '#{key}'"
664
+ end
665
+ hash[key] = val
666
+ }
667
+
668
+ # The event_type must be specified
669
+ unless hash['event_type']
670
+ raise ArgumentError, 'no event_type specified'
671
+ end
672
+
673
+ handle = RegisterEventSource(@server, hash['source'])
674
+
675
+ if handle == 0
676
+ raise SystemCallError.new('RegisterEventSource', FFI.errno)
677
+ end
678
+
679
+ if hash['data'].is_a?(String)
680
+ strptrs = []
681
+ strptrs << FFI::MemoryPointer.from_string(hash['data'])
682
+ strptrs << nil
683
+
684
+ data = FFI::MemoryPointer.new(:pointer, strptrs.size)
685
+
686
+ strptrs.each_with_index do |p, i|
687
+ data[i].put_pointer(0, p)
688
+ end
689
+
690
+ num_strings = 1
691
+ elsif hash['data'].is_a?(Array)
692
+ strptrs = []
693
+
694
+ hash['data'].each{ |str|
695
+ strptrs << FFI::MemoryPointer.from_string(str)
696
+ }
697
+
698
+ strptrs << nil
699
+ data = FFI::MemoryPointer.new(:pointer, strptrs.size)
700
+
701
+ strptrs.each_with_index do |p, i|
702
+ data[i].put_pointer(0, p)
703
+ end
704
+
705
+ num_strings = hash['data'].size
706
+ else
707
+ data = nil
708
+ num_strings = 0
709
+ end
710
+
711
+ bool = ReportEvent(
712
+ handle,
713
+ hash['event_type'],
714
+ hash['category'],
715
+ hash['event_id'],
716
+ nil,
717
+ num_strings,
718
+ 0,
719
+ data,
720
+ nil
721
+ )
722
+
723
+ unless bool
724
+ raise SystemCallError.new('ReportEvent', FFI.errno)
725
+ end
726
+ end
727
+
728
+ alias :write :report_event
729
+
730
+ private
731
+
732
+ # A private method that reads the last event log record.
733
+ #
734
+ def read_last_event(handle=@handle, source=@source, server=@server)
735
+ buf = FFI::MemoryPointer.new(:char, BUFFER_SIZE)
736
+ read = FFI::MemoryPointer.new(:ulong)
737
+ needed = FFI::MemoryPointer.new(:ulong)
738
+ lkey = HKEY_LOCAL_MACHINE
739
+
740
+ flags = EVENTLOG_BACKWARDS_READ | EVENTLOG_SEQUENTIAL_READ
741
+
742
+ unless ReadEventLog(@handle, flags, 0, buf, buf.size, read, needed)
743
+ if FFI.errno == ERROR_INSUFFICIENT_BUFFER
744
+ needed = needed.read_ulong / EVENTLOGRECORD.size
745
+ buf = FFI::MemoryPointer.new(EVENTLOGRECORD, needed)
746
+ unless ReadEventLog(@handle, flags, 0, buf, buf.size, read, needed)
747
+ raise SystemCallError.new('ReadEventLog', FFI.errno)
748
+ end
749
+ else
750
+ raise SystemCallError.new('ReadEventLog', FFI.errno)
751
+ end
752
+ end
753
+
754
+ if @server
755
+ hkey = FFI::MemoryPointer.new(:uintptr_t)
756
+ if RegConnectRegistry(@server, HKEY_LOCAL_MACHINE, hkey) != 0
757
+ raise SystemCallError.new('RegConnectRegistry', FFI.errno)
758
+ end
759
+ lkey = hkey.read_pointer.to_i
760
+ end
761
+
762
+ record = EVENTLOGRECORD.new(buf)
763
+
764
+ event_source = buf.read_bytes(buf.size)[56..-1][/^[^\0]*/]
765
+ computer = buf.read_bytes(buf.size)[56 + event_source.length + 1..-1][/^[^\0]*/]
766
+ event_type = get_event_type(record[:EventType])
767
+ user = get_user(record)
768
+ strings, desc = get_description(buf, event_source, lkey)
769
+
770
+ struct = EventLogStruct.new
771
+ struct.source = event_source
772
+ struct.computer = computer
773
+ struct.record_number = record[:RecordNumber]
774
+ struct.time_generated = Time.at(record[:TimeGenerated])
775
+ struct.time_written = Time.at(record[:TimeWritten])
776
+ struct.event_id = record[:EventID] & 0x0000FFFF
777
+ struct.event_type = event_type
778
+ struct.user = user
779
+ struct.category = record[:EventCategory]
780
+ struct.string_inserts = strings
781
+ struct.description = desc
782
+
783
+ struct.freeze # This is read-only information
784
+
785
+ struct
786
+ end
787
+
788
+ # Private method that retrieves the user name based on data in the
789
+ # EVENTLOGRECORD buffer.
790
+ #
791
+ def get_user(rec)
792
+ return nil if rec[:UserSidLength] <= 0
793
+
794
+ name = FFI::MemoryPointer.new(:char, MAX_SIZE)
795
+ domain = FFI::MemoryPointer.new(:char, MAX_SIZE)
796
+ snu = FFI::MemoryPointer.new(:int)
797
+
798
+ name_size = FFI::MemoryPointer.new(:ulong)
799
+ domain_size = FFI::MemoryPointer.new(:ulong)
800
+
801
+ name_size.write_ulong(name.size)
802
+ domain_size.write_ulong(domain.size)
803
+
804
+ offset = rec[:UserSidOffset]
805
+
806
+ val = LookupAccountSid(
807
+ @server,
808
+ rec.pointer + offset,
809
+ name,
810
+ name_size,
811
+ domain,
812
+ domain_size,
813
+ snu
814
+ )
815
+
816
+ # Return nil if the lookup failed
817
+ return val ? name.read_string : nil
818
+ end
819
+
820
+ # Private method that converts a numeric event type into a human
821
+ # readable string.
822
+ #
823
+ def get_event_type(event)
824
+ case event
825
+ when EVENTLOG_ERROR_TYPE
826
+ 'error'
827
+ when EVENTLOG_WARNING_TYPE
828
+ 'warning'
829
+ when EVENTLOG_INFORMATION_TYPE, EVENTLOG_SUCCESS
830
+ 'information'
831
+ when EVENTLOG_AUDIT_SUCCESS
832
+ 'audit_success'
833
+ when EVENTLOG_AUDIT_FAILURE
834
+ 'audit_failure'
835
+ else
836
+ nil
837
+ end
838
+ end
839
+
840
+ # Private method that gets the string inserts (Array) and the full
841
+ # event description (String) based on data from the EVENTLOGRECORD
842
+ # buffer.
843
+ #
844
+ def get_description(buf, event_source, lkey)
845
+ rec = EVENTLOGRECORD.new(buf)
846
+ str = buf.read_bytes(buf.size)[rec[:StringOffset] .. -1]
847
+ num = rec[:NumStrings]
848
+ hkey = FFI::MemoryPointer.new(:uintptr_t)
849
+ key = BASE_KEY + "#{@source}\\#{event_source}"
850
+ buf = FFI::MemoryPointer.new(:char, 8192)
851
+ va_list = va_list0 = (num == 0) ? [] : str.unpack('Z*' * num)
852
+
853
+ begin
854
+ old_wow_val = FFI::MemoryPointer.new(:int)
855
+ Wow64DisableWow64FsRedirection(old_wow_val)
856
+
857
+ param_exe = nil
858
+ message_exe = nil
859
+
860
+ if RegOpenKeyEx(lkey, key, 0, KEY_READ, hkey) == 0
861
+ hkey = hkey.read_pointer.to_i
862
+ value = 'providerGuid'
863
+
864
+ guid = FFI::MemoryPointer.new(:char, MAX_SIZE)
865
+ size = FFI::MemoryPointer.new(:ulong)
866
+
867
+ size.write_ulong(guid.size)
868
+
869
+ if RegQueryValueEx(hkey, value, nil, nil, guid, size) == 0
870
+ guid = guid.read_string
871
+ hkey2 = FFI::MemoryPointer.new(:uintptr_t)
872
+ key = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WINEVT\\Publishers\\#{guid}"
873
+
874
+ if RegOpenKeyEx(lkey, key, 0, KEY_READ|0x100, hkey2) == 0
875
+ hkey2 = hkey2.read_pointer.to_i
876
+
877
+ value = 'ParameterMessageFile'
878
+ file = FFI::MemoryPointer.new(:char, MAX_SIZE)
879
+ size = FFI::MemoryPointer.new(:ulong)
880
+
881
+ size.write_ulong(file.size)
882
+
883
+ if RegQueryValueEx(hkey2, value, nil, nil, file, size) == 0
884
+ file = file.read_string
885
+ exe = FFI::MemoryPointer.new(:char, MAX_SIZE)
886
+ ExpandEnvironmentStrings(file, exe, exe.size)
887
+ param_exe = exe.read_string
888
+ end
889
+
890
+ value = 'MessageFileName'
891
+ file = FFI::MemoryPointer.new(:char, MAX_SIZE)
892
+ size = FFI::MemoryPointer.new(:ulong)
893
+
894
+ size.write_ulong(file.size)
895
+
896
+ if RegQueryValueEx(hkey2, value, nil, nil, file, size) == 0
897
+ file = file.read_string
898
+ exe = FFI::MemoryPointer.new(:char, MAX_SIZE)
899
+ ExpandEnvironmentStrings(file, exe, exe.size)
900
+ message_exe = exe.read_string
901
+ end
902
+
903
+ RegCloseKey(hkey2)
904
+ end
905
+ else
906
+ value = 'ParameterMessageFile'
907
+ file = FFI::MemoryPointer.new(:char, MAX_SIZE)
908
+ size = FFI::MemoryPointer.new(:ulong)
909
+
910
+ size.write_ulong(file.size)
911
+
912
+ if RegQueryValueEx(hkey, value, nil, nil, file, size) == 0
913
+ file = file.read_string
914
+ exe = FFI::MemoryPointer.new(:char, MAX_SIZE)
915
+ ExpandEnvironmentStrings(file, exe, exe.size)
916
+ param_exe = exe.read_string
917
+ end
918
+
919
+ value = 'EventMessageFile'
920
+ file = FFI::MemoryPointer.new(:char, MAX_SIZE)
921
+ size = FFI::MemoryPointer.new(:ulong)
922
+
923
+ size.write_ulong(file.size)
924
+
925
+ if RegQueryValueEx(hkey, value, nil, nil, file, size) == 0
926
+ file = file.read_string
927
+ exe = FFI::MemoryPointer.new(:char, MAX_SIZE)
928
+ ExpandEnvironmentStrings(file, exe, exe.size)
929
+ message_exe = exe.read_string
930
+ end
931
+ end
932
+
933
+ RegCloseKey(hkey)
934
+ else
935
+ wevent_source = (event_source + 0.chr).encode('UTF-16LE')
936
+
937
+ begin
938
+ pubMetadata = EvtOpenPublisherMetadata(0, wevent_source, nil, 1024, 0)
939
+
940
+ if pubMetadata > 0
941
+ buf2 = FFI::MemoryPointer.new(:char, 8192)
942
+ val = FFI::MemoryPointer.new(:ulong)
943
+
944
+ bool = EvtGetPublisherMetadataProperty(
945
+ pubMetadata,
946
+ 2, # EvtPublisherMetadataParameterFilePath
947
+ 0,
948
+ buf2.size,
949
+ buf2,
950
+ val
951
+ )
952
+
953
+ unless bool
954
+ raise SystemCallError.new('EvtGetPublisherMetadataProperty', FFI.errno)
955
+ end
956
+
957
+ file = buf2.read_string[16..-1]
958
+ exe = FFI::MemoryPointer.new(:char, MAX_SIZE)
959
+ ExpandEnvironmentStrings(file, exe, exe.size)
960
+ param_exe = exe.read_string
961
+
962
+ buf2 = FFI::MemoryPointer.new(:char, 8192)
963
+ val = FFI::MemoryPointer.new(:ulong)
964
+
965
+ bool = EvtGetPublisherMetadataProperty(
966
+ pubMetadata,
967
+ 3, # EvtPublisherMetadataMessageFilePath
968
+ 0,
969
+ buf2.size,
970
+ buf2,
971
+ val
972
+ )
973
+
974
+ unless bool
975
+ raise SystemCallError.new('EvtGetPublisherMetadataProperty', FFI.errno)
976
+ end
977
+
978
+ file = buf2.read_string[16..-1]
979
+ exe = FFI::MemoryPointer.new(:char, MAX_SIZE)
980
+ ExpandEnvironmentStrings(file, exe, exe.size)
981
+ message_exe = exe.read_string
982
+ end
983
+ ensure
984
+ EvtClose(pubMetadata) if pubMetadata
985
+ end
986
+ end
987
+
988
+ if param_exe != nil
989
+ va_list = va_list0.map{ |v|
990
+ va = v
991
+
992
+ v.scan(/%%(\d+)/).uniq.each{ |x|
993
+ param_exe.split(';').each{ |lfile|
994
+ hmodule = LoadLibraryEx(
995
+ lfile,
996
+ 0,
997
+ DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE
998
+ )
999
+
1000
+ if hmodule != 0
1001
+ res = FormatMessage(
1002
+ FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_ARGUMENT_ARRAY,
1003
+ hmodule,
1004
+ x.first.to_i,
1005
+ 0,
1006
+ buf,
1007
+ buf.size,
1008
+ v
1009
+ )
1010
+
1011
+ if res == 0
1012
+ event_id = 0xB0000000 | event_id
1013
+ res = FormatMessage(
1014
+ FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS,
1015
+ hmodule,
1016
+ event_id,
1017
+ 0,
1018
+ buf,
1019
+ buf.size,
1020
+ nil
1021
+ )
1022
+ end
1023
+
1024
+ FreeLibrary(hmodule)
1025
+ break if buf.nstrip != ""
1026
+ end
1027
+ }
1028
+
1029
+ va = va.gsub("%%#{x.first}", buf.nstrip)
1030
+ }
1031
+
1032
+ va
1033
+ }
1034
+ end
1035
+
1036
+ if message_exe != nil
1037
+ buf = FFI::MemoryPointer.new(:char, 8192) # Reset the buffer
1038
+
1039
+ # Try to retrieve message *without* expanding the inserts yet
1040
+ message_exe.split(';').each{ |lfile|
1041
+ hmodule = LoadLibraryEx(
1042
+ lfile,
1043
+ 0,
1044
+ DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE
1045
+ )
1046
+
1047
+ event_id = rec[:EventID]
1048
+
1049
+ if hmodule != 0
1050
+ res = FormatMessage(
1051
+ FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS,
1052
+ hmodule,
1053
+ event_id,
1054
+ 0,
1055
+ buf,
1056
+ buf.size,
1057
+ nil
1058
+ )
1059
+
1060
+ if res == 0
1061
+ event_id = 0xB0000000 | event_id
1062
+
1063
+ res = FormatMessage(
1064
+ FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS,
1065
+ hmodule,
1066
+ event_id,
1067
+ 0,
1068
+ buf,
1069
+ buf.size,
1070
+ nil
1071
+ )
1072
+ end
1073
+
1074
+ FreeLibrary(hmodule)
1075
+ break if buf.read_string != "" # All messages read
1076
+ end
1077
+ }
1078
+
1079
+ # Determine higest %n insert number
1080
+ max_insert = [num, buf.read_string.scan(/%(\d+)/).map{ |x| x[0].to_i }.max].compact.max
1081
+
1082
+ # Insert dummy strings not provided by caller
1083
+ ((num+1)..(max_insert)).each{ |x| va_list.push("%#{x}") }
1084
+
1085
+ if num == 0
1086
+ va_list_ptr = FFI::MemoryPointer.new(:pointer)
1087
+ else
1088
+ strptrs = []
1089
+ va_list.each{ |x| strptrs << FFI::MemoryPointer.from_string(x) }
1090
+ strptrs << nil
1091
+
1092
+ va_list_ptr = FFI::MemoryPointer.new(:pointer, strptrs.size)
1093
+
1094
+ strptrs.each_with_index{ |p, i|
1095
+ va_list_ptr[i].put_pointer(0, p)
1096
+ }
1097
+ end
1098
+
1099
+ message_exe.split(';').each{ |lfile|
1100
+ hmodule = LoadLibraryEx(
1101
+ lfile,
1102
+ 0,
1103
+ DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE
1104
+ )
1105
+
1106
+ event_id = rec[:EventID]
1107
+
1108
+ if hmodule != 0
1109
+ res = FormatMessage(
1110
+ FORMAT_MESSAGE_FROM_HMODULE |
1111
+ FORMAT_MESSAGE_ARGUMENT_ARRAY,
1112
+ hmodule,
1113
+ event_id,
1114
+ 0,
1115
+ buf,
1116
+ buf.size,
1117
+ va_list_ptr
1118
+ )
1119
+
1120
+ if res == 0
1121
+ event_id = 0xB0000000 | event_id
1122
+
1123
+ res = FormatMessage(
1124
+ FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_ARGUMENT_ARRAY,
1125
+ hmodule,
1126
+ event_id,
1127
+ 0,
1128
+ buf,
1129
+ buf.size,
1130
+ va_list_ptr
1131
+ )
1132
+ end
1133
+
1134
+ FreeLibrary(hmodule)
1135
+ break if buf.read_string != "" # All messages read
1136
+ end
1137
+ }
1138
+ end
1139
+ ensure
1140
+ Wow64RevertWow64FsRedirection(old_wow_val.read_ulong)
1141
+ end
1142
+
1143
+ [va_list0, buf.read_string]
1144
+ end
1145
+ end
1146
+ end