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