win32-eventlog 0.6.3 → 0.6.4

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..."
@@ -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