| 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 }; |