master
js 466 lines 17.8 KB
Raw
1 /*
2 Copyright 2021 Intel Corporation
3
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15 */
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
34 const LocatorFunctions = ['QueryInterface', 'AddRef', 'Release', 'ConnectToServer'];
35
36 //
37 // Reference for IWbemServices can be found at:
38 // https://learn.microsoft.com/en-us/windows/win32/api/wbemcli/nn-wbemcli-iwbemservices
39 //
40 const ServiceFunctions = [
41 'QueryInterface',
42 'AddRef',
43 'Release',
44 'OpenNamespace',
45 'CancelAsyncCall',
46 'QueryObjectSink',
47 'GetObject',
48 'GetObjectAsync',
49 'PutClass',
50 'PutClassAsync',
51 'DeleteClass',
52 'DeleteClassAsync',
53 'CreateClassEnum',
54 'CreateClassEnumAsync',
55 'PutInstance',
56 'PutInstanceAsync',
57 'DeleteInstance',
58 'DeleteInstanceAsync',
59 'CreateInstanceEnum',
60 'CreateInstanceEnumAsync',
61 'ExecQuery',
62 'ExecQueryAsync',
63 'ExecNotificationQuery',
64 'ExecNotificationQueryAsync',
65 'ExecMethod',
66 'ExecMethodAsync'
67 ];
68
69 //
70 // Reference to IEnumWbemClassObject can be found at:
71 // https://learn.microsoft.com/en-us/windows/win32/api/wbemcli/nn-wbemcli-ienumwbemclassobject
72 //
73 const ResultsFunctions = [
74 'QueryInterface',
75 'AddRef',
76 'Release',
77 'Reset',
78 'Next',
79 'NextAsync',
80 'Clone',
81 'Skip'
82 ];
83
84 //
85 // Reference to IWbemClassObject can be found at:
86 // https://learn.microsoft.com/en-us/windows/win32/api/wbemcli/nn-wbemcli-iwbemclassobject
87 //
88 const ResultFunctions = [
89 'QueryInterface',
90 'AddRef',
91 'Release',
92 'GetQualifierSet',
93 'Get',
94 'Put',
95 'Delete',
96 'GetNames',
97 'BeginEnumeration',
98 'Next',
99 'EndEnumeration',
100 'GetPropertyQualifierSet',
101 'Clone',
102 'GetObjectText',
103 'SpawnDerivedClass',
104 'SpawnInstance',
105 'CompareTo',
106 'GetPropertyOrigin',
107 'InheritsFrom',
108 'GetMethod',
109 'PutMethod',
110 'DeleteMethod',
111 'BeginMethodEnumeration',
112 'NextMethod',
113 'EndMethodEnumeration',
114 'GetMethodQualifierSet',
115 'GetMethodOrigin'
116 ];
117
118 //
119 // Reference to IWbemObjectSink can be found at:
120 // https://learn.microsoft.com/en-us/windows/win32/wmisdk/iwbemobjectsink
121 //
122 const QueryAsyncHandler =
123 [
124 {
125 cx: 10, parms: 3, name: 'QueryInterface', func: function (j, riid, ppv)
126 {
127 var ret = GM.CreateVariable(4);
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
132 case '0178857C8173CF11884D00AA004B2E24': // IID_IWmiObjectSink
133 j.pointerBuffer().copy(ppv.Deref(0, GM.PointerSize).toBuffer());
134 ret.increment(0, true);
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);
140 //console.log(riid.Deref(0, 16).toBuffer().toString('hex'), 'returning E_NOINTERFACE');
141 break;
142 }
143
144 return ret;
145 }
146 },
147 {
148 cx: 11, parms: 1, name: 'AddRef', func: function ()
149 {
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 {
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 {
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 {
171 j = arr.Deref((i * GM.PointerSize) + 0, GM.PointerSize);
172 this.results.push(enumerateProperties(j, this.fields));
173 }
174
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 {
181 //console.log('SetStatus');
182 if (hResult.Val == 0)
183 {
184 this.p.resolve(this.results);
185 }
186 else
187 {
188 this.p.reject(new Error('WMI async query error: 0x' + (hResult.Val >>> 0).toString(16)));
189 }
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 {
224 //
225 // Reference to SafeArrayAccessData() can be found at:
226 // https://learn.microsoft.com/en-us/windows/win32/api/oleauto/nf-oleauto-safearrayaccessdata
227 //
228
229 var nme, len, nn;
230 var properties = [];
231 var values = {};
232
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))
237 {
238 properties = fields;
239 }
240 else
241 {
242 nme = GM.CreatePointer();
243 j.funcs.GetNames(j.Deref(), 0, WBEM_FLAG_ALWAYS, 0, nme);
244 len = nme.Deref().Deref(GM.PointerSize == 8 ? 24 : 16, 4).toBuffer().readUInt32LE();
245 nn = GM.CreatePointer();
246 OleAut32.SafeArrayAccessData(nme.Deref(), nn);
247
248
249 for (var i = 0; i < len; ++i)
250 {
251 var propName = nn.Deref().increment(i * GM.PointerSize).Deref().Wide2UTF8;
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
259 for (var i = 0; i < properties.length; ++i)
260 {
261 var tmp1 = GM.CreateVariable(24);
262 if (j.funcs.Get(j.Deref(), GM.CreateVariable(properties[i], { wide: true }), 0, tmp1, 0, 0).Val == 0)
263 {
264 //
265 // Reference for IWbemClassObject::Get() can be found at:
266 // https://learn.microsoft.com/en-us/windows/win32/api/wbemcli/nf-wbemcli-iwbemclassobject-get
267 //
268
269 var vartype = tmp1.toBuffer().readUInt16LE();
270 var isArray = (vartype & 0x2000) != 0; // VT_ARRAY flag
271 var baseType = vartype & 0x0FFF;
272
273 if (isArray)
274 {
275 // Handle array types (VT_ARRAY | base type)
276 var safeArray = tmp1.Deref(8, GM.PointerSize).Deref();
277 var arrayLength = safeArray.Deref(GM.PointerSize == 8 ? 24 : 16, 4).toBuffer().readUInt32LE();
278 var arrayData = GM.CreatePointer();
279 OleAut32.SafeArrayAccessData(safeArray, arrayData);
280
281 var arrayValues = [];
282 for (var k = 0; k < arrayLength; ++k)
283 {
284 switch (baseType)
285 {
286 case 0x0002: // VT_I2
287 arrayValues.push(arrayData.Deref().Deref(k * 2, 2).toBuffer().readInt16LE());
288 break;
289 case 0x0003: // VT_I4
290 case 0x0016: // VT_INT
291 arrayValues.push(arrayData.Deref().Deref(k * 4, 4).toBuffer().readInt32LE());
292 break;
293 case 0x000B: // VT_BOOL
294 arrayValues.push(arrayData.Deref().Deref(k * 2, 2).toBuffer().readInt16LE() != 0);
295 break;
296 case 0x0010: // VT_I1
297 arrayValues.push(arrayData.Deref().Deref(k, 1).toBuffer().readInt8());
298 break;
299 case 0x0011: // VT_UI1
300 arrayValues.push(arrayData.Deref().Deref(k, 1).toBuffer().readUInt8());
301 break;
302 case 0x0012: // VT_UI2
303 arrayValues.push(arrayData.Deref().Deref(k * 2, 2).toBuffer().readUInt16LE());
304 break;
305 case 0x0013: // VT_UI4
306 case 0x0017: // VT_UINT
307 arrayValues.push(arrayData.Deref().Deref(k * 4, 4).toBuffer().readUInt32LE());
308 break;
309 case 0x0008: // VT_BSTR
310 arrayValues.push(arrayData.Deref().Deref(k * GM.PointerSize, GM.PointerSize).Deref().Wide2UTF8);
311 break;
312 }
313 }
314 OleAut32.SafeArrayUnaccessData(safeArray);
315 values[properties[i]] = arrayValues;
316 }
317 else
318 {
319 // Handle scalar types
320 switch (vartype)
321 {
322 case 0x0000: // VT_EMPTY
323 case 0x0001: // VT_NULL
324 values[properties[i]] = null;
325 break;
326 case 0x0002: // VT_I2
327 values[properties[i]] = tmp1.Deref(8, GM.PointerSize).toBuffer().readInt16LE();
328 break;
329 case 0x0003: // VT_I4
330 case 0x0016: // VT_INT
331 values[properties[i]] = tmp1.Deref(8, GM.PointerSize).toBuffer().readInt32LE();
332 break;
333 case 0x000B: // VT_BOOL
334 values[properties[i]] = tmp1.Deref(8, GM.PointerSize).toBuffer().readInt32LE() != 0;
335 break;
336 case 0x000E: // VT_DECIMAL
337 break;
338 case 0x0010: // VT_I1
339 values[properties[i]] = tmp1.Deref(8, GM.PointerSize).toBuffer().readInt8();
340 break;
341 case 0x0011: // VT_UI1
342 values[properties[i]] = tmp1.Deref(8, GM.PointerSize).toBuffer().readUInt8();
343 break;
344 case 0x0012: // VT_UI2
345 values[properties[i]] = tmp1.Deref(8, GM.PointerSize).toBuffer().readUInt16LE();
346 break;
347 case 0x0013: // VT_UI4
348 case 0x0017: // VT_UINT
349 values[properties[i]] = tmp1.Deref(8, GM.PointerSize).toBuffer().readUInt32LE();
350 break;
351 //case 0x0014: // VT_I8
352 // break;
353 //case 0x0015: // VT_UI8
354 // break;
355 case 0x0008: // VT_BSTR
356 values[properties[i]] = tmp1.Deref(8, GM.PointerSize).Deref().Wide2UTF8;
357 break;
358 default:
359 console.info1('VARTYPE: ' + vartype);
360 break;
361 }
362 }
363 }
364 }
365
366 return (values);
367 }
368
369 function queryAsync(resourceString, queryString, fields)
370 {
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
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 {
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
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
466 module.exports = { query: query, queryAsync: queryAsync };