win32-eventlog 0.4.2

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.
data/CHANGES ADDED
@@ -0,0 +1,139 @@
1
+ = 0.4.2 - 6-Aug-2006
2
+ * Fixed a bug in the EventLog.read method related to the
3
+ EVENTLOG_BACKWARDS_READ flag.
4
+ * Fixed a bug in the EventLog.read method where the event_type was not being
5
+ set properly. Thanks go to "TheR" for the spot and the fix.
6
+ * Fixed bugs in the EventLog#tail method where it would fail if the log was
7
+ full and dups would appear.
8
+
9
+ = 0.4.1 - 28-May-2006
10
+ * Fixed a bug in the EventLog#notify_change method where the handle would
11
+ die after five or six reads. Thanks go to botp for the spot.
12
+
13
+ = 0.4.0 - 24-May-2006
14
+ * Now pure Ruby.
15
+ * Now includes a gemspec.
16
+ * Fixed a major bug the EventLog#tail method where it was reading the log in
17
+ the wrong order (!!!).
18
+ * Fixed a bug in the EventLog#report_event with regards to long category_id's.
19
+ Actually, this wasn't so much "fixed" as "avoided" by virtue of using a
20
+ pure Ruby solution.
21
+ * Added the EventLog#full? method.
22
+ * Documentation updates and corrections, including the tutorial on creating
23
+ and writing to your own event source.
24
+
25
+ == 0.3.3 - 2-Jan-2006
26
+ * If EventLog.new fails, it now raises EventLogError instead of StandardError.
27
+ * Added documentation to the eventlog.txt file for EventLog#notify_change.
28
+ * Added inline rdoc to the C source code.
29
+ * Code cleanup.
30
+
31
+ == 0.3.2 - 26-May-2005
32
+ * Code now more Unicode friendly, and other cleanup.
33
+ * Made the .txt files rdoc friendly, and removed the .rd files.
34
+ * Minor adjustments to the test suite.
35
+
36
+ == 0.3.1 - 16-Feb-2005
37
+ * Minor modifications to the source to eliminate some warnings that appeared
38
+ in Ruby 1.8.2 or later, plus other minor tweaks.
39
+ * Renamed the sample programs and moved the 'examples' directory to the
40
+ toplevel directory.
41
+ * Made this document rdoc friendly.
42
+
43
+ == 0.3.0 - 30-Oct-2004
44
+ * Added the notify_change() and tail() methods.
45
+ * Added the write() alias for report_event().
46
+ * The read() class and instance methods now return an array of structs if
47
+ no block is given.
48
+ * Added the 'rubymsg.mc' message category file. If installed, this will
49
+ create a 'RubyMsg' event source for use with win32-service, though you
50
+ may use it for whatever you wish. See the README for more details.
51
+ * Renamed struct back to EventLogStruct - sorry, sorry - I promise I won't
52
+ change this again.
53
+ * Fixed a bug where using EventLog::SEEK_READ did not work properly (thanks
54
+ Park).
55
+ * Added the notify.rb test script under doc/examples.
56
+
57
+ == 0.2.5 - 28-Oct-2004
58
+ * Modified the open() and read() class methods to default to "Application"
59
+ for the default Event source.
60
+ * Removed the .html files. You can generate these on your own with rd2.
61
+
62
+ == 0.2.4 - 19-Oct-2004
63
+ * Fixed a bug where a segfault would result if an Event Log entry were
64
+ somehow corrupted. Thanks go again to Joey Gibson for the spot.
65
+ * Very minor doc corrections in tutorial.txt and eventlog.txt
66
+
67
+ == 0.2.3 - 14-Oct-2004
68
+ * Fixed a bug where some Event Log descriptions were returning nil instead
69
+ of the actual description. Thanks go to Joey Gibson for the spot.
70
+ * Minor test suite and doc additions, including a "future plan" that may
71
+ be of interest.
72
+
73
+ == 0.2.2 - 02-Jul-2004
74
+ * Fixed a bug in the read() method where not all the records were being
75
+ returned.
76
+ * Fixed calls to rb_time_new() - second argument appears to be mandatory as
77
+ of 1.8.2.
78
+ * Renamed struct returned by read() to "EventLog".
79
+ * Changed struct members "id" and "type" to "event_id" and "event_type",
80
+ respectively. This was to avoid any confusion with the builtin Ruby
81
+ methods of the same name.
82
+ * Replaced STR2CSTR() with StringValuePtr(), as the former is deprecated.
83
+ This means that as of this version, Ruby 1.8.0 or later is required.
84
+ * Moved the sample programs to doc/examples.
85
+
86
+ == 0.2.1 - 04-Mar-2004
87
+ * The add_event_source() and report_event() methods now accept symbols as
88
+ well as do key validation.
89
+ * Moved the EventLogError class under the Win32 module.
90
+ * Normalized the tc_eventlog.rb and tc_mc.rb scripts by making it easier to
91
+ run them standalone from another directory.
92
+ * Minor modification to the build process.
93
+ * Added a test_write.rb script for general futzing when it comes to creating
94
+ and writing events to the log.
95
+ * Doc updates, warranty information added.
96
+
97
+ == 0.2.0 - 1-Feb-2004
98
+ * Changed the Win32EventLogError class to just EventLogError. I felt the
99
+ Win32 was redundant given that the class is already under the Win32 module.
100
+ * Fixed bug where bogus error messages were returned because I was calling
101
+ GetLastError() too late.
102
+ * Fixed bug where error messages were getting cut off and/or garbled due to
103
+ \r\n character. (Thanks Park Heesob).
104
+ * Added open_backup class method.
105
+
106
+ == 0.1.4 - 16-Jan-2004
107
+ * Slightly better handling of the EventLog#close method. Adopted from code
108
+ previously submitted by Park Heesob.
109
+ * Fixed a bug in the (internal) GetUser() function that caused a segfault if
110
+ there were 250+ records. That function has been greatly simplified.
111
+ * Removed the rb_ensure() approach. What I thought was a memory leak was not,
112
+ in fact, a memory leak. Also, it was slowing down the method considerably.
113
+ * Minor test suite update.
114
+
115
+ == 0.1.3 - 7-Jan-2004
116
+ * Fixed memory leaks in read() method (Park Heesob).
117
+ * Added read() class method (Daniel Berger).
118
+ * Test suite and doc additions & updates
119
+
120
+ == 0.1.2 - 30-Nov-2003
121
+ * Added the report_event method. You can now write to the event log.
122
+ * Added the add_event_source class method, allowing you to register a custom
123
+ event source.
124
+ * Added the win32-mc package to the distribution. This is a simple
125
+ wrapper to generate .dll files from .mc files.
126
+ * Added Event Type constants
127
+ * Test suite additions
128
+ * Documentation updates and additions, including a small tutorial.
129
+
130
+ == 0.1.1 - 5-Nov-2003
131
+ * Fixed the free() call, and added implicit eventlog closing (when the
132
+ EventLog object is free'd).
133
+ * Simplified constants by removing 'EVENTLOG_' portion of name.
134
+ * Cleanup in the GetDescription() method, which was causing segfaults in
135
+ conjunction with TestUnit.
136
+ * Minor doc updates and cleanup.
137
+
138
+ == 0.1.0 - 14-Oct-2003
139
+ * Initial release
data/MANIFEST ADDED
@@ -0,0 +1,22 @@
1
+ MANIFEST
2
+ CHANGES
3
+ README
4
+ install.rb
5
+ win32-eventlog.gemspec
6
+
7
+ doc/tutorial.txt
8
+
9
+ examples/test_read.rb
10
+ examples/test_write.rb
11
+ examples/test_notify.rb
12
+
13
+ lib/win32/eventlog.rb
14
+ lib/win32/mc.rb
15
+
16
+ misc/install_msg.rb
17
+ misc/rubymsg.mc
18
+
19
+ test/foo.mc
20
+ test/ts_all.rb
21
+ test/tc_eventlog.rb
22
+ test/tc_mc.rb
data/README ADDED
@@ -0,0 +1,63 @@
1
+ = What is it?
2
+ An interface to the Windows Event Log.
3
+
4
+ = Prerequisites
5
+ Ruby 1.8.2 or later.
6
+ windows-pr 0.5.0 or later.
7
+
8
+ = Installation
9
+ == Manual install
10
+ ruby test\tc_eventlog.rb (optional)
11
+ ruby install.rb
12
+ == Gem Install
13
+ ruby win32-eventlog.gemspec
14
+ gem install win32-eventlog-X.Y.Z-mswin32.gem
15
+
16
+ This will install both the win32-eventlog and win32-mc packages. The latter
17
+ is strictly for turning .mc files into .dll files. See the mc documentation
18
+ for more details.
19
+
20
+ = Installing the 'RubyMsg' event source
21
+ If you wish to install the RubyMsg event source, run the 'install_msg.rb'
22
+ script in the 'misc' directory. This will create a 'rubymsg' directory under
23
+ your toplevel Ruby installation directory (usually C:\ruby), and create the
24
+ .dll, .h, .rc and .res files there, in addition to copying the rubymsg.mc file.
25
+ It will then install the 'RubyMsg' event source into your registry.
26
+
27
+ DO NOT MOVE THE DLL FILE ONCE IT IS INSTALLED! If you do, you will have
28
+ to delete the registry entry and reinstall it with the correct path.
29
+
30
+ Take a look at the rubymsg.mc file for the category and message values. If
31
+ you do not understand this, please read the 'tutorial.txt' file in the 'doc'
32
+ directory.
33
+
34
+ = Additional documentation
35
+ If you are unfamiliar with message files and event logging on Windows in
36
+ general, please read the 'tutorial.txt' file.
37
+
38
+ There are also a couple of sample test scripts under the 'examples'
39
+ directory if you want to futz around and get a feel for how things work.
40
+
41
+ = If the tc_mc.rb tests fail
42
+ There's a chance that you either don't have the mc, rc and/or link commands
43
+ installed or they're not in your %PATH%. If you have MSVC++, you should have
44
+ them somewhere on your system.
45
+
46
+ = Known Issues
47
+ You may see this warning during the build and/or test phase:
48
+
49
+ LINK : warning LNK4068: /MACHINE not specified; defaulting to X86
50
+
51
+ You may ignore this warning.
52
+
53
+ = License
54
+ Ruby's
55
+
56
+ = Warranty
57
+ This package is provided "as is" and without any express or
58
+ implied warranties, including, without limitation, the implied
59
+ warranties of merchantability and fitness for a particular purpose.
60
+
61
+ = Authors
62
+ Daniel J. Berger
63
+ Park Heesob
data/doc/tutorial.txt ADDED
@@ -0,0 +1,140 @@
1
+ = Information about Message Files
2
+ Each event source should register message files that contain description
3
+ strings for each event identifier, event category, and parameter. Register
4
+ these files in the EventMessageFile, CategoryMessageFile, and
5
+ ParameterMessageFile registry values for the event source.
6
+
7
+ Note: ParameterMessageFiles are not yet supported in the add_event_source
8
+ method and probably won't be unless requested.
9
+
10
+ You can create one message file that contains descriptions for the event
11
+ identifiers, categories, and parameters, or create three separate message
12
+ files. Several applications can share the same message file.
13
+
14
+ You should typically create message files as resource-only DLLs. They are
15
+ smaller and faster than ordinary DLLs.
16
+
17
+ = What does a .mc file look like?
18
+
19
+ A .mc file is just a plain text file that is parsed by the "mc" utility to
20
+ generate a header and, ultimately, a .dll file. Here is a quick sample.
21
+ Note that there must be a newline after the last '.' at the bottom.
22
+ The ';' character denotes a comment.
23
+
24
+ ; foo.mc
25
+ MessageId=0x1
26
+ SymbolicName=CATEGORY_ERROR
27
+ Language=English
28
+ error
29
+ .
30
+
31
+ MessageId=0x2
32
+ SymbolicName=CATEGORY_WARNING
33
+ Language=English
34
+ warning
35
+ .
36
+
37
+ MessageId=0x3
38
+ Severity=Error
39
+ SymbolicName=FOO_ERROR
40
+ Language=English
41
+ Error: %1
42
+ .
43
+
44
+ = How to generate a .dll file from a .mc file
45
+ To turn this file into a .dll you have two options. The first is to use the
46
+ command line utilities. Follow these steps:
47
+
48
+ 1) mc filename.mc
49
+ 2) rc -r -fo filename.res filename.rc
50
+ 3) link -dll -noentry -out:filename.dll filename.res
51
+
52
+ Your other option is to use the win32-mc package, which is a simple wrapper
53
+ for the above commands, and is included with this package. You now have a
54
+ dll that you can associate with your event source (i.e. the one you associate
55
+ with your application). You can also take a look at the C header file that
56
+ .mc generates and use that in your own extensions if you like.
57
+
58
+ After this you'll need to register your event source and associate the .dll
59
+ file with it. To do that, use the EventLog.add_event_source method. Be sure
60
+ to specifiy the number of categories manually - it is not calculated
61
+ automatically by the OS.
62
+
63
+ Returning to the .mc file, the example I used actually creates two categories,
64
+ "error" and "warning", and one event message. The numbers you assign here
65
+ create corresponding (though not identical) values in the header file that
66
+ is generated. It is the values found in the header file that you pass to the
67
+ EventLog#report_event method for the category or event id. Here's the
68
+ relevant data from the foo.h file (using foo.mc above):
69
+
70
+ #define CATEGORY_ERROR 0x00000001L
71
+ #define CATEGORY_WARNING 0x00000002L
72
+ #define FOO_ERROR 0xC0000003L
73
+
74
+ In the case of categories, that number is the name number that shows up in the
75
+ "category" field in the Event Viewer. In the case of event message files, it
76
+ is the text that shows up in the event description.
77
+
78
+ The "data" field is what replaces "%1" as an actual text string in the event
79
+ log, sort of like a printf (except that it's always a string).
80
+
81
+ = Registering an event source
82
+ First, create the .dll file from the .mc file. Then register that .dll file
83
+ for an event source we'll call "foo". You can name the .dll file anything
84
+ you like, but for sanity's sake I recommend keeping the same as the event
85
+ source name.
86
+
87
+ require "win32/eventlog"
88
+ include Win32
89
+
90
+ dll_file = "c:\\wherever\\foo.dll"
91
+
92
+ EventLog.add_event_source(
93
+ "source" => "Application",
94
+ "key_name" => "foo",
95
+ "category_count" => 2,
96
+ "event_message_file" => dll_file,
97
+ "category_message_file" => dll_file
98
+ )
99
+
100
+ After you run this, you can run regedit and see that your event source has
101
+ been inserted into the registry. You can find it under:
102
+
103
+ HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog\Application.
104
+
105
+ = Writing to the event source
106
+ Now that our event source "foo" is registered, we can begin writing event
107
+ log data for it. Here's an example of how you use it:
108
+
109
+ require "win32/eventlog"
110
+ include Win32
111
+
112
+ EventLog.open("Application") do |log|
113
+ log.report_event(
114
+ :source => "foo",
115
+ :event_type => EventLog::WARN,
116
+ :category => "0x00000002L".hex,
117
+ :event_id => "0xC0000003L".hex,
118
+ :data => "I'm warning you!"
119
+ )
120
+ end
121
+
122
+ Note the values used for the 'category' and 'event_id' keys. Those are the
123
+ values that were generated automatically in the foo.h file that I showed you
124
+ above. You'll have to manually inspect the header file that's generated to
125
+ determine which values you should be using.
126
+
127
+ You can now open your event log viewer and look at the message. You can get
128
+ to your event log viewer via Start -> Control Panel -> Administrative Tools ->
129
+ Event Viewer. You should see a warning message with the category "warning"
130
+ and an event id of '3'. If you right click on that entry and select
131
+ "properties", you can see the event description is "Warning: I'm warning you!".
132
+
133
+ == More Info
134
+ For more information visit the following URL's:
135
+
136
+ http://msdn.microsoft.com/library/default.asp?url=/library/en-us/tools/tools/message_text_file_syntax.asp
137
+
138
+ http://msdn.microsoft.com/library/default.asp?url=/library/en-us/debug/base/message_files.asp
139
+
140
+ http://msdn.microsoft.com/library/default.asp?url=/library/en-us/tools/tools/header_section.asp
@@ -0,0 +1,754 @@
1
+ require 'windows/error'
2
+ require 'windows/eventlog'
3
+ require 'windows/security'
4
+ require 'windows/registry'
5
+ require 'windows/system_info'
6
+ require 'windows/library'
7
+ require 'windows/synchronize'
8
+ require 'windows/handle'
9
+
10
+ class String
11
+ # Return the portion of the string up to the first NULL character. This
12
+ # was added for both speed and convenience.
13
+ def nstrip
14
+ self[ /^[^\0]*/ ]
15
+ end
16
+ end
17
+
18
+ module Win32
19
+ class EventLogError < StandardError; end
20
+ class EventLog
21
+ include Windows::Error
22
+ include Windows::EventLog
23
+ include Windows::Security
24
+ include Windows::Registry
25
+ include Windows::SystemInfo
26
+ include Windows::Library
27
+ include Windows::Synchronize
28
+ include Windows::Handle
29
+ extend Windows::Error
30
+ extend Windows::Registry
31
+
32
+ VERSION = '0.4.2'
33
+
34
+ # Aliased read flags
35
+ FORWARDS_READ = EVENTLOG_FORWARDS_READ
36
+ BACKWARDS_READ = EVENTLOG_BACKWARDS_READ
37
+ SEEK_READ = EVENTLOG_SEEK_READ
38
+ SEQUENTIAL_READ = EVENTLOG_SEQUENTIAL_READ
39
+
40
+ # Aliased event types
41
+ SUCCESS = EVENTLOG_SUCCESS
42
+ ERROR = EVENTLOG_ERROR_TYPE
43
+ WARN = EVENTLOG_WARNING_TYPE
44
+ INFO = EVENTLOG_INFORMATION_TYPE
45
+ AUDIT_SUCCESS = EVENTLOG_AUDIT_SUCCESS
46
+ AUDIT_FAILURE = EVENTLOG_AUDIT_FAILURE
47
+
48
+ # These are not meant for public use
49
+ BUFFER_SIZE = 1024 * 64
50
+ MAX_SIZE = 256
51
+ MAX_STRINGS = 16
52
+ BASE_KEY = "SYSTEM\\CurrentControlSet\\Services\\EventLog\\"
53
+
54
+ EventLogStruct = Struct.new('EventLogStruct', :record_number,
55
+ :time_generated, :time_written, :event_id, :event_type, :category,
56
+ :source, :computer, :user, :description
57
+ )
58
+
59
+ # The name of the event log source. This will typically be
60
+ # 'Application', 'System' or 'Security', but could also refer to
61
+ # a custom event log source.
62
+ #
63
+ attr_reader :source
64
+
65
+ # The name of the server which the event log is reading from.
66
+ #
67
+ attr_reader :server
68
+
69
+ # The name of the file used in the EventLog.open_backup method. This is
70
+ # set to nil if the file was not opened using the EventLog.open_backup
71
+ # method.
72
+ #
73
+ attr_reader :file
74
+
75
+ # Opens a handle to the new EventLog +source+ on +server+, or the local
76
+ # machine if no host is specified. Typically, your source will be
77
+ # 'Application, 'Security' or 'System', although you can specify a
78
+ # custom log file as well.
79
+ #
80
+ # If a custom, registered log file name cannot be found, the event
81
+ # logging service opens the 'Application' log file. This is the
82
+ # behavior of the underlying Windows function, not my own doing.
83
+ #
84
+ def initialize(source = 'Application', server = nil, file = nil)
85
+ @source = source || 'Application' # In case of explicit nil
86
+ @server = server
87
+ @file = file
88
+
89
+ # Avoid Win32API segfaults
90
+ raise TypeError unless @source.is_a?(String)
91
+ raise TypeError unless @server.is_a?(String) if @server
92
+
93
+ function = 'OpenEventLog()'
94
+
95
+ if @file.nil?
96
+ @handle = OpenEventLog(@server, @source)
97
+ else
98
+ @handle = OpenBackupEventLog(@server, @file)
99
+ function = 'OpenBackupEventLog()'
100
+ end
101
+
102
+ if @handle == 0
103
+ error = "#{function} failed: " + get_last_error
104
+ raise EventLogError, error
105
+ end
106
+
107
+ # Ensure the handle is closed at the end of a block
108
+ if block_given?
109
+ begin
110
+ yield self
111
+ ensure
112
+ close
113
+ end
114
+ end
115
+ end
116
+
117
+ # Class method aliases
118
+ class << self
119
+ alias :open :new
120
+ end
121
+
122
+ # Nearly identical to EventLog.open, except that the source is a backup
123
+ # file and not an event source (and there is no default).
124
+ #
125
+ def self.open_backup(file, source = 'Application', server = nil)
126
+ @file = file
127
+ @source = source
128
+ @server = server
129
+
130
+ # Avoid Win32API segfaults
131
+ raise TypeError unless @file.is_a?(String)
132
+ raise TypeError unless @source.is_a?(String)
133
+ raise TypeError unless @server.is_a?(String) if @server
134
+
135
+ self.new(source, server, file)
136
+ end
137
+
138
+ # Adds an event source to the registry. The following are valid keys:
139
+ #
140
+ # * source - Source name. Set to "Application" by default
141
+ # * key_name - Name stored as the registry key
142
+ # * category_count - Number of supported (custom) categories
143
+ # * event_message_file - File (dll) that defines events
144
+ # * category_message_file - File (dll) that defines categories
145
+ # * supported_types - See the 'event types' constants
146
+ #
147
+ # Of these keys, only +key_name+ is mandatory. An ArgumentError is
148
+ # raised if you attempt to use an invalid key. If +supported_types+
149
+ # is not specified then the following value is used:
150
+ #
151
+ # EventLog::ERROR | EventLog::WARN | EventLog::INFO
152
+ #
153
+ # The +event_message_file+ and +category_message_file+ are typically,
154
+ # though not necessarily, the same file. See the documentation on .mc files
155
+ # for more details.
156
+ #
157
+ def self.add_event_source(args)
158
+ raise TypeError unless args.is_a?(Hash)
159
+
160
+ hkey = [0].pack('L')
161
+
162
+ valid_keys = %w/source key_name category_count event_message_file
163
+ category_message_file supported_types/
164
+
165
+ key_base = "SYSTEM\\CurrentControlSet\\Services\\EventLog\\"
166
+
167
+ # Default values
168
+ hash = {
169
+ 'source' => 'Application',
170
+ 'supported_types' => ERROR | WARN | INFO
171
+ }
172
+
173
+ # Validate the keys, and convert symbols and case to lowercase strings.
174
+ args.each{ |key, val|
175
+ key = key.to_s.downcase
176
+ unless valid_keys.include?(key)
177
+ raise ArgumentError, "invalid key '#{key}'"
178
+ end
179
+ hash[key] = val
180
+ }
181
+
182
+ # The key_name must be specified
183
+ unless hash['key_name']
184
+ raise EventLogError, 'no event_type specified'
185
+ end
186
+
187
+ key = key_base << hash['source'] << "\\" << hash['key_name']
188
+
189
+ if RegCreateKey(HKEY_LOCAL_MACHINE, key, hkey) != ERROR_SUCCESS
190
+ error = 'RegCreateKey() failed: ' + get_last_error
191
+ raise EventLogError, error
192
+ end
193
+
194
+ hkey = hkey.unpack('L').first
195
+
196
+ if hash['category_count']
197
+ data = [hash['category_count']].pack('L')
198
+
199
+ rv = RegSetValueEx(
200
+ hkey,
201
+ 'CategoryCount',
202
+ 0,
203
+ REG_DWORD,
204
+ data,
205
+ data.size
206
+ )
207
+
208
+ if rv != ERROR_SUCCESS
209
+ error = 'RegSetValueEx() failed: ', get_last_error
210
+ RegCloseKey(hkey)
211
+ raise EventLogError, error
212
+ end
213
+ end
214
+
215
+ if hash['category_message_file']
216
+ data = File.expand_path(hash['category_message_file'])
217
+
218
+ rv = RegSetValueEx(
219
+ hkey,
220
+ 'CategoryMessageFile',
221
+ 0,
222
+ REG_EXPAND_SZ,
223
+ data,
224
+ data.size
225
+ )
226
+
227
+ if rv != ERROR_SUCCESS
228
+ error = 'RegSetValueEx() failed: ', get_last_error
229
+ RegCloseKey(hkey)
230
+ raise EventLogError, error
231
+ end
232
+ end
233
+
234
+ if hash['event_message_file']
235
+ data = File.expand_path(hash['event_message_file'])
236
+
237
+ rv = RegSetValueEx(
238
+ hkey,
239
+ 'EventMessageFile',
240
+ 0,
241
+ REG_EXPAND_SZ,
242
+ data,
243
+ data.size
244
+ )
245
+
246
+ if rv != ERROR_SUCCESS
247
+ error = 'RegSetValueEx() failed: ', get_last_error
248
+ RegCloseKey(hkey)
249
+ raise EventLogError, error
250
+ end
251
+ end
252
+
253
+ data = [hash['supported_types']].pack('L')
254
+ rv = RegSetValueEx(
255
+ hkey,
256
+ 'TypesSupported',
257
+ 0,
258
+ REG_DWORD,
259
+ data,
260
+ data.size
261
+ )
262
+
263
+ if rv != ERROR_SUCCESS
264
+ error = 'RegSetValueEx() failed: ', get_last_error
265
+ RegCloseKey(hkey)
266
+ raise EventLogError, error
267
+ end
268
+
269
+ RegCloseKey(hkey)
270
+ self
271
+ end
272
+
273
+ # Backs up the event log to +file+. Note that you cannot backup to
274
+ # a file that already exists or a EventLogError will be raised.
275
+ #
276
+ def backup(file)
277
+ raise TypeError unless file.is_a?(String)
278
+ unless BackupEventLog(@handle, file)
279
+ error = 'BackupEventLog() failed: ' + get_last_error
280
+ raise EventLogError, error
281
+ end
282
+ self
283
+ end
284
+
285
+ # Clears the EventLog. If +backup_file+ is provided, it backs up the
286
+ # event log to that file first.
287
+ #
288
+ def clear(backup_file = nil)
289
+ raise TypeError unless backup_file.is_a?(String) if backup_file
290
+ backup_file = 0 unless backup_file
291
+
292
+ unless ClearEventLog(@handle, backup_file)
293
+ error = 'ClearEventLog() failed: ' + get_last_error
294
+ raise EventLogError
295
+ end
296
+
297
+ self
298
+ end
299
+
300
+ # Closes the EventLog handle. Automatically closed for you if you use
301
+ # the block form of EventLog.new.
302
+ #
303
+ def close
304
+ CloseEventLog(@handle)
305
+ end
306
+
307
+ # Indicates whether or not the event log is full.
308
+ #
309
+ def full?
310
+ buf = [0].pack('L') # dwFull
311
+ needed = [0].pack('L')
312
+
313
+ unless GetEventLogInformation(@handle, 0, buf, buf.size, needed)
314
+ raise 'GetEventLogInformation() failed: ' + get_last_error
315
+ end
316
+
317
+ buf[0,4].unpack('L').first != 0
318
+ end
319
+
320
+ # Returns the absolute record number of the oldest record. Note that
321
+ # this is not guaranteed to be 1 because event log records can be
322
+ # overwritten.
323
+ #
324
+ def oldest_record_number
325
+ rec = [0].pack('L')
326
+
327
+ unless GetOldestEventLogRecord(@handle, rec)
328
+ error = 'GetOldestEventLogRecord() failed: ' + get_last_error
329
+ raise EventLogError, error
330
+ end
331
+
332
+ rec.unpack('L').first
333
+ end
334
+
335
+ # Returns the total number of records for the given event log.
336
+ #
337
+ def total_records
338
+ total = [0].pack('L')
339
+
340
+ unless GetNumberOfEventLogRecords(@handle, total)
341
+ error = 'GetNumberOfEventLogRecords() failed: '
342
+ error += get_last_error
343
+ raise EventLogError, error
344
+ end
345
+
346
+ total.unpack('L').first
347
+ end
348
+
349
+ # Yields an EventLogStruct every time a record is written to the event
350
+ # log. Unlike EventLog#tail, this method breaks out of the block after
351
+ # the event.
352
+ #
353
+ # Raises an EventLogError if no block is provided.
354
+ #
355
+ def notify_change(&block)
356
+ unless block_given?
357
+ raise EventLogError, 'block missing for notify_change()'
358
+ end
359
+
360
+ # Reopen the handle because the NotifyChangeEventLog() function will
361
+ # choke after five or six reads otherwise.
362
+ @handle = OpenEventLog(@server, @source)
363
+
364
+ if @handle == 0
365
+ error = "OpenEventLog() failed: " + get_last_error
366
+ raise EventLogError, error
367
+ end
368
+
369
+ event = CreateEvent(0, 0, 0, 0)
370
+
371
+ unless NotifyChangeEventLog(@handle, event)
372
+ error = 'NotifyChangeEventLog() failed: ' + get_last_error
373
+ raise EventLogError, error
374
+ end
375
+
376
+ wait_result = WaitForSingleObject(event, INFINITE)
377
+
378
+ if wait_result == WAIT_FAILED
379
+ error = 'WaitForSingleObject() failed: ' + get_last_error
380
+ CloseHandle(event)
381
+ raise EventLogError, error
382
+ else
383
+ last = read_last_event
384
+ block.call(last)
385
+ end
386
+
387
+ CloseHandle(event)
388
+ self
389
+ end
390
+
391
+ # Yields an EventLogStruct every time a record is written to the event
392
+ # log, once every +frequency+ seconds. Unlike EventLog#notify_change,
393
+ # this method does not break out of the block after the event. The read
394
+ # +frequency+ is set to 5 seconds by default.
395
+ #
396
+ # Raises an EventLogError if no block is provided.
397
+ #
398
+ # The delay between reads is due to the nature of the Windows event log.
399
+ # It is not really designed to be tailed in the manner of a Unix syslog,
400
+ # for example, in that not nearly as many events are typically recorded.
401
+ # It's just not designed to be polled that heavily.
402
+ #
403
+ def tail(frequency = 5)
404
+ unless block_given?
405
+ raise EventLogError, 'block missing for tail()'
406
+ end
407
+
408
+ old_total = total_records()
409
+ flags = FORWARDS_READ | SEEK_READ
410
+ rec_num = read_last_event.record_number
411
+
412
+ while true
413
+ new_total = total_records()
414
+ if new_total != old_total
415
+ rec_num = oldest_record_number() if full?
416
+ read(flags, rec_num).each{ |log| yield log }
417
+ old_total = new_total
418
+ rec_num = read_last_event.record_number + 1
419
+ end
420
+ sleep frequency
421
+ end
422
+ end
423
+
424
+ # EventLog#read(flags=nil, offset=0)
425
+ # EventLog#read(flags=nil, offset=0){ |log| ... }
426
+ #
427
+ # Iterates over each record in the event log, yielding a EventLogStruct
428
+ # for each record. The offset value is only used when used in
429
+ # conjunction with the EventLog::SEEK_READ flag. Otherwise, it is
430
+ # ignored. If no flags are specified, then the default flags are:
431
+ #
432
+ # EventLog::SEQUENTIAL_READ | EventLog::FORWARDS_READ
433
+ #
434
+ # The EventLogStruct struct contains the following members:
435
+ #
436
+ # record_number # Fixnum
437
+ # time_generated # Time
438
+ # time_written # Time
439
+ # event_id # Fixnum
440
+ # event_type # String
441
+ # category # String
442
+ # source # String
443
+ # computer # String
444
+ # user # String or nil
445
+ # description # String or nil
446
+ #
447
+ # If no block is given the method returns an array of EventLogStruct's.
448
+ #
449
+ def read(flags = nil, offset = 0)
450
+ buf = 0.chr * BUFFER_SIZE # 64k buffer
451
+ size = buf.size
452
+ read = [0].pack('L')
453
+ needed = [0].pack('L')
454
+ array = []
455
+
456
+ unless flags
457
+ flags = FORWARDS_READ | SEQUENTIAL_READ
458
+ end
459
+
460
+ while ReadEventLog(@handle, flags, offset, buf, size, read, needed) ||
461
+ GetLastError() == ERROR_INSUFFICIENT_BUFFER
462
+
463
+ if GetLastError() == ERROR_INSUFFICIENT_BUFFER
464
+ buf += 0.chr * needed.unpack('L').first
465
+ ReadEventLog(@handle, flags, offset, buf, size, read, needed)
466
+ end
467
+
468
+ dwread = read.unpack('L').first
469
+
470
+ while dwread > 0
471
+ struct = EventLogStruct.new
472
+ event_source = buf[56..-1].nstrip
473
+ computer = buf[56 + event_source.length + 1..-1].nstrip
474
+
475
+ user = get_user(buf)
476
+ desc = get_description(buf, event_source)
477
+
478
+ struct.source = event_source
479
+ struct.computer = computer
480
+ struct.record_number = buf[8,4].unpack('L').first
481
+ struct.time_generated = Time.at(buf[12,4].unpack('L').first)
482
+ struct.time_written = Time.at(buf[16,4].unpack('L').first)
483
+ struct.event_id = buf[20,4].unpack('L').first & 0x0000FFFF
484
+ struct.event_type = get_event_type(buf[24,2].unpack('S').first)
485
+ struct.user = user
486
+ struct.category = buf[28,2].unpack('S').first
487
+ struct.description = desc
488
+
489
+ if block_given?
490
+ yield struct
491
+ else
492
+ array.push(struct)
493
+ end
494
+
495
+ if flags & EVENTLOG_BACKWARDS_READ > 0
496
+ offset = buf[8,4].unpack('L').first - 1
497
+ else
498
+ offset = buf[8,4].unpack('L').first + 1
499
+ end
500
+
501
+ length = buf[0,4].unpack('L').first # Length
502
+
503
+ dwread -= length
504
+ buf = buf[length..-1]
505
+ end
506
+
507
+ buf = 0.chr * BUFFER_SIZE
508
+ read = [0].pack('L')
509
+ end
510
+
511
+ block_given? ? nil : array
512
+ end
513
+
514
+ # This class method is nearly identical to the EventLog#read instance
515
+ # method, except that it takes a +source+ and +server+ as the first two
516
+ # arguments.
517
+ #
518
+ def self.read(source='Application', server=nil, flags=nil, offset=0)
519
+ self.new(source, server){ |log|
520
+ if block_given?
521
+ log.read(flags, offset){ |els| yield els }
522
+ else
523
+ return log.read(flags, offset)
524
+ end
525
+ }
526
+ end
527
+
528
+ # EventLog#report_event(key => value, ...)
529
+ #
530
+ # Writes an event to the event log. The following are valid keys:
531
+ #
532
+ # source # Event log source name. Defaults to "Application"
533
+ # event_id # Event ID (defined in event message file)
534
+ # category # Event category (defined in category message file)
535
+ # data # String that is written to the log
536
+ # event_type # Type of event, e.g. EventLog::ERROR, etc.
537
+ #
538
+ # The +event_type+ keyword is the only mandatory keyword. The others are
539
+ # optional. Although the +source+ defaults to "Application", I
540
+ # recommend that you create an application specific event source and use
541
+ # that instead. See the 'EventLog.add_event_source' method for more
542
+ # details.
543
+ #
544
+ # The +event_id+ and +category+ values are defined in the message
545
+ # file(s) that you created for your application. See the tutorial.txt
546
+ # file for more details on how to create a message file.
547
+ #
548
+ # An ArgumentError is raised if you attempt to use an invalid key.
549
+ #
550
+ def report_event(args)
551
+ raise TypeError unless args.is_a?(Hash)
552
+
553
+ valid_keys = %w/source event_id category data event_type/
554
+ num_strings = 0
555
+
556
+ # Default values
557
+ hash = {
558
+ 'source' => @source,
559
+ 'event_id' => 0,
560
+ 'category' => 0,
561
+ 'data' => 0
562
+ }
563
+
564
+ # Validate the keys, and convert symbols and case to lowercase strings.
565
+ args.each{ |key, val|
566
+ key = key.to_s.downcase
567
+ unless valid_keys.include?(key)
568
+ raise ArgumentError, "invalid key '#{key}'"
569
+ end
570
+ hash[key] = val
571
+ }
572
+
573
+ # The event_type must be specified
574
+ unless hash['event_type']
575
+ raise EventLogError, 'no event_type specified'
576
+ end
577
+
578
+ handle = RegisterEventSource(@server, hash['source'])
579
+
580
+ if handle == 0
581
+ error = 'RegisterEventSource() failed: ' + get_last_error
582
+ raise EventLogError, error
583
+ end
584
+
585
+ if hash['data'].is_a?(String)
586
+ data = hash['data'] << 0.chr
587
+ data = [data].pack('p*')
588
+ num_strings = 1
589
+ else
590
+ data = 0
591
+ num_strings = 0
592
+ end
593
+
594
+ bool = ReportEvent(
595
+ handle,
596
+ hash['event_type'],
597
+ hash['category'],
598
+ hash['event_id'],
599
+ 0,
600
+ num_strings,
601
+ 0,
602
+ data,
603
+ 0
604
+ )
605
+
606
+ unless bool
607
+ error = 'ReportEvent() failed: ' + get_last_error
608
+ raise EventLogError, error
609
+ end
610
+
611
+ self
612
+ end
613
+
614
+ alias :write :report_event
615
+
616
+ private
617
+
618
+ # A private method that reads the last event log record.
619
+ #
620
+ def read_last_event(handle=@handle, source=@source, server=@server)
621
+ buf = 0.chr * BUFFER_SIZE # 64k buffer
622
+ read = [0].pack('L')
623
+ needed = [0].pack('L')
624
+
625
+ flags = EVENTLOG_BACKWARDS_READ | EVENTLOG_SEQUENTIAL_READ
626
+ ReadEventLog(@handle, flags, 0, buf, buf.size, read, needed)
627
+
628
+ event_source = buf[56..-1].nstrip
629
+ computer = buf[56 + event_source.length + 1..-1].nstrip
630
+ event_type = get_event_type(buf[24,2].unpack('S').first)
631
+ user = get_user(buf)
632
+ desc = get_description(buf, event_source)
633
+
634
+ struct = EventLogStruct.new
635
+ struct.source = event_source
636
+ struct.computer = computer
637
+ struct.record_number = buf[8,4].unpack('L').first
638
+ struct.time_generated = Time.at(buf[12,4].unpack('L').first)
639
+ struct.time_written = Time.at(buf[16,4].unpack('L').first)
640
+ struct.event_id = buf[20,4].unpack('L').first & 0x0000FFFF
641
+ struct.event_type = event_type
642
+ struct.user = user
643
+ struct.category = buf[28,2].unpack('S').first
644
+ struct.description = desc
645
+
646
+ struct
647
+ end
648
+
649
+ # Private method that gets the event description (text) based on data
650
+ # from the EVENTLOGRECORD buffer.
651
+ #
652
+ def get_description(rec, event_source)
653
+ str = rec[ rec[36,4].unpack('L').first .. -1]
654
+ num = rec[26,2].unpack('S').first # NumStrings
655
+ hkey = [0].pack('L')
656
+ key = BASE_KEY + "#{@source}\\#{event_source}"
657
+ buf = 0.chr * 1024
658
+
659
+ if num == 0
660
+ va_list_ptr = 0.chr * 4
661
+ else
662
+ va_list = str.split(0.chr)[0...num]
663
+ va_list_ptr = va_list.map{ |x|
664
+ [x + 0.chr].pack('P').unpack('L').first
665
+ }.pack('L*')
666
+ end
667
+
668
+ if RegOpenKeyEx(HKEY_LOCAL_MACHINE, key, 0, KEY_READ, hkey) == 0
669
+ value = 'EventMessageFile'
670
+ file = 0.chr * MAX_SIZE
671
+ hkey = hkey.unpack('L').first
672
+ size = [file.length].pack('L')
673
+
674
+ if RegQueryValueEx(hkey, value, 0, 0, file, size) == 0
675
+ file = file.nstrip
676
+ exe = 0.chr * MAX_SIZE
677
+
678
+ ExpandEnvironmentStrings(file, exe, exe.size)
679
+ exe = exe.nstrip
680
+
681
+ exe.split(';').each{ |file|
682
+ hmodule = LoadLibraryEx(file, 0, LOAD_LIBRARY_AS_DATAFILE)
683
+ event_id = rec[20,4].unpack('L').first
684
+ if hmodule != 0
685
+ FormatMessage(
686
+ FORMAT_MESSAGE_FROM_HMODULE |
687
+ FORMAT_MESSAGE_FROM_SYSTEM |
688
+ FORMAT_MESSAGE_ARGUMENT_ARRAY,
689
+ hmodule,
690
+ event_id,
691
+ 0,
692
+ buf,
693
+ buf.size,
694
+ va_list_ptr
695
+ )
696
+ FreeLibrary(hmodule)
697
+ end
698
+ }
699
+ end
700
+
701
+ RegCloseKey(hkey)
702
+ end
703
+ buf.strip
704
+ end
705
+
706
+ # Private method that retrieves the user name based on data in the
707
+ # EVENTLOGRECORD buffer.
708
+ #
709
+ def get_user(buf)
710
+ return nil if buf[40,4].unpack('L').first <= 0 # UserSidLength
711
+
712
+ name = 0.chr * MAX_SIZE
713
+ name_size = [name.size].pack('L')
714
+ domain = 0.chr * MAX_SIZE
715
+ domain_size = [domain.size].pack('L')
716
+ snu = 0.chr * 4
717
+
718
+ offset = buf[44,4].unpack('L').first # UserSidOffset
719
+
720
+ val = LookupAccountSid(
721
+ @server,
722
+ [buf].pack('P').unpack('L').first + offset,
723
+ name,
724
+ name_size,
725
+ domain,
726
+ domain_size,
727
+ snu
728
+ )
729
+
730
+ # Return nil if the lookup failed
731
+ return val ? name.nstrip : nil
732
+ end
733
+
734
+ # Private method that converts a numeric event type into a human
735
+ # readable string.
736
+ #
737
+ def get_event_type(event)
738
+ case event
739
+ when EVENTLOG_ERROR_TYPE
740
+ 'error'
741
+ when EVENTLOG_WARNING_TYPE
742
+ 'warning'
743
+ when EVENTLOG_INFORMATION_TYPE
744
+ 'information'
745
+ when EVENTLOG_AUDIT_SUCCESS
746
+ 'audit_success'
747
+ when EVENTLOG_AUDIT_FAILURE
748
+ 'audit_failure'
749
+ else
750
+ nil
751
+ end
752
+ end
753
+ end
754
+ end