win32-eventlog 0.5.3 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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