win32-eventlog 0.4.2

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