win32-eventlog 0.6.3 → 0.6.4

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..."
@@ -0,0 +1 @@
1
+ require_relative 'win32/eventlog'
@@ -1,1139 +1,1139 @@
1
- require_relative 'windows/constants'
2
- require_relative 'windows/structs'
3
- require_relative '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.3'
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
- struct.source = buf.read_bytes(buf.size)[56..-1][/^[^\0]*/]
568
- struct.computer = buf.read_bytes(buf.size)[56 + struct.source.length + 1..-1][/^[^\0]*/]
569
- struct.record_number = record[:RecordNumber]
570
- struct.time_generated = Time.at(record[:TimeGenerated])
571
- struct.time_written = Time.at(record[:TimeWritten])
572
- struct.event_id = record[:EventID] & 0x0000FFFF
573
- struct.event_type = get_event_type(record[:EventType])
574
- struct.user = get_user(record)
575
- struct.category = record[:EventCategory]
576
- struct.string_inserts, struct.description = get_description(buf, struct.source, lkey)
577
-
578
- struct.freeze # This is read-only information
579
-
580
- if block_given?
581
- yield struct
582
- else
583
- array.push(struct)
584
- end
585
-
586
- if flags & EVENTLOG_BACKWARDS_READ > 0
587
- offset = record[:RecordNumber] - 1
588
- else
589
- offset = record[:RecordNumber] + 1
590
- end
591
-
592
- length = record[:Length]
593
-
594
- dwread -= length
595
- buf += length
596
- end
597
-
598
- buf = FFI::MemoryPointer.new(:char, BUFFER_SIZE)
599
- end
600
-
601
- block_given? ? nil : array
602
- end
603
-
604
- # This class method is nearly identical to the EventLog#read instance
605
- # method, except that it takes a +source+ and +server+ as the first two
606
- # arguments.
607
- #
608
- def self.read(source='Application', server=nil, flags=nil, offset=0)
609
- self.new(source, server){ |log|
610
- if block_given?
611
- log.read(flags, offset){ |els| yield els }
612
- else
613
- return log.read(flags, offset)
614
- end
615
- }
616
- end
617
-
618
- # Writes an event to the event log. The following are valid keys:
619
- #
620
- # * source # Event log source name. Defaults to "Application".
621
- # * event_id # Event ID (defined in event message file).
622
- # * category # Event category (defined in category message file).
623
- # * data # String, or array of strings, that is written to the log.
624
- # * event_type # Type of event, e.g. EventLog::ERROR_TYPE, etc.
625
- #
626
- # The +event_type+ keyword is the only mandatory keyword. The others are
627
- # optional. Although the +source+ defaults to "Application", I
628
- # recommend that you create an application specific event source and use
629
- # that instead. See the 'EventLog.add_event_source' method for more
630
- # details.
631
- #
632
- # The +event_id+ and +category+ values are defined in the message
633
- # file(s) that you created for your application. See the tutorial.txt
634
- # file for more details on how to create a message file.
635
- #
636
- # An ArgumentError is raised if you attempt to use an invalid key.
637
- #
638
- def report_event(args)
639
- raise TypeError unless args.is_a?(Hash)
640
-
641
- valid_keys = %w[source event_id category data event_type]
642
- num_strings = 0
643
-
644
- # Default values
645
- hash = {
646
- 'source' => @source,
647
- 'event_id' => 0,
648
- 'category' => 0,
649
- 'data' => 0
650
- }
651
-
652
- # Validate the keys, and convert symbols and case to lowercase strings.
653
- args.each{ |key, val|
654
- key = key.to_s.downcase
655
- unless valid_keys.include?(key)
656
- raise ArgumentError, "invalid key '#{key}'"
657
- end
658
- hash[key] = val
659
- }
660
-
661
- # The event_type must be specified
662
- unless hash['event_type']
663
- raise ArgumentError, 'no event_type specified'
664
- end
665
-
666
- handle = RegisterEventSource(@server, hash['source'])
667
-
668
- if handle == 0
669
- raise SystemCallError.new('RegisterEventSource', FFI.errno)
670
- end
671
-
672
- if hash['data'].is_a?(String)
673
- strptrs = []
674
- strptrs << FFI::MemoryPointer.from_string(hash['data'])
675
- strptrs << nil
676
-
677
- data = FFI::MemoryPointer.new(:pointer, strptrs.size)
678
-
679
- strptrs.each_with_index do |p, i|
680
- data[i].put_pointer(0, p)
681
- end
682
-
683
- num_strings = 1
684
- elsif hash['data'].is_a?(Array)
685
- strptrs = []
686
-
687
- hash['data'].each{ |str|
688
- strptrs << FFI::MemoryPointer.from_string(str)
689
- }
690
-
691
- strptrs << nil
692
- data = FFI::MemoryPointer.new(:pointer, strptrs.size)
693
-
694
- strptrs.each_with_index do |p, i|
695
- data[i].put_pointer(0, p)
696
- end
697
-
698
- num_strings = hash['data'].size
699
- else
700
- data = nil
701
- num_strings = 0
702
- end
703
-
704
- bool = ReportEvent(
705
- handle,
706
- hash['event_type'],
707
- hash['category'],
708
- hash['event_id'],
709
- nil,
710
- num_strings,
711
- 0,
712
- data,
713
- nil
714
- )
715
-
716
- unless bool
717
- raise SystemCallError.new('ReportEvent', FFI.errno)
718
- end
719
- end
720
-
721
- alias :write :report_event
722
-
723
- # Reads the last event record.
724
- #
725
- def read_last_event
726
- buf = FFI::MemoryPointer.new(:char, BUFFER_SIZE)
727
- read = FFI::MemoryPointer.new(:ulong)
728
- needed = FFI::MemoryPointer.new(:ulong)
729
- lkey = HKEY_LOCAL_MACHINE
730
-
731
- flags = EVENTLOG_BACKWARDS_READ | EVENTLOG_SEQUENTIAL_READ
732
-
733
- unless ReadEventLog(@handle, flags, 0, buf, buf.size, read, needed)
734
- if FFI.errno == ERROR_INSUFFICIENT_BUFFER
735
- needed = needed.read_ulong / EVENTLOGRECORD.size
736
- buf = FFI::MemoryPointer.new(EVENTLOGRECORD, needed)
737
- unless ReadEventLog(@handle, flags, 0, buf, buf.size, read, needed)
738
- raise SystemCallError.new('ReadEventLog', FFI.errno)
739
- end
740
- else
741
- raise SystemCallError.new('ReadEventLog', FFI.errno)
742
- end
743
- end
744
-
745
- if @server
746
- hkey = FFI::MemoryPointer.new(:uintptr_t)
747
- if RegConnectRegistry(@server, HKEY_LOCAL_MACHINE, hkey) != 0
748
- raise SystemCallError.new('RegConnectRegistry', FFI.errno)
749
- end
750
- lkey = hkey.read_pointer.to_i
751
- end
752
-
753
- record = EVENTLOGRECORD.new(buf)
754
-
755
- struct = EventLogStruct.new
756
- struct.source = buf.read_bytes(buf.size)[56..-1][/^[^\0]*/]
757
- struct.computer = buf.read_bytes(buf.size)[56 + struct.source.length + 1..-1][/^[^\0]*/]
758
- struct.record_number = record[:RecordNumber]
759
- struct.time_generated = Time.at(record[:TimeGenerated])
760
- struct.time_written = Time.at(record[:TimeWritten])
761
- struct.event_id = record[:EventID] & 0x0000FFFF
762
- struct.event_type = get_event_type(record[:EventType])
763
- struct.user = get_user(record)
764
- struct.category = record[:EventCategory]
765
- struct.string_inserts, struct.description = get_description(buf, struct.source, lkey)
766
-
767
- struct.freeze # This is read-only information
768
-
769
- struct
770
- end
771
-
772
- private
773
-
774
- # Private method that retrieves the user name based on data in the
775
- # EVENTLOGRECORD buffer.
776
- #
777
- def get_user(rec)
778
- return nil if rec[:UserSidLength] <= 0
779
-
780
- name = FFI::MemoryPointer.new(:char, MAX_SIZE)
781
- domain = FFI::MemoryPointer.new(:char, MAX_SIZE)
782
- snu = FFI::MemoryPointer.new(:int)
783
-
784
- name_size = FFI::MemoryPointer.new(:ulong)
785
- domain_size = FFI::MemoryPointer.new(:ulong)
786
-
787
- name_size.write_ulong(name.size)
788
- domain_size.write_ulong(domain.size)
789
-
790
- offset = rec[:UserSidOffset]
791
-
792
- val = LookupAccountSid(
793
- @server,
794
- rec.pointer + offset,
795
- name,
796
- name_size,
797
- domain,
798
- domain_size,
799
- snu
800
- )
801
-
802
- # Return nil if the lookup failed
803
- return val ? name.read_string : nil
804
- end
805
-
806
- # Private method that converts a numeric event type into a human
807
- # readable string.
808
- #
809
- def get_event_type(event)
810
- case event
811
- when EVENTLOG_ERROR_TYPE
812
- 'error'
813
- when EVENTLOG_WARNING_TYPE
814
- 'warning'
815
- when EVENTLOG_INFORMATION_TYPE, EVENTLOG_SUCCESS
816
- 'information'
817
- when EVENTLOG_AUDIT_SUCCESS
818
- 'audit_success'
819
- when EVENTLOG_AUDIT_FAILURE
820
- 'audit_failure'
821
- else
822
- nil
823
- end
824
- end
825
-
826
- # Private method that gets the string inserts (Array) and the full
827
- # event description (String) based on data from the EVENTLOGRECORD
828
- # buffer.
829
- #
830
- def get_description(buf, event_source, lkey)
831
- rec = EVENTLOGRECORD.new(buf)
832
- str = buf.read_bytes(buf.size)[rec[:StringOffset] .. -1]
833
- num = rec[:NumStrings]
834
- hkey = FFI::MemoryPointer.new(:uintptr_t)
835
- key = BASE_KEY + "#{@source}\\#{event_source}"
836
- buf = FFI::MemoryPointer.new(:char, 8192)
837
- va_list = va_list0 = (num == 0) ? [] : str.unpack('Z*' * num)
838
-
839
- begin
840
- old_wow_val = FFI::MemoryPointer.new(:int)
841
- Wow64DisableWow64FsRedirection(old_wow_val)
842
-
843
- param_exe = nil
844
- message_exe = nil
845
-
846
- if RegOpenKeyEx(lkey, key, 0, KEY_READ, hkey) == 0
847
- hkey = hkey.read_pointer.to_i
848
- value = 'providerGuid'
849
-
850
- guid_ptr = FFI::MemoryPointer.new(:char, MAX_SIZE)
851
- size_ptr = FFI::MemoryPointer.new(:ulong)
852
-
853
- size_ptr.write_ulong(guid_ptr.size)
854
-
855
- if RegQueryValueEx(hkey, value, nil, nil, guid_ptr, size_ptr) == 0
856
- guid = guid_ptr.read_string
857
- hkey2 = FFI::MemoryPointer.new(:uintptr_t)
858
- key = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WINEVT\\Publishers\\#{guid}"
859
-
860
- guid_ptr.free
861
-
862
- if RegOpenKeyEx(lkey, key, 0, KEY_READ|0x100, hkey2) == 0
863
- hkey2 = hkey2.read_pointer.to_i
864
-
865
- value = 'ParameterMessageFile'
866
- file_ptr = FFI::MemoryPointer.new(:char, MAX_SIZE)
867
- size_ptr.clear.write_ulong(file_ptr.size)
868
-
869
- if RegQueryValueEx(hkey2, value, nil, nil, file_ptr, size_ptr) == 0
870
- file = file_ptr.read_string
871
- exe = FFI::MemoryPointer.new(:char, MAX_SIZE)
872
- ExpandEnvironmentStrings(file, exe, exe.size)
873
- param_exe = exe.read_string
874
- end
875
-
876
- value = 'MessageFileName'
877
-
878
- file_ptr.clear
879
- size_ptr.clear.write_ulong(file_ptr.size)
880
-
881
- if RegQueryValueEx(hkey2, value, nil, nil, file_ptr, size_ptr) == 0
882
- file = file_ptr.read_string
883
- exe = FFI::MemoryPointer.new(:char, MAX_SIZE)
884
- ExpandEnvironmentStrings(file, exe, exe.size)
885
- message_exe = exe.read_string
886
- end
887
-
888
- RegCloseKey(hkey2)
889
-
890
- file_ptr.free
891
- size_ptr.free
892
- end
893
- else
894
- value = 'ParameterMessageFile'
895
- file_ptr = FFI::MemoryPointer.new(:char, MAX_SIZE)
896
- size_ptr.clear.write_ulong(file_ptr.size)
897
-
898
- if RegQueryValueEx(hkey, value, nil, nil, file_ptr, size_ptr) == 0
899
- file = file_ptr.read_string
900
- exe = FFI::MemoryPointer.new(:char, MAX_SIZE)
901
- ExpandEnvironmentStrings(file, exe, exe.size)
902
- param_exe = exe.read_string
903
- end
904
-
905
- value = 'EventMessageFile'
906
-
907
- file_ptr.clear
908
- size_ptr.clear.write_ulong(file_ptr.size)
909
-
910
- if RegQueryValueEx(hkey, value, nil, nil, file_ptr, size_ptr) == 0
911
- file = file_ptr.read_string
912
- exe = FFI::MemoryPointer.new(:char, MAX_SIZE)
913
- ExpandEnvironmentStrings(file, exe, exe.size)
914
- message_exe = exe.read_string
915
- end
916
-
917
- file_ptr.free
918
- size_ptr.free
919
- end
920
-
921
- RegCloseKey(hkey)
922
- else
923
- wevent_source = (event_source + 0.chr).encode('UTF-16LE')
924
-
925
- begin
926
- pubMetadata = EvtOpenPublisherMetadata(0, wevent_source, nil, 1024, 0)
927
-
928
- if pubMetadata > 0
929
- buf2 = FFI::MemoryPointer.new(:char, 8192)
930
- val = FFI::MemoryPointer.new(:ulong)
931
-
932
- bool = EvtGetPublisherMetadataProperty(
933
- pubMetadata,
934
- 2, # EvtPublisherMetadataParameterFilePath
935
- 0,
936
- buf2.size,
937
- buf2,
938
- val
939
- )
940
-
941
- unless bool
942
- raise SystemCallError.new('EvtGetPublisherMetadataProperty', FFI.errno)
943
- end
944
-
945
- file = buf2.read_string[16..-1]
946
- exe = FFI::MemoryPointer.new(:char, MAX_SIZE)
947
- ExpandEnvironmentStrings(file, exe, exe.size)
948
- param_exe = exe.read_string
949
-
950
- buf2.clear
951
- val.clear
952
-
953
- bool = EvtGetPublisherMetadataProperty(
954
- pubMetadata,
955
- 3, # EvtPublisherMetadataMessageFilePath
956
- 0,
957
- buf2.size,
958
- buf2,
959
- val
960
- )
961
-
962
- unless bool
963
- raise SystemCallError.new('EvtGetPublisherMetadataProperty', FFI.errno)
964
- end
965
-
966
- exe.clear
967
-
968
- file = buf2.read_string[16..-1]
969
- ExpandEnvironmentStrings(file, exe, exe.size)
970
- message_exe = exe.read_string
971
-
972
- buf2.free
973
- val.free
974
- exe.free
975
- end
976
- ensure
977
- EvtClose(pubMetadata) if pubMetadata
978
- end
979
- end
980
-
981
- if param_exe != nil
982
- va_list = va_list0.map{ |v|
983
- va = v
984
-
985
- v.scan(/%%(\d+)/).uniq.each{ |x|
986
- param_exe.split(';').each{ |lfile|
987
- hmodule = LoadLibraryEx(
988
- lfile,
989
- 0,
990
- DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE
991
- )
992
-
993
- if hmodule != 0
994
- res = FormatMessage(
995
- FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_ARGUMENT_ARRAY,
996
- hmodule,
997
- x.first.to_i,
998
- 0,
999
- buf,
1000
- buf.size,
1001
- v
1002
- )
1003
-
1004
- if res == 0
1005
- event_id = 0xB0000000 | event_id
1006
- res = FormatMessage(
1007
- FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS,
1008
- hmodule,
1009
- event_id,
1010
- 0,
1011
- buf,
1012
- buf.size,
1013
- nil
1014
- )
1015
- end
1016
-
1017
- FreeLibrary(hmodule)
1018
- break if buf.nstrip != ""
1019
- end
1020
- }
1021
-
1022
- va = va.gsub("%%#{x.first}", buf.nstrip)
1023
- }
1024
-
1025
- va
1026
- }
1027
- end
1028
-
1029
- if message_exe != nil
1030
- buf.clear
1031
-
1032
- # Try to retrieve message *without* expanding the inserts yet
1033
- message_exe.split(';').each{ |lfile|
1034
- hmodule = LoadLibraryEx(
1035
- lfile,
1036
- 0,
1037
- DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE
1038
- )
1039
-
1040
- event_id = rec[:EventID]
1041
-
1042
- if hmodule != 0
1043
- res = FormatMessage(
1044
- FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS,
1045
- hmodule,
1046
- event_id,
1047
- 0,
1048
- buf,
1049
- buf.size,
1050
- nil
1051
- )
1052
-
1053
- if res == 0
1054
- event_id = 0xB0000000 | event_id
1055
-
1056
- res = FormatMessage(
1057
- FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS,
1058
- hmodule,
1059
- event_id,
1060
- 0,
1061
- buf,
1062
- buf.size,
1063
- nil
1064
- )
1065
- end
1066
-
1067
- FreeLibrary(hmodule)
1068
- break if buf.read_string != "" # All messages read
1069
- end
1070
- }
1071
-
1072
- # Determine higest %n insert number
1073
- max_insert = [num, buf.read_string.scan(/%(\d+)/).map{ |x| x[0].to_i }.max].compact.max
1074
-
1075
- # Insert dummy strings not provided by caller
1076
- ((num+1)..(max_insert)).each{ |x| va_list.push("%#{x}") }
1077
-
1078
- if num == 0
1079
- va_list_ptr = FFI::MemoryPointer.new(:pointer)
1080
- else
1081
- strptrs = []
1082
- va_list.each{ |x| strptrs << FFI::MemoryPointer.from_string(x) }
1083
- strptrs << nil
1084
-
1085
- va_list_ptr = FFI::MemoryPointer.new(:pointer, strptrs.size)
1086
-
1087
- strptrs.each_with_index{ |p, i|
1088
- va_list_ptr[i].put_pointer(0, p)
1089
- }
1090
- end
1091
-
1092
- message_exe.split(';').each{ |lfile|
1093
- hmodule = LoadLibraryEx(
1094
- lfile,
1095
- 0,
1096
- DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE
1097
- )
1098
-
1099
- event_id = rec[:EventID]
1100
-
1101
- if hmodule != 0
1102
- res = FormatMessage(
1103
- FORMAT_MESSAGE_FROM_HMODULE |
1104
- FORMAT_MESSAGE_ARGUMENT_ARRAY,
1105
- hmodule,
1106
- event_id,
1107
- 0,
1108
- buf,
1109
- buf.size,
1110
- va_list_ptr
1111
- )
1112
-
1113
- if res == 0
1114
- event_id = 0xB0000000 | event_id
1115
-
1116
- res = FormatMessage(
1117
- FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_ARGUMENT_ARRAY,
1118
- hmodule,
1119
- event_id,
1120
- 0,
1121
- buf,
1122
- buf.size,
1123
- va_list_ptr
1124
- )
1125
- end
1126
-
1127
- FreeLibrary(hmodule)
1128
- break if buf.read_string != "" # All messages read
1129
- end
1130
- }
1131
- end
1132
- ensure
1133
- Wow64RevertWow64FsRedirection(old_wow_val.read_ulong)
1134
- end
1135
-
1136
- [va_list0, buf.read_string]
1137
- end
1138
- end
1139
- end
1
+ require_relative 'windows/constants'
2
+ require_relative 'windows/structs'
3
+ require_relative '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.4'
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, 0, 0, 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
+ struct.source = buf.read_bytes(buf.size)[56..-1][/^[^\0]*/]
568
+ struct.computer = buf.read_bytes(buf.size)[56 + struct.source.length + 1..-1][/^[^\0]*/]
569
+ struct.record_number = record[:RecordNumber]
570
+ struct.time_generated = Time.at(record[:TimeGenerated])
571
+ struct.time_written = Time.at(record[:TimeWritten])
572
+ struct.event_id = record[:EventID] & 0x0000FFFF
573
+ struct.event_type = get_event_type(record[:EventType])
574
+ struct.user = get_user(record)
575
+ struct.category = record[:EventCategory]
576
+ struct.string_inserts, struct.description = get_description(buf, struct.source, lkey)
577
+
578
+ struct.freeze # This is read-only information
579
+
580
+ if block_given?
581
+ yield struct
582
+ else
583
+ array.push(struct)
584
+ end
585
+
586
+ if flags & EVENTLOG_BACKWARDS_READ > 0
587
+ offset = record[:RecordNumber] - 1
588
+ else
589
+ offset = record[:RecordNumber] + 1
590
+ end
591
+
592
+ length = record[:Length]
593
+
594
+ dwread -= length
595
+ buf += length
596
+ end
597
+
598
+ buf = FFI::MemoryPointer.new(:char, BUFFER_SIZE)
599
+ end
600
+
601
+ block_given? ? nil : array
602
+ end
603
+
604
+ # This class method is nearly identical to the EventLog#read instance
605
+ # method, except that it takes a +source+ and +server+ as the first two
606
+ # arguments.
607
+ #
608
+ def self.read(source='Application', server=nil, flags=nil, offset=0)
609
+ self.new(source, server){ |log|
610
+ if block_given?
611
+ log.read(flags, offset){ |els| yield els }
612
+ else
613
+ return log.read(flags, offset)
614
+ end
615
+ }
616
+ end
617
+
618
+ # Writes an event to the event log. The following are valid keys:
619
+ #
620
+ # * source # Event log source name. Defaults to "Application".
621
+ # * event_id # Event ID (defined in event message file).
622
+ # * category # Event category (defined in category message file).
623
+ # * data # String, or array of strings, that is written to the log.
624
+ # * event_type # Type of event, e.g. EventLog::ERROR_TYPE, etc.
625
+ #
626
+ # The +event_type+ keyword is the only mandatory keyword. The others are
627
+ # optional. Although the +source+ defaults to "Application", I
628
+ # recommend that you create an application specific event source and use
629
+ # that instead. See the 'EventLog.add_event_source' method for more
630
+ # details.
631
+ #
632
+ # The +event_id+ and +category+ values are defined in the message
633
+ # file(s) that you created for your application. See the tutorial.txt
634
+ # file for more details on how to create a message file.
635
+ #
636
+ # An ArgumentError is raised if you attempt to use an invalid key.
637
+ #
638
+ def report_event(args)
639
+ raise TypeError unless args.is_a?(Hash)
640
+
641
+ valid_keys = %w[source event_id category data event_type]
642
+ num_strings = 0
643
+
644
+ # Default values
645
+ hash = {
646
+ 'source' => @source,
647
+ 'event_id' => 0,
648
+ 'category' => 0,
649
+ 'data' => 0
650
+ }
651
+
652
+ # Validate the keys, and convert symbols and case to lowercase strings.
653
+ args.each{ |key, val|
654
+ key = key.to_s.downcase
655
+ unless valid_keys.include?(key)
656
+ raise ArgumentError, "invalid key '#{key}'"
657
+ end
658
+ hash[key] = val
659
+ }
660
+
661
+ # The event_type must be specified
662
+ unless hash['event_type']
663
+ raise ArgumentError, 'no event_type specified'
664
+ end
665
+
666
+ handle = RegisterEventSource(@server, hash['source'])
667
+
668
+ if handle == 0
669
+ raise SystemCallError.new('RegisterEventSource', FFI.errno)
670
+ end
671
+
672
+ if hash['data'].is_a?(String)
673
+ strptrs = []
674
+ strptrs << FFI::MemoryPointer.from_string(hash['data'])
675
+ strptrs << nil
676
+
677
+ data = FFI::MemoryPointer.new(:pointer, strptrs.size)
678
+
679
+ strptrs.each_with_index do |p, i|
680
+ data[i].put_pointer(0, p)
681
+ end
682
+
683
+ num_strings = 1
684
+ elsif hash['data'].is_a?(Array)
685
+ strptrs = []
686
+
687
+ hash['data'].each{ |str|
688
+ strptrs << FFI::MemoryPointer.from_string(str)
689
+ }
690
+
691
+ strptrs << nil
692
+ data = FFI::MemoryPointer.new(:pointer, strptrs.size)
693
+
694
+ strptrs.each_with_index do |p, i|
695
+ data[i].put_pointer(0, p)
696
+ end
697
+
698
+ num_strings = hash['data'].size
699
+ else
700
+ data = nil
701
+ num_strings = 0
702
+ end
703
+
704
+ bool = ReportEvent(
705
+ handle,
706
+ hash['event_type'],
707
+ hash['category'],
708
+ hash['event_id'],
709
+ nil,
710
+ num_strings,
711
+ 0,
712
+ data,
713
+ nil
714
+ )
715
+
716
+ unless bool
717
+ raise SystemCallError.new('ReportEvent', FFI.errno)
718
+ end
719
+ end
720
+
721
+ alias :write :report_event
722
+
723
+ # Reads the last event record.
724
+ #
725
+ def read_last_event
726
+ buf = FFI::MemoryPointer.new(:char, BUFFER_SIZE)
727
+ read = FFI::MemoryPointer.new(:ulong)
728
+ needed = FFI::MemoryPointer.new(:ulong)
729
+ lkey = HKEY_LOCAL_MACHINE
730
+
731
+ flags = EVENTLOG_BACKWARDS_READ | EVENTLOG_SEQUENTIAL_READ
732
+
733
+ unless ReadEventLog(@handle, flags, 0, buf, buf.size, read, needed)
734
+ if FFI.errno == ERROR_INSUFFICIENT_BUFFER
735
+ needed = needed.read_ulong / EVENTLOGRECORD.size
736
+ buf = FFI::MemoryPointer.new(EVENTLOGRECORD, needed)
737
+ unless ReadEventLog(@handle, flags, 0, buf, buf.size, read, needed)
738
+ raise SystemCallError.new('ReadEventLog', FFI.errno)
739
+ end
740
+ else
741
+ raise SystemCallError.new('ReadEventLog', FFI.errno)
742
+ end
743
+ end
744
+
745
+ if @server
746
+ hkey = FFI::MemoryPointer.new(:uintptr_t)
747
+ if RegConnectRegistry(@server, HKEY_LOCAL_MACHINE, hkey) != 0
748
+ raise SystemCallError.new('RegConnectRegistry', FFI.errno)
749
+ end
750
+ lkey = hkey.read_pointer.to_i
751
+ end
752
+
753
+ record = EVENTLOGRECORD.new(buf)
754
+
755
+ struct = EventLogStruct.new
756
+ struct.source = buf.read_bytes(buf.size)[56..-1][/^[^\0]*/]
757
+ struct.computer = buf.read_bytes(buf.size)[56 + struct.source.length + 1..-1][/^[^\0]*/]
758
+ struct.record_number = record[:RecordNumber]
759
+ struct.time_generated = Time.at(record[:TimeGenerated])
760
+ struct.time_written = Time.at(record[:TimeWritten])
761
+ struct.event_id = record[:EventID] & 0x0000FFFF
762
+ struct.event_type = get_event_type(record[:EventType])
763
+ struct.user = get_user(record)
764
+ struct.category = record[:EventCategory]
765
+ struct.string_inserts, struct.description = get_description(buf, struct.source, lkey)
766
+
767
+ struct.freeze # This is read-only information
768
+
769
+ struct
770
+ end
771
+
772
+ private
773
+
774
+ # Private method that retrieves the user name based on data in the
775
+ # EVENTLOGRECORD buffer.
776
+ #
777
+ def get_user(rec)
778
+ return nil if rec[:UserSidLength] <= 0
779
+
780
+ name = FFI::MemoryPointer.new(:char, MAX_SIZE)
781
+ domain = FFI::MemoryPointer.new(:char, MAX_SIZE)
782
+ snu = FFI::MemoryPointer.new(:int)
783
+
784
+ name_size = FFI::MemoryPointer.new(:ulong)
785
+ domain_size = FFI::MemoryPointer.new(:ulong)
786
+
787
+ name_size.write_ulong(name.size)
788
+ domain_size.write_ulong(domain.size)
789
+
790
+ offset = rec[:UserSidOffset]
791
+
792
+ val = LookupAccountSid(
793
+ @server,
794
+ rec.pointer + offset,
795
+ name,
796
+ name_size,
797
+ domain,
798
+ domain_size,
799
+ snu
800
+ )
801
+
802
+ # Return nil if the lookup failed
803
+ return val ? name.read_string : nil
804
+ end
805
+
806
+ # Private method that converts a numeric event type into a human
807
+ # readable string.
808
+ #
809
+ def get_event_type(event)
810
+ case event
811
+ when EVENTLOG_ERROR_TYPE
812
+ 'error'
813
+ when EVENTLOG_WARNING_TYPE
814
+ 'warning'
815
+ when EVENTLOG_INFORMATION_TYPE, EVENTLOG_SUCCESS
816
+ 'information'
817
+ when EVENTLOG_AUDIT_SUCCESS
818
+ 'audit_success'
819
+ when EVENTLOG_AUDIT_FAILURE
820
+ 'audit_failure'
821
+ else
822
+ nil
823
+ end
824
+ end
825
+
826
+ # Private method that gets the string inserts (Array) and the full
827
+ # event description (String) based on data from the EVENTLOGRECORD
828
+ # buffer.
829
+ #
830
+ def get_description(buf, event_source, lkey)
831
+ rec = EVENTLOGRECORD.new(buf)
832
+ str = buf.read_bytes(buf.size)[rec[:StringOffset] .. -1]
833
+ num = rec[:NumStrings]
834
+ hkey = FFI::MemoryPointer.new(:uintptr_t)
835
+ key = BASE_KEY + "#{@source}\\#{event_source}"
836
+ buf = FFI::MemoryPointer.new(:char, 8192)
837
+ va_list = va_list0 = (num == 0) ? [] : str.unpack('Z*' * num)
838
+
839
+ begin
840
+ old_wow_val = FFI::MemoryPointer.new(:int)
841
+ Wow64DisableWow64FsRedirection(old_wow_val)
842
+
843
+ param_exe = nil
844
+ message_exe = nil
845
+
846
+ if RegOpenKeyEx(lkey, key, 0, KEY_READ, hkey) == 0
847
+ hkey = hkey.read_pointer.to_i
848
+ value = 'providerGuid'
849
+
850
+ guid_ptr = FFI::MemoryPointer.new(:char, MAX_SIZE)
851
+ size_ptr = FFI::MemoryPointer.new(:ulong)
852
+
853
+ size_ptr.write_ulong(guid_ptr.size)
854
+
855
+ if RegQueryValueEx(hkey, value, nil, nil, guid_ptr, size_ptr) == 0
856
+ guid = guid_ptr.read_string
857
+ hkey2 = FFI::MemoryPointer.new(:uintptr_t)
858
+ key = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WINEVT\\Publishers\\#{guid}"
859
+
860
+ guid_ptr.free
861
+
862
+ if RegOpenKeyEx(lkey, key, 0, KEY_READ|0x100, hkey2) == 0
863
+ hkey2 = hkey2.read_pointer.to_i
864
+
865
+ value = 'ParameterMessageFile'
866
+ file_ptr = FFI::MemoryPointer.new(:char, MAX_SIZE)
867
+ size_ptr.clear.write_ulong(file_ptr.size)
868
+
869
+ if RegQueryValueEx(hkey2, value, nil, nil, file_ptr, size_ptr) == 0
870
+ file = file_ptr.read_string
871
+ exe = FFI::MemoryPointer.new(:char, MAX_SIZE)
872
+ ExpandEnvironmentStrings(file, exe, exe.size)
873
+ param_exe = exe.read_string
874
+ end
875
+
876
+ value = 'MessageFileName'
877
+
878
+ file_ptr.clear
879
+ size_ptr.clear.write_ulong(file_ptr.size)
880
+
881
+ if RegQueryValueEx(hkey2, value, nil, nil, file_ptr, size_ptr) == 0
882
+ file = file_ptr.read_string
883
+ exe = FFI::MemoryPointer.new(:char, MAX_SIZE)
884
+ ExpandEnvironmentStrings(file, exe, exe.size)
885
+ message_exe = exe.read_string
886
+ end
887
+
888
+ RegCloseKey(hkey2)
889
+
890
+ file_ptr.free
891
+ size_ptr.free
892
+ end
893
+ else
894
+ value = 'ParameterMessageFile'
895
+ file_ptr = FFI::MemoryPointer.new(:char, MAX_SIZE)
896
+ size_ptr.clear.write_ulong(file_ptr.size)
897
+
898
+ if RegQueryValueEx(hkey, value, nil, nil, file_ptr, size_ptr) == 0
899
+ file = file_ptr.read_string
900
+ exe = FFI::MemoryPointer.new(:char, MAX_SIZE)
901
+ ExpandEnvironmentStrings(file, exe, exe.size)
902
+ param_exe = exe.read_string
903
+ end
904
+
905
+ value = 'EventMessageFile'
906
+
907
+ file_ptr.clear
908
+ size_ptr.clear.write_ulong(file_ptr.size)
909
+
910
+ if RegQueryValueEx(hkey, value, nil, nil, file_ptr, size_ptr) == 0
911
+ file = file_ptr.read_string
912
+ exe = FFI::MemoryPointer.new(:char, MAX_SIZE)
913
+ ExpandEnvironmentStrings(file, exe, exe.size)
914
+ message_exe = exe.read_string
915
+ end
916
+
917
+ file_ptr.free
918
+ size_ptr.free
919
+ end
920
+
921
+ RegCloseKey(hkey)
922
+ else
923
+ wevent_source = (event_source + 0.chr).encode('UTF-16LE')
924
+
925
+ begin
926
+ pubMetadata = EvtOpenPublisherMetadata(0, wevent_source, nil, 1024, 0)
927
+
928
+ if pubMetadata > 0
929
+ buf2 = FFI::MemoryPointer.new(:char, 8192)
930
+ val = FFI::MemoryPointer.new(:ulong)
931
+
932
+ bool = EvtGetPublisherMetadataProperty(
933
+ pubMetadata,
934
+ 2, # EvtPublisherMetadataParameterFilePath
935
+ 0,
936
+ buf2.size,
937
+ buf2,
938
+ val
939
+ )
940
+
941
+ unless bool
942
+ raise SystemCallError.new('EvtGetPublisherMetadataProperty', FFI.errno)
943
+ end
944
+
945
+ file = buf2.read_string[16..-1]
946
+ exe = FFI::MemoryPointer.new(:char, MAX_SIZE)
947
+ ExpandEnvironmentStrings(file, exe, exe.size)
948
+ param_exe = exe.read_string
949
+
950
+ buf2.clear
951
+ val.clear
952
+
953
+ bool = EvtGetPublisherMetadataProperty(
954
+ pubMetadata,
955
+ 3, # EvtPublisherMetadataMessageFilePath
956
+ 0,
957
+ buf2.size,
958
+ buf2,
959
+ val
960
+ )
961
+
962
+ unless bool
963
+ raise SystemCallError.new('EvtGetPublisherMetadataProperty', FFI.errno)
964
+ end
965
+
966
+ exe.clear
967
+
968
+ file = buf2.read_string[16..-1]
969
+ ExpandEnvironmentStrings(file, exe, exe.size)
970
+ message_exe = exe.read_string
971
+
972
+ buf2.free
973
+ val.free
974
+ exe.free
975
+ end
976
+ ensure
977
+ EvtClose(pubMetadata) if pubMetadata
978
+ end
979
+ end
980
+
981
+ if param_exe != nil
982
+ va_list = va_list0.map{ |v|
983
+ va = v
984
+
985
+ v.scan(/%%(\d+)/).uniq.each{ |x|
986
+ param_exe.split(';').each{ |lfile|
987
+ hmodule = LoadLibraryEx(
988
+ lfile,
989
+ 0,
990
+ DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE
991
+ )
992
+
993
+ if hmodule != 0
994
+ res = FormatMessage(
995
+ FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_ARGUMENT_ARRAY,
996
+ hmodule,
997
+ x.first.to_i,
998
+ 0,
999
+ buf,
1000
+ buf.size,
1001
+ v
1002
+ )
1003
+
1004
+ if res == 0
1005
+ event_id = 0xB0000000 | event_id
1006
+ res = FormatMessage(
1007
+ FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS,
1008
+ hmodule,
1009
+ event_id,
1010
+ 0,
1011
+ buf,
1012
+ buf.size,
1013
+ nil
1014
+ )
1015
+ end
1016
+
1017
+ FreeLibrary(hmodule)
1018
+ break if buf.nstrip != ""
1019
+ end
1020
+ }
1021
+
1022
+ va = va.gsub("%%#{x.first}", buf.nstrip)
1023
+ }
1024
+
1025
+ va
1026
+ }
1027
+ end
1028
+
1029
+ if message_exe != nil
1030
+ buf.clear
1031
+
1032
+ # Try to retrieve message *without* expanding the inserts yet
1033
+ message_exe.split(';').each{ |lfile|
1034
+ hmodule = LoadLibraryEx(
1035
+ lfile,
1036
+ 0,
1037
+ DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE
1038
+ )
1039
+
1040
+ event_id = rec[:EventID]
1041
+
1042
+ if hmodule != 0
1043
+ res = FormatMessage(
1044
+ FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS,
1045
+ hmodule,
1046
+ event_id,
1047
+ 0,
1048
+ buf,
1049
+ buf.size,
1050
+ nil
1051
+ )
1052
+
1053
+ if res == 0
1054
+ event_id = 0xB0000000 | event_id
1055
+
1056
+ res = FormatMessage(
1057
+ FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS,
1058
+ hmodule,
1059
+ event_id,
1060
+ 0,
1061
+ buf,
1062
+ buf.size,
1063
+ nil
1064
+ )
1065
+ end
1066
+
1067
+ FreeLibrary(hmodule)
1068
+ break if buf.read_string != "" # All messages read
1069
+ end
1070
+ }
1071
+
1072
+ # Determine higest %n insert number
1073
+ max_insert = [num, buf.read_string.scan(/%(\d+)/).map{ |x| x[0].to_i }.max].compact.max
1074
+
1075
+ # Insert dummy strings not provided by caller
1076
+ ((num+1)..(max_insert)).each{ |x| va_list.push("%#{x}") }
1077
+
1078
+ if num == 0
1079
+ va_list_ptr = FFI::MemoryPointer.new(:pointer)
1080
+ else
1081
+ strptrs = []
1082
+ va_list.each{ |x| strptrs << FFI::MemoryPointer.from_string(x) }
1083
+ strptrs << nil
1084
+
1085
+ va_list_ptr = FFI::MemoryPointer.new(:pointer, strptrs.size)
1086
+
1087
+ strptrs.each_with_index{ |p, i|
1088
+ va_list_ptr[i].put_pointer(0, p)
1089
+ }
1090
+ end
1091
+
1092
+ message_exe.split(';').each{ |lfile|
1093
+ hmodule = LoadLibraryEx(
1094
+ lfile,
1095
+ 0,
1096
+ DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE
1097
+ )
1098
+
1099
+ event_id = rec[:EventID]
1100
+
1101
+ if hmodule != 0
1102
+ res = FormatMessage(
1103
+ FORMAT_MESSAGE_FROM_HMODULE |
1104
+ FORMAT_MESSAGE_ARGUMENT_ARRAY,
1105
+ hmodule,
1106
+ event_id,
1107
+ 0,
1108
+ buf,
1109
+ buf.size,
1110
+ va_list_ptr
1111
+ )
1112
+
1113
+ if res == 0
1114
+ event_id = 0xB0000000 | event_id
1115
+
1116
+ res = FormatMessage(
1117
+ FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_ARGUMENT_ARRAY,
1118
+ hmodule,
1119
+ event_id,
1120
+ 0,
1121
+ buf,
1122
+ buf.size,
1123
+ va_list_ptr
1124
+ )
1125
+ end
1126
+
1127
+ FreeLibrary(hmodule)
1128
+ break if buf.read_string != "" # All messages read
1129
+ end
1130
+ }
1131
+ end
1132
+ ensure
1133
+ Wow64RevertWow64FsRedirection(old_wow_val.read_ulong)
1134
+ end
1135
+
1136
+ [va_list0, buf.read_string]
1137
+ end
1138
+ end
1139
+ end