Win-wmi-fixed: fix resource handling (#7836)

* Add SafeArrayUnaccessData see: https://learn.microsoft.com/en-us/windows/win32/api/oleauto/nf-oleauto-safearrayaccessdata * Add try/catch/finally to handle resource cleanup * Add try/catch to queryAsync() to handle resource cleanup * Add wmi service check * Fix COM refcount and returns * Replace inline requires * Consolidate COM release * Add safeguard against multiple queryAsync() on 32-bit windows 32-bit windows cannot do more than 1 async query at a time because of the hardcoded vtable for the cx pre-compiled __stdcall custom handlers in iLibDuktape_GenericMarshal.c * Missed replacement requires

PTR committed May 28, 2026 at 00:04 UTC de987635e3acae0c56e9110ce81b8a4995c951c9
1 file changed +135 -116
agents/modules_meshcore/win-wmi-fixed.js
+135 -116
@@ -16,14 +16,18 @@ limitations under the License.
16
17 var promise = require('promise');
18 var GM = require('_GenericMarshal');
19 +var COM = require('win-com');
20 +var sm = require('service-manager');
21 const CLSID_WbemAdministrativeLocator = '{CB8555CC-9128-11D1-AD9B-00C04FD8FDFF}';
22 const IID_WbemLocator = '{dc12a687-737f-11cf-884d-00aa004b2e24}';
23 const WBEM_FLAG_BIDIRECTIONAL = 0;
24 const WBEM_INFINITE = -1;
25 const WBEM_FLAG_ALWAYS = 0;
26 const E_NOINTERFACE = 0x80004002;
27 +const WBEM_S_NO_ERROR = 0;
28 var OleAut32 = GM.CreateNativeProxy('OleAut32.dll');
29 OleAut32.CreateMethod('SafeArrayAccessData');
30 +OleAut32.CreateMethod('SafeArrayUnaccessData');
31
32 var wmi_handlers = {};
33
@@ -121,71 +125,46 @@ const QueryAsyncHandler =
125 cx: 10, parms: 3, name: 'QueryInterface', func: function (j, riid, ppv)
126 {
127 var ret = GM.CreateVariable(4);
124 - console.info1('QueryInterface', riid.Deref(0, 16).toBuffer().toString('hex'));
128 + // console.log('QueryInterface', riid.Deref(0, 16).toBuffer().toString('hex'));
129 switch (riid.Deref(0, 16).toBuffer().toString('hex'))
130 {
131 case '0000000000000000C000000000000046': // IID_IUnknown
128 - j.pointerBuffer().copy(ppv.Deref(0, GM.PointerSize).toBuffer());
129 - ret.increment(0, true);
130 - //++this.p.refcount;
131 - console.info1('QueryInterface (IID_IUnknown)', this.refcount);
132 - break;
132 case '0178857C8173CF11884D00AA004B2E24': // IID_IWmiObjectSink
133 j.pointerBuffer().copy(ppv.Deref(0, GM.PointerSize).toBuffer());
134 ret.increment(0, true);
136 - //++this.p.refcount;
137 - console.info1('QueryInterface (IID_IWmiObjectSink)', this.refcount);
135 + ++this.refcount;
136 + //console.log('QueryInterface ' + riid.Deref(0, 16).toBuffer().toString('hex') + ' refcount: ' + this.refcount);
137 break;
138 default:
139 ret.increment(E_NOINTERFACE, true);
141 - console.info1(riid.Deref(0, 16).toBuffer().toString('hex'), 'returning E_NOINTERFACE');
140 + //console.log(riid.Deref(0, 16).toBuffer().toString('hex'), 'returning E_NOINTERFACE');
141 break;
142 }
143
145 - return (ret);
144 + return ret;
145 }
146 },
147 {
148 cx: 11, parms: 1, name: 'AddRef', func: function ()
149 {
151 - ++this.refcount;
152 - console.info1('AddRef', this.refcount);
153 - return (GM.CreateVariable(4));
150 + //console.log('AddRef: ' + this.refcount);
151 + return (GM.CreateVariable(4).increment(++this.refcount, true));
152 }
153 },
154 {
155 cx: 12, parms: 1, name: 'Release', func: function ()
156 {
159 - --this.refcount;
160 - console.info1('Release', this.refcount);
161 - if (this.refcount == 0)
162 - {
163 - console.info1('No More References');
164 -
165 - this.cleanup();
166 - this.services.funcs.Release(this.services.Deref());
167 -
168 - this.services = null;
169 - this.p = null;
170 - if (this.callbackDispatched)
171 - {
172 - setImmediate(function (j) { j.locator = null; }, this);
173 - }
174 - else
175 - {
176 - this.locator = null;
177 - }
178 -
179 - console.info1('No More References [END]');
180 - }
181 - return (GM.CreateVariable(4));
157 + //console.log('Release: ' + this.refcount);
158 + //--this.refcount;
159 + if (--this.refcount === 0) { destroy(this); }
160 + return GM.CreateVariable(4).increment(this.refcount >>> 0, true);
161 }
162 },
163 {
164 cx: 13, parms: 3, name: 'Indicate', func: function (j, count, arr)
165 {
187 - console.info1('Indicate', count.Val);
188 - var j, nme, len, nn;
166 + //console.log('Indicate: ' + count.Val);
167 + if (!this.results) return GM.CreateVariable(4).increment(0, true);
168
169 for (var i = 0; i < count.Val; ++i)
170 {
@@ -193,32 +172,52 @@ const QueryAsyncHandler =
172 this.results.push(enumerateProperties(j, this.fields));
173 }
174
196 - var ret = GM.CreateVariable(4);
197 - ret.increment(0, true);
198 - return (ret);
175 + return GM.CreateVariable(4).increment(0, true);
176 }
177 },
178 {
179 cx: 14, parms: 5, name: 'SetStatus', func: function (j, lFlags, hResult, strParam, pObjParam)
180 {
204 - console.info1('SetStatus', hResult.Val);
205 -
206 - var ret = GM.CreateVariable(4);
207 - ret.increment(0, true);
208 -
181 + //console.log('SetStatus');
182 if (hResult.Val == 0)
183 {
184 this.p.resolve(this.results);
185 }
186 else
187 {
215 - this.p.reject(hResult.Val);
188 + this.p.reject(new Error('WMI async query error: 0x' + (hResult.Val >>> 0).toString(16)));
189 }
217 - return (ret);
190 + var self = this;
191 + setImmediate(function () {
192 + // console.log('SetStatus refcount: ' + self.refcount);
193 + if (--self.refcount === 0) { destroy(self); }
194 + });
195 + return GM.CreateVariable(4).increment(0, true);
196 }
197 }
198 ];
199
200 +function destroy(h) {
201 + if (h.cleanup) { h.cleanup(); }
202 + h.p = null;
203 + delete wmi_handlers[h._hashCode()];
204 + h.services = releaseCOM(h.services, true);
205 +
206 + if (h.callbackDispatched) {
207 + setImmediate(function () { h.locator = releaseCOM(h.locator, false); });
208 + } else {
209 + h.locator = releaseCOM(h.locator, false);
210 + }
211 +}
212 +
213 +function releaseCOM(obj, deref) {
214 + if (obj && obj.funcs) {
215 + try { obj.funcs.Release(deref ? obj.Deref() : obj); }
216 + catch (e) { console.log('releaseCOM error: ' + (e && e.message ? e.message : e)); }
217 + }
218 + return null;
219 +}
220 +
221
222 function enumerateProperties(j, fields)
223 {
@@ -231,7 +230,7 @@ function enumerateProperties(j, fields)
230 var properties = [];
231 var values = {};
232
234 - j.funcs = require('win-com').marshalFunctions(j.Deref(), ResultFunctions);
233 + j.funcs = COM.marshalFunctions(j.Deref(), ResultFunctions);
234
235 // First we need to enumerate the COM Array
236 if (fields != null && Array.isArray(fields))
@@ -253,6 +252,7 @@ function enumerateProperties(j, fields)
252 if (propName.length === 0) { continue; }
253 properties.push(propName);
254 }
255 + OleAut32.SafeArrayUnaccessData(nme.Deref());
256 }
257
258 // Now we need to introspect the Array Fields
@@ -311,6 +311,7 @@ function enumerateProperties(j, fields)
311 break;
312 }
313 }
314 + OleAut32.SafeArrayUnaccessData(safeArray);
315 values[properties[i]] = arrayValues;
316 }
317 else
@@ -367,80 +368,98 @@ function enumerateProperties(j, fields)
368
369 function queryAsync(resourceString, queryString, fields)
370 {
370 - var p = new promise(require('promise').defaultInit);
371 - var resource = GM.CreateVariable(resourceString, { wide: true });
372 - var language = GM.CreateVariable("WQL", { wide: true });
373 - var query = GM.CreateVariable(queryString, { wide: true });
374 - var results = GM.CreatePointer();
375 -
376 - // Setup the Async COM handler for QueryAsync()
377 - var handlers = require('win-com').marshalInterface(QueryAsyncHandler);
378 - handlers.refcount = 1;
379 - handlers.results = [];
380 - handlers.fields = fields;
381 - handlers.locator = require('win-com').createInstance(require('win-com').CLSIDFromString(CLSID_WbemAdministrativeLocator), require('win-com').IID_IUnknown);
382 - handlers.locator.funcs = require('win-com').marshalFunctions(handlers.locator, LocatorFunctions);
383 -
384 - handlers.services = require('_GenericMarshal').CreatePointer();
385 -
386 - // For easier debugging in case a certain WMI component is not available
387 - var hr = handlers.locator.funcs.ConnectToServer(handlers.locator, resource, 0, 0, 0, 0, 0, 0, handlers.services).Val;
388 - if (hr != 0) {
389 - var hex = (hr < 0 ? hr + 0x100000000 : hr).toString(16).toUpperCase();
390 - throw ('queryAsync: Error calling ConnectToServer: HRESULT=0x' + hex + ' resource=' + resourceString);
391 - }
392 -
393 - handlers.services.funcs = require('win-com').marshalFunctions(handlers.services.Deref(), ServiceFunctions);
394 - handlers.p = p;
395 -
396 - // Make the COM call
397 - if (handlers.services.funcs.ExecQueryAsync(handlers.services.Deref(), language, query, WBEM_FLAG_BIDIRECTIONAL, 0, handlers).Val != 0)
398 - {
399 - throw ('Error in Query');
400 - }
371 + var queryStarted = false;
372 + try {
373 + var s = sm.manager.getService('winmgmt');
374 + if (!s.isRunning()) { throw new Error ('WMI service not running')};
375 + //32-bit windows cannot do more than 1 async query at a time because of the hardcoded vtable for the cx pre-compiled __stdcall custom handlers in iLibDuktape_GenericMarshal.c
376 + if (GM.PointerSize == 4 && Object.keys(wmi_handlers).length != 0) {
377 + throw new Error('Another AsyncQuery is already running, only one AsyncQuery possible at a time on 32-bit Windows'); }
378 + var p = new promise(promise.defaultInit);
379 + var resource = GM.CreateVariable(resourceString, { wide: true });
380 + var language = GM.CreateVariable("WQL", { wide: true });
381 + var query = GM.CreateVariable(queryString, { wide: true });
382 +
383 + // Setup the Async COM handler for QueryAsync()
384 + var handlers = COM.marshalInterface(QueryAsyncHandler);
385 + handlers.refcount = 1;
386 + handlers.results = [];
387 + handlers.fields = fields;
388 + handlers.locator = COM.createInstance(COM.CLSIDFromString(CLSID_WbemAdministrativeLocator), COM.IID_IUnknown);
389 + handlers.locator.funcs = COM.marshalFunctions(handlers.locator, LocatorFunctions);
390 +
391 + handlers.services = GM.CreatePointer();
392 +
393 + // For easier debugging in case a certain WMI component is not available
394 + var hr = handlers.locator.funcs.ConnectToServer(handlers.locator, resource, 0, 0, 0, 0, 0, 0, handlers.services).Val;
395 + if (hr != 0) {
396 + var hex = (hr < 0 ? hr + 0x100000000 : hr).toString(16).toUpperCase();
397 + throw ('queryAsync: Error calling ConnectToServer: HRESULT=0x' + hex + ' resource=' + resourceString);
398 + }
399
402 - // Hold a reference to the callback object
403 - wmi_handlers[handlers._hashCode()] = handlers;
400 + handlers.services.funcs = COM.marshalFunctions(handlers.services.Deref(), ServiceFunctions);
401 + handlers.p = p;
402 +
403 + // Make the COM call
404 + if (handlers.services.funcs.ExecQueryAsync(handlers.services.Deref(), language, query, WBEM_FLAG_BIDIRECTIONAL, 0, handlers).Val != 0) { throw new Error('Error in Query'); }
405 + queryStarted = true;
406 + // Hold a reference to the callback object
407 + wmi_handlers[handlers._hashCode()] = handlers;
408 + } catch (e) {
409 + console.log('win-wmi queryAsync error: ' + e.message);
410 + if (!queryStarted && handlers) {
411 + handlers.refcount = 0;
412 + destroy(handlers);
413 + }
414 + throw (e);
415 + }
416 return (p);
417 }
418 +
419 function query(resourceString, queryString, fields)
420 {
408 - var resource = GM.CreateVariable(resourceString, { wide: true });
409 - var language = GM.CreateVariable("WQL", { wide: true });
410 - var query = GM.CreateVariable(queryString, { wide: true });
411 - var results = GM.CreatePointer();
412 -
413 - // Connect the locator connection for WMI
414 - var locator = require('win-com').createInstance(require('win-com').CLSIDFromString(CLSID_WbemAdministrativeLocator), require('win-com').IID_IUnknown);
415 - locator.funcs = require('win-com').marshalFunctions(locator, LocatorFunctions);
416 - var services = require('_GenericMarshal').CreatePointer();
417 -
418 - // For easier debugging in case a certain WMI component is not available
419 - var hr = locator.funcs.ConnectToServer(locator, resource, 0, 0, 0, 0, 0, 0, services).Val;
420 - if (hr != 0) {
421 - var hex = (hr < 0 ? hr + 0x100000000 : hr).toString(16).toUpperCase();
422 - throw ('query: Error calling ConnectToServer: HRESULT=0x' + hex + ' resource=' + resourceString);
423 - }
424 -
425 - // Execute the Query
426 - services.funcs = require('win-com').marshalFunctions(services.Deref(), ServiceFunctions);
427 - if (services.funcs.ExecQuery(services.Deref(), language, query, WBEM_FLAG_BIDIRECTIONAL, 0, results).Val != 0) { throw ('Error in Query'); }
428 -
429 - results.funcs = require('win-com').marshalFunctions(results.Deref(), ResultsFunctions);
430 - var returnedCount = GM.CreateVariable(8);
431 - var result = GM.CreatePointer();
432 - var ret = [];
433 -
434 - // Enumerate the results
435 - while (results.funcs.Next(results.Deref(), WBEM_INFINITE, 1, result, returnedCount).Val == 0)
436 - {
437 - ret.push(enumerateProperties(result, fields));
438 - }
421 + try {
422 + var s = sm.manager.getService('winmgmt');
423 + if (!s.isRunning()) { throw new Error ('WMI service not running')};
424 + var resource = GM.CreateVariable(resourceString, { wide: true });
425 + var language = GM.CreateVariable("WQL", { wide: true });
426 + var query = GM.CreateVariable(queryString, { wide: true });
427 + var results = GM.CreatePointer();
428 +
429 + // Connect the locator connection for WMI
430 + var locator = COM.createInstance(COM.CLSIDFromString(CLSID_WbemAdministrativeLocator), COM.IID_IUnknown);
431 + locator.funcs = COM.marshalFunctions(locator, LocatorFunctions);
432 + var services = GM.CreatePointer();
433 +
434 + // For easier debugging in case a certain WMI component is not available
435 + var hr = locator.funcs.ConnectToServer(locator, resource, 0, 0, 0, 0, 0, 0, services).Val;
436 + if (hr != 0) {
437 + var hex = (hr < 0 ? hr + 0x100000000 : hr).toString(16).toUpperCase();
438 + throw ('query: Error calling ConnectToServer: HRESULT=0x' + hex + ' resource=' + resourceString);
439 + }
440
440 - results.funcs.Release(results.Deref());
441 - services.funcs.Release(services.Deref());
442 - locator.funcs.Release(locator);
441 + // Execute the Query
442 + services.funcs = COM.marshalFunctions(services.Deref(), ServiceFunctions);
443 + if (services.funcs.ExecQuery(services.Deref(), language, query, WBEM_FLAG_BIDIRECTIONAL, 0, results).Val != 0) { throw ('Error in Query'); }
444
445 + results.funcs = COM.marshalFunctions(results.Deref(), ResultsFunctions);
446 + var returnedCount = GM.CreateVariable(8);
447 + var result = GM.CreatePointer();
448 + var ret = [];
449 +
450 + // Enumerate the results
451 + while (results.funcs.Next(results.Deref(), WBEM_INFINITE, 1, result, returnedCount).Val == 0)
452 + {
453 + ret.push(enumerateProperties(result, fields));
454 + }
455 + } catch (e) {
456 + console.log('win-wmi query error: ' + e.message);
457 + throw (e);
458 + } finally {
459 + results = releaseCOM(results, true);
460 + services = releaseCOM(services, true);
461 + locator = releaseCOM(locator, false);
462 + }
463 return (ret);
464 }
465