winevt_c 0.11.5 → 0.11.6

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6f7ef84281eaba15c88529977e4d2d84bca0c66069f89f9c66dd46b992a65cff
4
- data.tar.gz: a1cbda6208cecadcd57d130151c15161985f2dbd57ea83599cd546910f23af47
3
+ metadata.gz: 8cae317f8e53bccce324f256e88df86042e221489c05384728ceb44acf40929d
4
+ data.tar.gz: a08a75d868d7ff8f186077f0dbaafc3fd82ef6a89f8ebd92c8deeaab996fa257
5
5
  SHA512:
6
- metadata.gz: 89e04099f849f47445c61005218e080fc21d6fb6a18cb59b51b1e17d8f28e3de85289aae4494793bacc14f08a6545a2b5775bd529dffc5710aa74dbf71b0d2e6
7
- data.tar.gz: ade1be36ac7253dae7d72be6bd65a25424d21625da3efb1fff5a619d4e04f015ac3f6da6c6f18cc6252482361ea594cd1aabb6bf41f36894aa510023dfa70c6a
6
+ metadata.gz: 3ca5a2836e88d7e1815a04d4f3698c4526d64643035833f7ce22acc220de23d7c5f26222f398284da02f47d55b36fd0f262124a2c38cfe8daff057813b919374
7
+ data.tar.gz: 426256cdc15034f4acfadcac742f854467b8eb574efe1f15e6cb2b8e8b6ac8d4b3fc70644953d4382b55fd7378f98b88bd51628d957a7200973fb24ac400f5a3
@@ -28,8 +28,8 @@ jobs:
28
28
  ruby: '3.2'
29
29
  name: Ruby ${{ matrix.ruby }} on ${{ matrix.os }}
30
30
  steps:
31
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
32
- - uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0
31
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
32
+ - uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
33
33
  with:
34
34
  ruby-version: ${{ matrix.ruby }}
35
35
  - name: Run tests
data/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  * Windows Vista/Windows Server 2008 or later.
8
8
  * gcc and g++ from MSYS2 for building C/C++ extension.
9
- * Ruby 2.4 or later with MSYS2.
9
+ * Ruby 3.2 or later with MSYS2.
10
10
 
11
11
  ## Installation
12
12
 
@@ -14,9 +14,6 @@ have_library("wevtapi")
14
14
  have_func("EvtQuery", "winevt.h")
15
15
  have_library("advapi32")
16
16
  have_library("ole32")
17
- if have_macro("RB_ALLOCV")
18
- $CFLAGS << " -DHAVE_RB_ALLOCV=1 "
19
- end
20
17
 
21
18
  $LDFLAGS << " -lwevtapi -ladvapi32 -lole32"
22
19
  $CFLAGS << " -Wall -std=c99 -fPIC -fms-extensions "
@@ -17,7 +17,7 @@
17
17
  *
18
18
  * puts @bookmark.render
19
19
  */
20
- /* clang-format pn */
20
+ /* clang-format on */
21
21
 
22
22
  VALUE rb_cBookmark;
23
23
 
@@ -54,9 +54,9 @@ rb_winevt_bookmark_alloc(VALUE klass)
54
54
  }
55
55
 
56
56
  /*
57
- * Initalize Bookmark class. Receive XML string or nil.
57
+ * Initialize Bookmark class. Receive XML string or nil.
58
58
  *
59
- * @overload initailize(options={})
59
+ * @overload initialize(options={})
60
60
  * @option options [String] XML rendered Bookmark string.
61
61
  * @return [Bookmark]
62
62
  *
@@ -109,12 +109,16 @@ rb_winevt_bookmark_update(VALUE self, VALUE event)
109
109
  struct WinevtQuery* winevtQuery;
110
110
  struct WinevtBookmark* winevtBookmark;
111
111
 
112
+ if (!rb_obj_is_kind_of(event, rb_cQuery)) {
113
+ rb_raise(rb_eArgError, "Expected a Query instance");
114
+ }
115
+
112
116
  winevtQuery = EventQuery(event);
113
117
 
114
118
  TypedData_Get_Struct(
115
119
  self, struct WinevtBookmark, &rb_winevt_bookmark_type, winevtBookmark);
116
120
 
117
- for (int i = 0; i < winevtQuery->count; i++) {
121
+ for (ULONG i = 0; i < winevtQuery->count; i++) {
118
122
  if (!EvtUpdateBookmark(winevtBookmark->bookmark, winevtQuery->hEvents[i]))
119
123
  return Qfalse;
120
124
  }
@@ -16,11 +16,6 @@
16
16
  #endif /* WIN32_WINNT */
17
17
  #define _WIN32_WINNT MINIMUM_WINDOWS_VERSION
18
18
 
19
- #if !defined(HAVE_RB_ALLOCV)
20
- #define ALLOCV RB_ALLOCV
21
- #define ALLOCV_N RB_ALLOCV_N
22
- #endif
23
-
24
19
  #include <time.h>
25
20
  #include <winevt.h>
26
21
  #define EventQuery(object) ((struct WinevtQuery*)DATA_PTR(object))
@@ -52,7 +47,8 @@ EVT_HANDLE connect_to_remote(LPWSTR computerName, LPWSTR domain,
52
47
  LPWSTR username, LPWSTR password,
53
48
  EVT_RPC_LOGIN_FLAGS flags,
54
49
  DWORD *error_code);
55
- WCHAR* get_description(EVT_HANDLE handle, LANGID langID, EVT_HANDLE hRemote);
50
+ WCHAR* get_description(EVT_HANDLE handle, LANGID langID, EVT_HANDLE hRemote,
51
+ BOOL* resolved);
56
52
  VALUE get_values(EVT_HANDLE handle);
57
53
  VALUE render_system_event(EVT_HANDLE handle, BOOL preserve_qualifiers, BOOL preserveSID);
58
54
  LocaleInfo* get_locale_info_from_rb_str(VALUE rb_locale_str);
@@ -112,6 +108,7 @@ struct WinevtQuery
112
108
  BOOL preserveSID;
113
109
  LocaleInfo *localeInfo;
114
110
  EVT_HANDLE remoteHandle;
111
+ ULONGLONG unresolvedMessageCount;
115
112
  };
116
113
 
117
114
  #define SUBSCRIBE_ARRAY_SIZE 10
@@ -134,6 +131,7 @@ struct WinevtSubscribe
134
131
  BOOL preserveSID;
135
132
  LocaleInfo* localeInfo;
136
133
  EVT_HANDLE remoteHandle;
134
+ ULONGLONG unresolvedMessageCount;
137
135
  };
138
136
 
139
137
  void Init_winevt_query(VALUE rb_cEventLog);
@@ -56,7 +56,7 @@ rb_winevt_channel_alloc(VALUE klass)
56
56
  }
57
57
 
58
58
  /*
59
- * Initalize Channel class.
59
+ * Initialize Channel class.
60
60
  *
61
61
  * @return [Channel]
62
62
  *
@@ -177,6 +177,14 @@ DWORD check_subscribable_with_channel_config_type(int Id, PEVT_VARIANT pProperty
177
177
  #undef EVENT_DEBUG_TYPE
178
178
  #undef EVENT_ANALYTICAL_TYPE
179
179
 
180
+ // A per-channel failure skips that channel, but memory exhaustion is not a
181
+ // property of any channel and would silently shorten the whole list.
182
+ static BOOL
183
+ is_resource_exhaustion(DWORD status)
184
+ {
185
+ return status == ERROR_OUTOFMEMORY || status == ERROR_NOT_ENOUGH_MEMORY;
186
+ }
187
+
180
188
  /*
181
189
  * Enumerate Windows EventLog channels
182
190
  *
@@ -245,34 +253,26 @@ rb_winevt_channel_each(VALUE self)
245
253
  }
246
254
  hChannelConfig = EvtOpenChannelConfig(NULL, buffer, 0);
247
255
  if (NULL == hChannelConfig) {
248
- _snprintf_s(errBuf,
249
- _countof(errBuf),
250
- _TRUNCATE,
251
- "EvtOpenChannelConfig failed with %lu.\n",
252
- GetLastError());
253
-
254
- EvtClose(winevtChannel->channels);
255
- winevtChannel->channels = NULL;
256
+ status = GetLastError();
256
257
 
257
258
  free(buffer);
258
259
  buffer = NULL;
259
260
  bufferSize = 0;
260
261
 
261
- rb_raise(rb_eRuntimeError, errBuf);
262
- }
263
-
264
- status = is_subscribable_channel_p(hChannelConfig, winevtChannel->force_enumerate);
265
- EvtClose(hChannelConfig);
262
+ if (is_resource_exhaustion(status)) {
263
+ EvtClose(winevtChannel->channels);
264
+ winevtChannel->channels = NULL;
266
265
 
267
- if (status == ERROR_INVALID_DATA) {
268
- free(buffer);
269
- buffer = NULL;
270
- bufferSize = 0;
266
+ rb_raise(rb_eRuntimeError, "EvtOpenChannelConfig failed with %lu\n", status);
267
+ }
271
268
 
272
269
  continue;
273
270
  }
274
271
 
275
- if (status == ERROR_OUTOFMEMORY) {
272
+ status = is_subscribable_channel_p(hChannelConfig, winevtChannel->force_enumerate);
273
+ EvtClose(hChannelConfig);
274
+
275
+ if (is_resource_exhaustion(status)) {
276
276
  EvtClose(winevtChannel->channels);
277
277
  winevtChannel->channels = NULL;
278
278
 
@@ -280,16 +280,18 @@ rb_winevt_channel_each(VALUE self)
280
280
  buffer = NULL;
281
281
  bufferSize = 0;
282
282
 
283
- rb_raise(rb_eRuntimeError, "realloc failed\n");
284
- } else if (status != ERROR_SUCCESS) {
285
- EvtClose(winevtChannel->channels);
286
- winevtChannel->channels = NULL;
283
+ rb_raise(rb_eRuntimeError, "Failed to read channel configuration with %lu\n", status);
284
+ }
287
285
 
286
+ // Not fatal: Security's configuration cannot be read without elevation, and
287
+ // it is the third channel EvtOpenChannelEnum returns, so raising here
288
+ // truncated the whole enumeration to two channels.
289
+ if (status != ERROR_SUCCESS) {
288
290
  free(buffer);
289
291
  buffer = NULL;
290
292
  bufferSize = 0;
291
293
 
292
- rb_raise(rb_eRuntimeError, "is_subscribe_channel_p is failed with %ld\n", status);
294
+ continue;
293
295
  }
294
296
 
295
297
  utf8str = wstr_to_rb_str(CP_UTF8, buffer, -1);
@@ -49,7 +49,7 @@ rb_winevt_locale_alloc(VALUE klass)
49
49
  }
50
50
 
51
51
  /*
52
- * Initalize Locale class.
52
+ * Initialize Locale class.
53
53
  *
54
54
  * @return [Locale]
55
55
  *
@@ -1,5 +1,7 @@
1
1
  #include <winevt_c.h>
2
2
 
3
+ #include <ruby/thread.h>
4
+
3
5
  /* clang-format off */
4
6
  /*
5
7
  * Document-class: Winevt::EventLog::Query
@@ -39,7 +41,7 @@ close_handles(struct WinevtQuery* winevtQuery)
39
41
  winevtQuery->query = NULL;
40
42
  }
41
43
 
42
- for (int i = 0; i < winevtQuery->count; i++) {
44
+ for (ULONG i = 0; i < winevtQuery->count; i++) {
43
45
  if (winevtQuery->hEvents[i]) {
44
46
  EvtClose(winevtQuery->hEvents[i]);
45
47
  winevtQuery->hEvents[i] = NULL;
@@ -71,8 +73,35 @@ rb_winevt_query_alloc(VALUE klass)
71
73
  return obj;
72
74
  }
73
75
 
76
+ struct query_args
77
+ {
78
+ EVT_HANDLE session;
79
+ LPCWSTR channel;
80
+ LPCWSTR xpath;
81
+ DWORD flags;
82
+ EVT_HANDLE handle;
83
+ DWORD error_code;
84
+ };
85
+
86
+ /*
87
+ * GetLastError is read here rather than by the caller: it belongs to the thread
88
+ * that made the call and does not survive re-acquiring the GVL.
89
+ */
90
+ static void*
91
+ query_without_gvl(void* ptr)
92
+ {
93
+ struct query_args* args = (struct query_args*)ptr;
94
+
95
+ args->handle = EvtQuery(args->session, args->channel, args->xpath, args->flags);
96
+ if (!args->handle) {
97
+ args->error_code = GetLastError();
98
+ }
99
+
100
+ return NULL;
101
+ }
102
+
74
103
  /*
75
- * Initalize Query class.
104
+ * Initialize Query class.
76
105
  *
77
106
  * @overload initialize(channel, xpath, session=nil)
78
107
  * @param channel [String] Querying EventLog channel.
@@ -141,10 +170,30 @@ rb_winevt_query_initialize(VALUE argc, VALUE *argv, VALUE self)
141
170
 
142
171
  TypedData_Get_Struct(self, struct WinevtQuery, &rb_winevt_query_type, winevtQuery);
143
172
 
144
- winevtQuery->query = EvtQuery(
145
- hRemoteHandle, evtChannel, evtXPath, flags);
173
+ if (hRemoteHandle != NULL) {
174
+ struct query_args args;
175
+
176
+ args.session = hRemoteHandle;
177
+ args.channel = evtChannel;
178
+ args.xpath = evtXPath;
179
+ args.flags = flags;
180
+ args.handle = NULL;
181
+ args.error_code = ERROR_SUCCESS;
182
+
183
+ // EvtOpenSession only builds the session object; this is the first call
184
+ // that reaches the remote machine, so it is the one that can stall.
185
+ rb_thread_call_without_gvl(query_without_gvl, &args, NULL, NULL);
186
+
187
+ winevtQuery->query = args.handle;
188
+ err = args.error_code;
189
+ } else {
190
+ winevtQuery->query = EvtQuery(hRemoteHandle, evtChannel, evtXPath, flags);
191
+ if (winevtQuery->query == NULL) {
192
+ err = GetLastError();
193
+ }
194
+ }
195
+
146
196
  if (winevtQuery->query == NULL) {
147
- err = GetLastError();
148
197
  if (hRemoteHandle != NULL) {
149
198
  EvtClose(hRemoteHandle);
150
199
  }
@@ -160,6 +209,7 @@ rb_winevt_query_initialize(VALUE argc, VALUE *argv, VALUE self)
160
209
  winevtQuery->localeInfo = &default_locale;
161
210
  winevtQuery->remoteHandle = hRemoteHandle;
162
211
  winevtQuery->preserveSID = TRUE;
212
+ winevtQuery->unresolvedMessageCount = 0;
163
213
 
164
214
  ALLOCV_END(wchannelBuf);
165
215
  ALLOCV_END(wpathBuf);
@@ -260,8 +310,17 @@ rb_winevt_query_next(VALUE self)
260
310
  }
261
311
 
262
312
  if (status == ERROR_SUCCESS) {
313
+ // #each frees each batch in its ensure block; this covers calling #next
314
+ // directly in a loop.
315
+ for (ULONG i = 0; i < winevtQuery->count; i++) {
316
+ if (winevtQuery->hEvents[i]) {
317
+ EvtClose(winevtQuery->hEvents[i]);
318
+ winevtQuery->hEvents[i] = NULL;
319
+ }
320
+ }
321
+
263
322
  winevtQuery->count = count;
264
- for (int i = 0; i < count; i++) {
323
+ for (ULONG i = 0; i < count; i++) {
265
324
  winevtQuery->hEvents[i] = hEvents[i];
266
325
  }
267
326
 
@@ -287,12 +346,19 @@ rb_winevt_query_render(VALUE self, EVT_HANDLE event)
287
346
  }
288
347
 
289
348
  static VALUE
290
- rb_winevt_query_message(EVT_HANDLE event, LocaleInfo* localeInfo, EVT_HANDLE hRemote)
349
+ rb_winevt_query_message(struct WinevtQuery* winevtQuery, EVT_HANDLE event)
291
350
  {
292
351
  WCHAR* wResult;
293
352
  VALUE utf8str;
294
-
295
- wResult = get_description(event, localeInfo->langID, hRemote);
353
+ BOOL resolved = TRUE;
354
+
355
+ wResult = get_description(event,
356
+ winevtQuery->localeInfo->langID,
357
+ winevtQuery->remoteHandle,
358
+ &resolved);
359
+ if (!resolved) {
360
+ winevtQuery->unresolvedMessageCount++;
361
+ }
296
362
  utf8str = wstr_to_rb_str(CP_UTF8, wResult, -1);
297
363
  free(wResult);
298
364
 
@@ -382,7 +448,7 @@ rb_winevt_query_close_handle(VALUE self)
382
448
 
383
449
  TypedData_Get_Struct(self, struct WinevtQuery, &rb_winevt_query_type, winevtQuery);
384
450
 
385
- for (int i = 0; i < winevtQuery->count; i++) {
451
+ for (ULONG i = 0; i < winevtQuery->count; i++) {
386
452
  if (winevtQuery->hEvents[i] != NULL) {
387
453
  EvtClose(winevtQuery->hEvents[i]);
388
454
  winevtQuery->hEvents[i] = NULL;
@@ -401,11 +467,10 @@ rb_winevt_query_each_yield(VALUE self)
401
467
 
402
468
  TypedData_Get_Struct(self, struct WinevtQuery, &rb_winevt_query_type, winevtQuery);
403
469
 
404
- for (int i = 0; i < winevtQuery->count; i++) {
470
+ for (ULONG i = 0; i < winevtQuery->count; i++) {
405
471
  rb_yield_values(3,
406
472
  rb_winevt_query_render(self, winevtQuery->hEvents[i]),
407
- rb_winevt_query_message(winevtQuery->hEvents[i], winevtQuery->localeInfo,
408
- winevtQuery->remoteHandle),
473
+ rb_winevt_query_message(winevtQuery, winevtQuery->hEvents[i]),
409
474
  rb_winevt_query_string_inserts(winevtQuery->hEvents[i]));
410
475
  }
411
476
  return Qnil;
@@ -577,6 +642,27 @@ rb_winevt_query_preserve_sid_p(VALUE self)
577
642
  return winevtQuery->preserveSID ? Qtrue : Qfalse;
578
643
  }
579
644
 
645
+ /*
646
+ * This method returns how many events had no description message. Message
647
+ * resolution fails for reasons outside the caller's control (a provider whose
648
+ * message DLL is gone, a locale without resources), and #each degrades to an
649
+ * empty message instead of aborting the enumeration. The counter increases
650
+ * monotonically, so callers can poll it and take the difference to tell a few
651
+ * unresolvable events apart from a channel that resolves nothing.
652
+ *
653
+ * @return [Integer]
654
+ */
655
+ static VALUE
656
+ rb_winevt_query_get_unresolved_message_count(VALUE self)
657
+ {
658
+ struct WinevtQuery* winevtQuery;
659
+
660
+ TypedData_Get_Struct(
661
+ self, struct WinevtQuery, &rb_winevt_query_type, winevtQuery);
662
+
663
+ return ULL2NUM(winevtQuery->unresolvedMessageCount);
664
+ }
665
+
580
666
  /*
581
667
  * This method cancels channel query.
582
668
  *
@@ -733,6 +819,7 @@ Init_winevt_query(VALUE rb_cEventLog)
733
819
  * @since 0.11.0
734
820
  */
735
821
  rb_define_method(rb_cQuery, "preserve_sid=", rb_winevt_query_set_preserve_sid, 1);
822
+ rb_define_method(rb_cQuery, "unresolved_message_count", rb_winevt_query_get_unresolved_message_count, 0);
736
823
  /*
737
824
  * @since 0.9.1
738
825
  */
@@ -14,7 +14,7 @@
14
14
  * @session.domain = "<EXAMPLEGROUP>"
15
15
  * @session.username = "<username>"
16
16
  * @session.password = "<password>"
17
- * # Then pass @session veriable into Winevt::EventLog::Query or
17
+ * # Then pass @session variable into Winevt::EventLog::Query or
18
18
  * # Winevt::EventLog::Subscribe#subscribe
19
19
  * @query = Winevt::EventLog::Query.new(
20
20
  * "Application",
@@ -60,8 +60,11 @@ session_free(void* ptr)
60
60
  free(winevtSession->domain);
61
61
  if (winevtSession->username)
62
62
  free(winevtSession->username);
63
- if (winevtSession->password)
63
+ if (winevtSession->password) {
64
+ SecureZeroMemory(winevtSession->password,
65
+ (wcslen(winevtSession->password) + 1) * sizeof(WCHAR));
64
66
  free(winevtSession->password);
67
+ }
65
68
 
66
69
  xfree(ptr);
67
70
  }
@@ -77,7 +80,7 @@ rb_winevt_session_alloc(VALUE klass)
77
80
  }
78
81
 
79
82
  /*
80
- * Initalize Session class.
83
+ * Initialize Session class.
81
84
  *
82
85
  * @overload initialize(server, domain=nil, username=nil, password=nil, flags=Winevt::EventLog::Session::RpcLoginFlag::AuthDefault)
83
86
  * @param server [String] Server ip address or fqdn.
@@ -151,6 +154,8 @@ rb_winevt_session_set_server(VALUE self, VALUE rb_server)
151
154
  RSTRING_PTR(rb_server), RSTRING_LEN(rb_server),
152
155
  wServer, len);
153
156
  wServer[len] = L'\0';
157
+ if (winevtSession->server)
158
+ free(winevtSession->server);
154
159
  winevtSession->server = _wcsdup(wServer);
155
160
 
156
161
  ALLOCV_END(vserverBuf);
@@ -204,6 +209,8 @@ rb_winevt_session_set_domain(VALUE self, VALUE rb_domain)
204
209
  wDomain, len);
205
210
  wDomain[len] = L'\0';
206
211
 
212
+ if (winevtSession->domain)
213
+ free(winevtSession->domain);
207
214
  winevtSession->domain = _wcsdup(wDomain);
208
215
 
209
216
  ALLOCV_END(vdomainBuf);
@@ -257,6 +264,8 @@ rb_winevt_session_set_username(VALUE self, VALUE rb_username)
257
264
  wUsername, len);
258
265
  wUsername[len] = L'\0';
259
266
 
267
+ if (winevtSession->username)
268
+ free(winevtSession->username);
260
269
  winevtSession->username = _wcsdup(wUsername);
261
270
 
262
271
  ALLOCV_END(vusernameBuf);
@@ -310,6 +319,11 @@ rb_winevt_session_set_password(VALUE self, VALUE rb_password)
310
319
  wPassword, len);
311
320
  wPassword[len] = L'\0';
312
321
 
322
+ if (winevtSession->password) {
323
+ SecureZeroMemory(winevtSession->password,
324
+ (wcslen(winevtSession->password) + 1) * sizeof(WCHAR));
325
+ free(winevtSession->password);
326
+ }
313
327
  winevtSession->password = _wcsdup(wPassword);
314
328
 
315
329
  ALLOCV_END(vpasswordBuf);
@@ -408,7 +422,7 @@ Init_winevt_session(VALUE rb_cEventLog)
408
422
  */
409
423
  rb_define_const(rb_cRpcLoginFlag, "AuthDefault", LONG2NUM(EvtRpcLoginAuthDefault));
410
424
  /*
411
- * EVT_RPC_LOGIN_FLAGS enumeration: EvtRpcLoginAuthNegociate
425
+ * EVT_RPC_LOGIN_FLAGS enumeration: EvtRpcLoginAuthNegotiate
412
426
  * @see https://docs.microsoft.com/en-us/windows/win32/api/winevt/ne-winevt-evt_rpc_login_flags
413
427
  */
414
428
  rb_define_const(rb_cRpcLoginFlag, "AuthNegociate", LONG2NUM(EvtRpcLoginAuthNegotiate));
@@ -56,7 +56,7 @@ close_handles(struct WinevtSubscribe* winevtSubscribe)
56
56
  winevtSubscribe->bookmark = NULL;
57
57
  }
58
58
 
59
- for (int i = 0; i < winevtSubscribe->count; i++) {
59
+ for (DWORD i = 0; i < winevtSubscribe->count; i++) {
60
60
  if (winevtSubscribe->hEvents[i]) {
61
61
  EvtClose(winevtSubscribe->hEvents[i]);
62
62
  winevtSubscribe->hEvents[i] = NULL;
@@ -90,7 +90,7 @@ rb_winevt_subscribe_alloc(VALUE klass)
90
90
  }
91
91
 
92
92
  /*
93
- * Initalize Subscribe class.
93
+ * Initialize Subscribe class.
94
94
  *
95
95
  * @return [Subscribe]
96
96
  *
@@ -111,6 +111,7 @@ rb_winevt_subscribe_initialize(VALUE self)
111
111
  winevtSubscribe->preserveQualifiers = FALSE;
112
112
  winevtSubscribe->localeInfo = &default_locale;
113
113
  winevtSubscribe->preserveSID = TRUE;
114
+ winevtSubscribe->unresolvedMessageCount = 0;
114
115
 
115
116
  return Qnil;
116
117
  }
@@ -302,7 +303,7 @@ is_rate_limit_exceeded(struct WinevtSubscribe* winevtSubscribe)
302
303
  {
303
304
  time_t now;
304
305
 
305
- if (winevtSubscribe->rateLimit == SUBSCRIBE_RATE_INFINITE)
306
+ if (winevtSubscribe->rateLimit == (DWORD)SUBSCRIBE_RATE_INFINITE)
306
307
  return FALSE;
307
308
 
308
309
  time(&now);
@@ -323,7 +324,7 @@ update_to_reflect_rate_limit_state(struct WinevtSubscribe* winevtSubscribe, ULON
323
324
  {
324
325
  time_t lastTime = 0;
325
326
 
326
- if (winevtSubscribe->rateLimit == SUBSCRIBE_RATE_INFINITE)
327
+ if (winevtSubscribe->rateLimit == (DWORD)SUBSCRIBE_RATE_INFINITE)
327
328
  return;
328
329
 
329
330
  time(&lastTime);
@@ -397,8 +398,17 @@ rb_winevt_subscribe_next(VALUE self)
397
398
  }
398
399
 
399
400
  if (status == ERROR_SUCCESS) {
401
+ // #each frees each batch in its ensure block; this covers calling #next
402
+ // directly in a loop.
403
+ for (DWORD i = 0; i < winevtSubscribe->count; i++) {
404
+ if (winevtSubscribe->hEvents[i]) {
405
+ EvtClose(winevtSubscribe->hEvents[i]);
406
+ winevtSubscribe->hEvents[i] = NULL;
407
+ }
408
+ }
409
+
400
410
  winevtSubscribe->count = count;
401
- for (int i = 0; i < count; i++) {
411
+ for (ULONG i = 0; i < count; i++) {
402
412
  winevtSubscribe->hEvents[i] = hEvents[i];
403
413
  EvtUpdateBookmark(winevtSubscribe->bookmark, winevtSubscribe->hEvents[i]);
404
414
  }
@@ -428,12 +438,19 @@ rb_winevt_subscribe_render(VALUE self, EVT_HANDLE event)
428
438
  }
429
439
 
430
440
  static VALUE
431
- rb_winevt_subscribe_message(EVT_HANDLE event, LocaleInfo* localeInfo, EVT_HANDLE hRemote)
441
+ rb_winevt_subscribe_message(struct WinevtSubscribe* winevtSubscribe, EVT_HANDLE event)
432
442
  {
433
443
  WCHAR* wResult;
434
444
  VALUE utf8str;
435
-
436
- wResult = get_description(event, localeInfo->langID, hRemote);
445
+ BOOL resolved = TRUE;
446
+
447
+ wResult = get_description(event,
448
+ winevtSubscribe->localeInfo->langID,
449
+ winevtSubscribe->remoteHandle,
450
+ &resolved);
451
+ if (!resolved) {
452
+ winevtSubscribe->unresolvedMessageCount++;
453
+ }
437
454
  utf8str = wstr_to_rb_str(CP_UTF8, wResult, -1);
438
455
  free(wResult);
439
456
 
@@ -454,7 +471,7 @@ rb_winevt_subscribe_close_handle(VALUE self)
454
471
  TypedData_Get_Struct(
455
472
  self, struct WinevtSubscribe, &rb_winevt_subscribe_type, winevtSubscribe);
456
473
 
457
- for (int i = 0; i < winevtSubscribe->count; i++) {
474
+ for (DWORD i = 0; i < winevtSubscribe->count; i++) {
458
475
  if (winevtSubscribe->hEvents[i] != NULL) {
459
476
  EvtClose(winevtSubscribe->hEvents[i]);
460
477
  winevtSubscribe->hEvents[i] = NULL;
@@ -473,11 +490,10 @@ rb_winevt_subscribe_each_yield(VALUE self)
473
490
  TypedData_Get_Struct(
474
491
  self, struct WinevtSubscribe, &rb_winevt_subscribe_type, winevtSubscribe);
475
492
 
476
- for (int i = 0; i < winevtSubscribe->count; i++) {
493
+ for (DWORD i = 0; i < winevtSubscribe->count; i++) {
477
494
  rb_yield_values(3,
478
495
  rb_winevt_subscribe_render(self, winevtSubscribe->hEvents[i]),
479
- rb_winevt_subscribe_message(winevtSubscribe->hEvents[i], winevtSubscribe->localeInfo,
480
- winevtSubscribe->remoteHandle),
496
+ rb_winevt_subscribe_message(winevtSubscribe, winevtSubscribe->hEvents[i]),
481
497
  rb_winevt_subscribe_string_inserts(winevtSubscribe->hEvents[i]));
482
498
  }
483
499
 
@@ -557,7 +573,7 @@ rb_winevt_subscribe_set_rate_limit(VALUE self, VALUE rb_rate_limit)
557
573
 
558
574
  rateLimit = NUM2LONG(rb_rate_limit);
559
575
 
560
- if ((rateLimit != SUBSCRIBE_RATE_INFINITE) && (rateLimit < 10 || rateLimit % 10)) {
576
+ if ((rateLimit != (DWORD)SUBSCRIBE_RATE_INFINITE) && (rateLimit < 10 || rateLimit % 10)) {
561
577
  rb_raise(rb_eArgError, "Specify a multiples of 10 or RATE_INFINITE constant");
562
578
  } else {
563
579
  winevtSubscribe->rateLimit = rateLimit;
@@ -714,6 +730,27 @@ rb_winevt_subscribe_preserve_sid_p(VALUE self)
714
730
  return winevtSubscribe->preserveSID ? Qtrue : Qfalse;
715
731
  }
716
732
 
733
+ /*
734
+ * This method returns how many events had no description message. Message
735
+ * resolution fails for reasons outside the caller's control (a provider whose
736
+ * message DLL is gone, a locale without resources), and #each degrades to an
737
+ * empty message instead of aborting the enumeration. The counter increases
738
+ * monotonically, so callers can poll it and take the difference to tell a few
739
+ * unresolvable events apart from a channel that resolves nothing.
740
+ *
741
+ * @return [Integer]
742
+ */
743
+ static VALUE
744
+ rb_winevt_subscribe_get_unresolved_message_count(VALUE self)
745
+ {
746
+ struct WinevtSubscribe* winevtSubscribe;
747
+
748
+ TypedData_Get_Struct(
749
+ self, struct WinevtSubscribe, &rb_winevt_subscribe_type, winevtSubscribe);
750
+
751
+ return ULL2NUM(winevtSubscribe->unresolvedMessageCount);
752
+ }
753
+
717
754
  /*
718
755
  * This method cancels channel subscription.
719
756
  *
@@ -819,6 +856,7 @@ Init_winevt_subscribe(VALUE rb_cEventLog)
819
856
  * @since 0.11.0
820
857
  */
821
858
  rb_define_method(rb_cSubscribe, "preserve_sid=", rb_winevt_subscribe_set_preserve_sid, 1);
859
+ rb_define_method(rb_cSubscribe, "unresolved_message_count", rb_winevt_subscribe_get_unresolved_message_count, 0);
822
860
  /*
823
861
  * @since 0.9.1
824
862
  */
@@ -2,7 +2,6 @@
2
2
 
3
3
  #include <sddl.h>
4
4
  #include <stdlib.h>
5
- #include <string>
6
5
  #include <vector>
7
6
 
8
7
  VALUE
@@ -127,18 +126,6 @@ connect_to_remote(LPWSTR computerName, LPWSTR domain, LPWSTR username, LPWSTR pa
127
126
  return hRemote;
128
127
  }
129
128
 
130
- static std::wstring
131
- guid_to_wstr(const GUID& guid)
132
- {
133
- LPOLESTR p = nullptr;
134
- if (FAILED(StringFromCLSID(guid, &p))) {
135
- return std::wstring();
136
- }
137
- std::wstring s(p);
138
- CoTaskMemFree(p);
139
- return s;
140
- }
141
-
142
129
  static VALUE
143
130
  make_displayable_binary_string(PBYTE bin, size_t length)
144
131
  {
@@ -184,8 +171,7 @@ extract_user_evt_variants(PEVT_VARIANT pRenderedValues, DWORD propCount)
184
171
  if (pRenderedValues[i].StringVal == nullptr) {
185
172
  rb_ary_push(userValues, rb_utf8_str_new_cstr("(NULL)"));
186
173
  } else {
187
- std::wstring wStr(pRenderedValues[i].StringVal);
188
- rbObj = wstr_to_rb_str(CP_UTF8, &wStr[0], -1);
174
+ rbObj = wstr_to_rb_str(CP_UTF8, pRenderedValues[i].StringVal, -1);
189
175
  rb_ary_push(userValues, rbObj);
190
176
  }
191
177
  break;
@@ -250,10 +236,9 @@ extract_user_evt_variants(PEVT_VARIANT pRenderedValues, DWORD propCount)
250
236
  break;
251
237
  case EvtVarTypeGuid:
252
238
  if (pRenderedValues[i].GuidVal != nullptr) {
253
- const GUID guid = *pRenderedValues[i].GuidVal;
254
- std::wstring wstr = guid_to_wstr(guid);
255
- if (!wstr.empty()) {
256
- rbObj = wstr_to_rb_str(CP_UTF8, wstr.c_str(), -1);
239
+ WCHAR wsGuid[50];
240
+ if (StringFromGUID2(*pRenderedValues[i].GuidVal, wsGuid, _countof(wsGuid)) > 0) {
241
+ rbObj = wstr_to_rb_str(CP_UTF8, wsGuid, -1);
257
242
  } else {
258
243
  rbObj = rb_utf8_str_new_cstr("?");
259
244
  }
@@ -408,130 +393,74 @@ get_values(EVT_HANDLE handle)
408
393
  return userValues;
409
394
  }
410
395
 
396
+ /*
397
+ * These statuses still leave a usable (if partial) message in the buffer. The
398
+ * Windows SDK sample (GetEventRawDescription.cpp) and Zabbix
399
+ * (src/zabbix_agent/eventlog.c) treat exactly this set the same way.
400
+ */
401
+ static bool
402
+ is_partial_message(ULONG status)
403
+ {
404
+ return status == ERROR_SUCCESS ||
405
+ status == ERROR_EVT_UNRESOLVED_VALUE_INSERT ||
406
+ status == ERROR_EVT_UNRESOLVED_PARAMETER_INSERT ||
407
+ status == ERROR_EVT_MAX_INSERTS_REACHED;
408
+ }
409
+
410
+ /*
411
+ * Returns the formatted message, or an empty vector when it cannot be resolved.
412
+ * The buffer is sized to BUFSIZE / the size EvtFormatMessage asked for, not to
413
+ * the string, so it carries trailing L'\0' padding: read it as a NUL-terminated
414
+ * string, never by size(), or the padding comes along.
415
+ */
411
416
  static std::vector<WCHAR>
412
417
  get_message(EVT_HANDLE hMetadata, EVT_HANDLE handle)
413
418
  {
414
419
  #define BUFSIZE 4096
415
420
  std::vector<WCHAR> result;
416
- ULONG status;
417
- ULONG bufferSizeNeeded = 0;
418
- LPVOID lpMsgBuf;
419
421
  std::vector<WCHAR> message(BUFSIZE);
422
+ ULONG bufferSizeNeeded = 0;
423
+ ULONG status;
420
424
 
421
- if (!EvtFormatMessage(hMetadata,
422
- handle,
423
- 0xffffffff,
424
- 0,
425
- nullptr,
426
- EvtFormatMessageEvent,
427
- message.size(),
428
- &message[0],
429
- &bufferSizeNeeded)) {
430
- status = GetLastError();
431
-
432
- if (status != ERROR_EVT_UNRESOLVED_VALUE_INSERT) {
433
- switch (status) {
434
- case ERROR_EVT_MESSAGE_NOT_FOUND:
435
- case ERROR_EVT_MESSAGE_ID_NOT_FOUND:
436
- case ERROR_EVT_MESSAGE_LOCALE_NOT_FOUND:
437
- case ERROR_RESOURCE_DATA_NOT_FOUND:
438
- case ERROR_RESOURCE_TYPE_NOT_FOUND:
439
- case ERROR_RESOURCE_NAME_NOT_FOUND:
440
- case ERROR_RESOURCE_LANG_NOT_FOUND:
441
- case ERROR_MUI_FILE_NOT_FOUND:
442
- case ERROR_EVT_UNRESOLVED_PARAMETER_INSERT: {
443
- if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
444
- FORMAT_MESSAGE_IGNORE_INSERTS,
445
- nullptr,
446
- status,
447
- MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
448
- reinterpret_cast<WCHAR*>(&lpMsgBuf),
449
- 0,
450
- nullptr) == 0)
451
- FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
452
- FORMAT_MESSAGE_IGNORE_INSERTS,
453
- nullptr,
454
- status,
455
- MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
456
- reinterpret_cast<WCHAR*>(&lpMsgBuf),
457
- 0,
458
- nullptr);
459
-
460
- std::wstring ret(reinterpret_cast<WCHAR*>(lpMsgBuf));
461
- std::copy(ret.begin(), ret.end(), std::back_inserter(result));
462
- result.push_back(L'\0');
463
- LocalFree(lpMsgBuf);
464
-
465
- goto cleanup;
466
- }
467
- }
425
+ if (EvtFormatMessage(hMetadata,
426
+ handle,
427
+ 0xffffffff,
428
+ 0,
429
+ nullptr,
430
+ EvtFormatMessageEvent,
431
+ message.size(),
432
+ &message[0],
433
+ &bufferSizeNeeded)) {
434
+ return message;
435
+ }
468
436
 
469
- if (status != ERROR_INSUFFICIENT_BUFFER)
470
- rb_raise(rb_eWinevtQueryError, "ErrorCode: %lu", status);
437
+ status = GetLastError();
438
+
439
+ if (status == ERROR_INSUFFICIENT_BUFFER) {
440
+ message.resize(bufferSizeNeeded);
441
+
442
+ if (EvtFormatMessage(hMetadata,
443
+ handle,
444
+ 0xffffffff,
445
+ 0,
446
+ nullptr,
447
+ EvtFormatMessageEvent,
448
+ message.size(),
449
+ &message.front(),
450
+ &bufferSizeNeeded)) {
451
+ return message;
471
452
  }
472
453
 
473
- if (status == ERROR_INSUFFICIENT_BUFFER) {
474
- message.resize(bufferSizeNeeded);
475
- message.shrink_to_fit();
476
-
477
- if (!EvtFormatMessage(hMetadata,
478
- handle,
479
- 0xffffffff,
480
- 0,
481
- nullptr,
482
- EvtFormatMessageEvent,
483
- message.size(),
484
- &message.front(),
485
- &bufferSizeNeeded)) {
486
- status = GetLastError();
487
-
488
- if (status != ERROR_EVT_UNRESOLVED_VALUE_INSERT) {
489
- switch (status) {
490
- case ERROR_EVT_MESSAGE_NOT_FOUND:
491
- case ERROR_EVT_MESSAGE_ID_NOT_FOUND:
492
- case ERROR_EVT_MESSAGE_LOCALE_NOT_FOUND:
493
- case ERROR_RESOURCE_DATA_NOT_FOUND:
494
- case ERROR_RESOURCE_TYPE_NOT_FOUND:
495
- case ERROR_RESOURCE_NAME_NOT_FOUND:
496
- case ERROR_RESOURCE_LANG_NOT_FOUND:
497
- case ERROR_MUI_FILE_NOT_FOUND:
498
- case ERROR_EVT_UNRESOLVED_PARAMETER_INSERT:
499
- if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
500
- FORMAT_MESSAGE_FROM_SYSTEM |
501
- FORMAT_MESSAGE_IGNORE_INSERTS,
502
- nullptr,
503
- status,
504
- MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
505
- reinterpret_cast<WCHAR*>(&lpMsgBuf),
506
- 0,
507
- nullptr) == 0)
508
- FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
509
- FORMAT_MESSAGE_FROM_SYSTEM |
510
- FORMAT_MESSAGE_IGNORE_INSERTS,
511
- nullptr,
512
- status,
513
- MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
514
- reinterpret_cast<WCHAR*>(&lpMsgBuf),
515
- 0,
516
- nullptr);
517
-
518
- std::wstring ret(reinterpret_cast<WCHAR*>(lpMsgBuf));
519
- std::copy(ret.begin(), ret.end(), std::back_inserter(result));
520
- result.push_back(L'\0');
521
- LocalFree(lpMsgBuf);
522
-
523
- goto cleanup;
524
- }
525
-
526
- rb_raise(rb_eWinevtQueryError, "ErrorCode: %lu", status);
527
- }
528
- }
529
- }
454
+ status = GetLastError();
530
455
  }
531
456
 
532
- result = message;
533
-
534
- cleanup:
457
+ // Message resolution fails for open-ended reasons -- a message DLL removed
458
+ // from disk, a locale without resources, anything LoadLibraryEx surfaces --
459
+ // so there is no error set to enumerate. Raising on "unknown" statuses would
460
+ // let one unresolvable event abort the whole #each enumeration.
461
+ if (is_partial_message(status)) {
462
+ result = message;
463
+ }
535
464
 
536
465
  return result;
537
466
 
@@ -539,27 +468,37 @@ cleanup:
539
468
  }
540
469
 
541
470
  WCHAR*
542
- get_description(EVT_HANDLE handle, LANGID langID, EVT_HANDLE hRemote)
471
+ get_description(EVT_HANDLE handle, LANGID langID, EVT_HANDLE hRemote, BOOL* resolved)
543
472
  {
544
473
  #define BUFSIZE 4096
545
- std::vector<WCHAR> buffer(BUFSIZE);
474
+ // rb_raise longjmps out of this frame without running C++ destructors, and
475
+ // both error paths below raise. Ruby owns this buffer so the GC still
476
+ // reclaims it on those paths; a std::vector here would leak its storage.
477
+ VALUE vbuffer;
478
+ WCHAR* buffer = RB_ALLOCV_N(WCHAR, vbuffer, BUFSIZE);
546
479
  ULONG bufferSizeNeeded = 0;
547
480
  ULONG status, count;
548
481
  std::vector<WCHAR> result;
549
482
  EVT_HANDLE hMetadata = nullptr;
550
483
 
484
+ if (resolved)
485
+ *resolved = TRUE;
486
+
551
487
  static PCWSTR eventProperties[] = { L"Event/System/Provider/@Name" };
552
488
  EVT_HANDLE renderContext =
553
489
  EvtCreateRenderContext(1, eventProperties, EvtRenderContextValues);
554
490
  if (renderContext == nullptr) {
491
+ RB_ALLOCV_END(vbuffer);
555
492
  rb_raise(rb_eWinevtQueryError, "Failed to create renderContext");
556
493
  }
557
494
 
495
+ // EvtRender sizes in bytes, not characters, and this call has no retry: a
496
+ // short size turns into ERROR_INSUFFICIENT_BUFFER and raises below.
558
497
  if (EvtRender(renderContext,
559
498
  handle,
560
499
  EvtRenderEventValues,
561
- buffer.size(),
562
- &buffer.front(),
500
+ BUFSIZE * sizeof(WCHAR),
501
+ buffer,
563
502
  &bufferSizeNeeded,
564
503
  &count) != FALSE) {
565
504
  status = ERROR_SUCCESS;
@@ -569,11 +508,20 @@ get_description(EVT_HANDLE handle, LANGID langID, EVT_HANDLE hRemote)
569
508
 
570
509
  if (status != ERROR_SUCCESS) {
571
510
  EvtClose(renderContext);
511
+ RB_ALLOCV_END(vbuffer);
572
512
  raise_system_error(rb_eWinevtQueryError, status);
573
513
  }
574
514
 
575
- // Obtain buffer as EVT_VARIANT pointer. To avoid ErrorCide 87 in EvtRender.
576
- const PEVT_VARIANT values = reinterpret_cast<PEVT_VARIANT>(&buffer.front());
515
+ // Obtain buffer as EVT_VARIANT pointer. To avoid ErrorCode 87 in EvtRender.
516
+ const PEVT_VARIANT values = reinterpret_cast<PEVT_VARIANT>(buffer);
517
+
518
+ // EvtRender reports what it actually wrote, and the buffer is not zeroed, so
519
+ // values[0] is only meaningful once count says it was filled in. A truncated
520
+ // event -- more likely arriving over a remote session -- would otherwise hand
521
+ // EvtOpenPublisherMetadata whatever the buffer happened to contain.
522
+ if (count == 0 || values[0].Type != EvtVarTypeString) {
523
+ goto cleanup;
524
+ }
577
525
 
578
526
  // Open publisher metadata
579
527
  hMetadata = EvtOpenPublisherMetadata(
@@ -594,13 +542,19 @@ get_description(EVT_HANDLE handle, LANGID langID, EVT_HANDLE hRemote)
594
542
 
595
543
  cleanup:
596
544
 
545
+ RB_ALLOCV_END(vbuffer);
546
+
597
547
  if (renderContext)
598
548
  EvtClose(renderContext);
599
549
 
600
550
  if (hMetadata)
601
551
  EvtClose(hMetadata);
602
552
 
553
+ // Both ways of giving up -- publisher metadata that cannot be opened, and
554
+ // EvtFormatMessage failing to resolve -- end up here with an empty result.
603
555
  if (result.empty()) {
556
+ if (resolved)
557
+ *resolved = FALSE;
604
558
  return _wcsdup(L"");
605
559
  }
606
560
 
@@ -713,7 +667,10 @@ render_system_event(EVT_HANDLE hEvent, BOOL preserve_qualifiers, BOOL preserveSI
713
667
  DWORD dwBufferSize = 0;
714
668
  DWORD dwBufferUsed = 0;
715
669
  DWORD dwPropertyCount = 0;
716
- VALUE vRenderedValues;
670
+ // The error path below runs RB_ALLOCV_END() whether or not RB_ALLOCV() was
671
+ // reached, and RB_ALLOCV_END() dereferences anything non-zero. Zero, not
672
+ // Qnil, is the value it treats as "nothing was allocated".
673
+ VALUE vRenderedValues = 0;
717
674
  PEVT_VARIANT pRenderedValues = NULL;
718
675
  WCHAR wsGuid[50];
719
676
  LPSTR pwsSid = NULL;
@@ -858,16 +815,20 @@ render_system_event(EVT_HANDLE hEvent, BOOL preserve_qualifiers, BOOL preserveSI
858
815
  rb_str_new2("TimeCreated"),
859
816
  Qnil);
860
817
  }
861
- _snprintf_s(buffer,
862
- _countof(buffer),
863
- _TRUNCATE,
864
- "%llu",
865
- pRenderedValues[EvtSystemEventRecordId].UInt64Val);
866
- rb_hash_aset(hash,
867
- rb_str_new2("EventRecordID"),
868
- (EvtVarTypeNull == pRenderedValues[EvtSystemEventRecordId].UInt64Val)
869
- ? Qnil
870
- : rb_str_new2(buffer));
818
+ if (EvtVarTypeNull != pRenderedValues[EvtSystemEventRecordId].Type) {
819
+ _snprintf_s(buffer,
820
+ _countof(buffer),
821
+ _TRUNCATE,
822
+ "%llu",
823
+ pRenderedValues[EvtSystemEventRecordId].UInt64Val);
824
+ rb_hash_aset(hash,
825
+ rb_str_new2("EventRecordID"),
826
+ rb_str_new2(buffer));
827
+ } else {
828
+ rb_hash_aset(hash,
829
+ rb_str_new2("EventRecordID"),
830
+ Qnil);
831
+ }
871
832
 
872
833
  if (EvtVarTypeNull != pRenderedValues[EvtSystemActivityID].Type) {
873
834
  const GUID* Guid = pRenderedValues[EvtSystemActivityID].GuidVal;
@@ -1,3 +1,3 @@
1
1
  module Winevt
2
- VERSION = "0.11.5"
2
+ VERSION = "0.11.6"
3
3
  end
data/winevt_c.gemspec CHANGED
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
23
23
  spec.require_paths = ["lib"]
24
24
  spec.extensions = ["ext/winevt/extconf.rb"]
25
25
  spec.license = "Apache-2.0"
26
- spec.required_ruby_version = Gem::Requirement.new(">= 2.4".freeze)
26
+ spec.required_ruby_version = Gem::Requirement.new(">= 3.2".freeze)
27
27
 
28
28
  spec.add_development_dependency "bundler"
29
29
  spec.add_development_dependency "rake", "~> 13.0"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: winevt_c
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.5
4
+ version: 0.11.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hiroshi Hatake
@@ -132,14 +132,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
132
132
  requirements:
133
133
  - - ">="
134
134
  - !ruby/object:Gem::Version
135
- version: '2.4'
135
+ version: '3.2'
136
136
  required_rubygems_version: !ruby/object:Gem::Requirement
137
137
  requirements:
138
138
  - - ">="
139
139
  - !ruby/object:Gem::Version
140
140
  version: '0'
141
141
  requirements: []
142
- rubygems_version: 4.0.10
142
+ rubygems_version: 4.0.16
143
143
  specification_version: 4
144
144
  summary: Windows Event Log API bindings from winevt.h.
145
145
  test_files: []