wmi.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603
  1. //go:build windows
  2. // +build windows
  3. /*
  4. Package wmi provides a WQL interface for WMI on Windows.
  5. Example code to print names of running processes:
  6. type Win32_Process struct {
  7. Name string
  8. }
  9. func main() {
  10. var dst []Win32_Process
  11. q := wmi.CreateQuery(&dst, "")
  12. err := wmi.Query(q, &dst)
  13. if err != nil {
  14. log.Fatal(err)
  15. }
  16. for i, v := range dst {
  17. println(i, v.Name)
  18. }
  19. }
  20. */
  21. package wmi
  22. import (
  23. "bytes"
  24. "errors"
  25. "fmt"
  26. "log"
  27. "os"
  28. "reflect"
  29. "runtime"
  30. "strconv"
  31. "strings"
  32. "sync"
  33. "time"
  34. "github.com/go-ole/go-ole"
  35. "github.com/go-ole/go-ole/oleutil"
  36. )
  37. var l = log.New(os.Stdout, "", log.LstdFlags)
  38. var (
  39. ErrInvalidEntityType = errors.New("wmi: invalid entity type")
  40. // ErrNilCreateObject is the error returned if CreateObject returns nil even
  41. // if the error was nil.
  42. ErrNilCreateObject = errors.New("wmi: create object returned nil")
  43. lock sync.Mutex
  44. )
  45. // S_FALSE is returned by CoInitializeEx if it was already called on this thread.
  46. const S_FALSE = 0x00000001
  47. // QueryNamespace invokes Query with the given namespace on the local machine.
  48. func QueryNamespace(query string, dst interface{}, namespace string) error {
  49. return Query(query, dst, nil, namespace)
  50. }
  51. // Query runs the WQL query and appends the values to dst.
  52. //
  53. // dst must have type *[]S or *[]*S, for some struct type S. Fields selected in
  54. // the query must have the same name in dst. Supported types are all signed and
  55. // unsigned integers, time.Time, string, bool, or a pointer to one of those.
  56. // Array types are not supported.
  57. //
  58. // By default, the local machine and default namespace are used. These can be
  59. // changed using connectServerArgs. See
  60. // https://docs.microsoft.com/en-us/windows/desktop/WmiSdk/swbemlocator-connectserver
  61. // for details.
  62. //
  63. // Query is a wrapper around DefaultClient.Query.
  64. func Query(query string, dst interface{}, connectServerArgs ...interface{}) error {
  65. if DefaultClient.SWbemServicesClient == nil {
  66. return DefaultClient.Query(query, dst, connectServerArgs...)
  67. }
  68. return DefaultClient.SWbemServicesClient.Query(query, dst, connectServerArgs...)
  69. }
  70. // CallMethod calls a method named methodName on an instance of the class named
  71. // className, with the given params.
  72. //
  73. // CallMethod is a wrapper around DefaultClient.CallMethod.
  74. func CallMethod(connectServerArgs []interface{}, className, methodName string, params []interface{}) (int32, error) {
  75. return DefaultClient.CallMethod(connectServerArgs, className, methodName, params)
  76. }
  77. // A Client is an WMI query client.
  78. //
  79. // Its zero value (DefaultClient) is a usable client.
  80. type Client struct {
  81. // NonePtrZero specifies if nil values for fields which aren't pointers
  82. // should be returned as the field types zero value.
  83. //
  84. // Setting this to true allows stucts without pointer fields to be used
  85. // without the risk failure should a nil value returned from WMI.
  86. NonePtrZero bool
  87. // PtrNil specifies if nil values for pointer fields should be returned
  88. // as nil.
  89. //
  90. // Setting this to true will set pointer fields to nil where WMI
  91. // returned nil, otherwise the types zero value will be returned.
  92. PtrNil bool
  93. // AllowMissingFields specifies that struct fields not present in the
  94. // query result should not result in an error.
  95. //
  96. // Setting this to true allows custom queries to be used with full
  97. // struct definitions instead of having to define multiple structs.
  98. AllowMissingFields bool
  99. // SWbemServiceClient is an optional SWbemServices object that can be
  100. // initialized and then reused across multiple queries. If it is null
  101. // then the method will initialize a new temporary client each time.
  102. SWbemServicesClient *SWbemServices
  103. }
  104. // DefaultClient is the default Client and is used by Query, QueryNamespace, and CallMethod.
  105. var DefaultClient = &Client{}
  106. // coinitService coinitializes WMI service. If no error is returned, a cleanup function
  107. // is returned which must be executed (usually deferred) to clean up allocated resources.
  108. func (c *Client) coinitService(connectServerArgs ...interface{}) (*ole.IDispatch, func(), error) {
  109. var unknown *ole.IUnknown
  110. var wmi *ole.IDispatch
  111. var serviceRaw *ole.VARIANT
  112. // be sure teardown happens in the reverse
  113. // order from that which they were created
  114. deferFn := func() {
  115. if serviceRaw != nil {
  116. serviceRaw.Clear()
  117. }
  118. if wmi != nil {
  119. wmi.Release()
  120. }
  121. if unknown != nil {
  122. unknown.Release()
  123. }
  124. ole.CoUninitialize()
  125. }
  126. // if we error'ed here, clean up immediately
  127. var err error
  128. defer func() {
  129. if err != nil {
  130. deferFn()
  131. }
  132. }()
  133. err = ole.CoInitializeEx(0, ole.COINIT_MULTITHREADED)
  134. if err != nil {
  135. oleCode := err.(*ole.OleError).Code()
  136. if oleCode != ole.S_OK && oleCode != S_FALSE {
  137. return nil, nil, err
  138. }
  139. }
  140. unknown, err = oleutil.CreateObject("WbemScripting.SWbemLocator")
  141. if err != nil {
  142. return nil, nil, err
  143. } else if unknown == nil {
  144. return nil, nil, ErrNilCreateObject
  145. }
  146. wmi, err = unknown.QueryInterface(ole.IID_IDispatch)
  147. if err != nil {
  148. return nil, nil, err
  149. }
  150. // service is a SWbemServices
  151. serviceRaw, err = oleutil.CallMethod(wmi, "ConnectServer", connectServerArgs...)
  152. if err != nil {
  153. return nil, nil, err
  154. }
  155. return serviceRaw.ToIDispatch(), deferFn, nil
  156. }
  157. // CallMethod calls a WMI method named methodName on an instance
  158. // of the class named className. It passes in the arguments given
  159. // in params. Use connectServerArgs to customize the machine and
  160. // namespace; by default, the local machine and default namespace
  161. // are used. See
  162. // https://docs.microsoft.com/en-us/windows/desktop/WmiSdk/swbemlocator-connectserver
  163. // for details.
  164. func (c *Client) CallMethod(connectServerArgs []interface{}, className, methodName string, params []interface{}) (int32, error) {
  165. service, cleanup, err := c.coinitService(connectServerArgs...)
  166. if err != nil {
  167. return 0, fmt.Errorf("coinit: %v", err)
  168. }
  169. defer cleanup()
  170. // Get class
  171. classRaw, err := oleutil.CallMethod(service, "Get", className)
  172. if err != nil {
  173. return 0, fmt.Errorf("CallMethod Get class %s: %v", className, err)
  174. }
  175. class := classRaw.ToIDispatch()
  176. defer classRaw.Clear()
  177. // Run method
  178. resultRaw, err := oleutil.CallMethod(class, methodName, params...)
  179. if err != nil {
  180. return 0, fmt.Errorf("CallMethod %s.%s: %v", className, methodName, err)
  181. }
  182. resultInt, ok := resultRaw.Value().(int32)
  183. if !ok {
  184. return 0, fmt.Errorf("return value was not an int32: %v (%T)", resultRaw, resultRaw)
  185. }
  186. return resultInt, nil
  187. }
  188. // Query runs the WQL query and appends the values to dst.
  189. //
  190. // dst must have type *[]S or *[]*S, for some struct type S. Fields selected in
  191. // the query must have the same name in dst. Supported types are all signed and
  192. // unsigned integers, time.Time, string, bool, or a pointer to one of those.
  193. // Array types are not supported.
  194. //
  195. // By default, the local machine and default namespace are used. These can be
  196. // changed using connectServerArgs. See
  197. // https://docs.microsoft.com/en-us/windows/desktop/WmiSdk/swbemlocator-connectserver
  198. // for details.
  199. func (c *Client) Query(query string, dst interface{}, connectServerArgs ...interface{}) error {
  200. dv := reflect.ValueOf(dst)
  201. if dv.Kind() != reflect.Ptr || dv.IsNil() {
  202. return ErrInvalidEntityType
  203. }
  204. dv = dv.Elem()
  205. mat, elemType := checkMultiArg(dv)
  206. if mat == multiArgTypeInvalid {
  207. return ErrInvalidEntityType
  208. }
  209. lock.Lock()
  210. defer lock.Unlock()
  211. runtime.LockOSThread()
  212. defer runtime.UnlockOSThread()
  213. service, cleanup, err := c.coinitService(connectServerArgs...)
  214. if err != nil {
  215. return err
  216. }
  217. defer cleanup()
  218. // result is a SWBemObjectSet
  219. resultRaw, err := oleutil.CallMethod(service, "ExecQuery", query)
  220. if err != nil {
  221. return err
  222. }
  223. result := resultRaw.ToIDispatch()
  224. defer resultRaw.Clear()
  225. count, err := oleInt64(result, "Count")
  226. if err != nil {
  227. return err
  228. }
  229. enumProperty, err := result.GetProperty("_NewEnum")
  230. if err != nil {
  231. return err
  232. }
  233. defer enumProperty.Clear()
  234. enum, err := enumProperty.ToIUnknown().IEnumVARIANT(ole.IID_IEnumVariant)
  235. if err != nil {
  236. return err
  237. }
  238. if enum == nil {
  239. return fmt.Errorf("can't get IEnumVARIANT, enum is nil")
  240. }
  241. defer enum.Release()
  242. // Initialize a slice with Count capacity
  243. dv.Set(reflect.MakeSlice(dv.Type(), 0, int(count)))
  244. var errFieldMismatch error
  245. for itemRaw, length, err := enum.Next(1); length > 0; itemRaw, length, err = enum.Next(1) {
  246. if err != nil {
  247. return err
  248. }
  249. err := func() error {
  250. // item is a SWbemObject, but really a Win32_Process
  251. item := itemRaw.ToIDispatch()
  252. defer item.Release()
  253. ev := reflect.New(elemType)
  254. if err = c.loadEntity(ev.Interface(), item); err != nil {
  255. if _, ok := err.(*ErrFieldMismatch); ok {
  256. // We continue loading entities even in the face of field mismatch errors.
  257. // If we encounter any other error, that other error is returned. Otherwise,
  258. // an ErrFieldMismatch is returned.
  259. errFieldMismatch = err
  260. } else {
  261. return err
  262. }
  263. }
  264. if mat != multiArgTypeStructPtr {
  265. ev = ev.Elem()
  266. }
  267. dv.Set(reflect.Append(dv, ev))
  268. return nil
  269. }()
  270. if err != nil {
  271. return err
  272. }
  273. }
  274. return errFieldMismatch
  275. }
  276. // ErrFieldMismatch is returned when a field is to be loaded into a different
  277. // type than the one it was stored from, or when a field is missing or
  278. // unexported in the destination struct.
  279. // StructType is the type of the struct pointed to by the destination argument.
  280. type ErrFieldMismatch struct {
  281. StructType reflect.Type
  282. FieldName string
  283. Reason string
  284. }
  285. func (e *ErrFieldMismatch) Error() string {
  286. return fmt.Sprintf("wmi: cannot load field %q into a %q: %s",
  287. e.FieldName, e.StructType, e.Reason)
  288. }
  289. var timeType = reflect.TypeOf(time.Time{})
  290. // loadEntity loads a SWbemObject into a struct pointer.
  291. func (c *Client) loadEntity(dst interface{}, src *ole.IDispatch) (errFieldMismatch error) {
  292. v := reflect.ValueOf(dst).Elem()
  293. for i := 0; i < v.NumField(); i++ {
  294. f := v.Field(i)
  295. of := f
  296. isPtr := f.Kind() == reflect.Ptr
  297. n := v.Type().Field(i).Name
  298. if n[0] < 'A' || n[0] > 'Z' {
  299. continue
  300. }
  301. if !f.CanSet() {
  302. return &ErrFieldMismatch{
  303. StructType: of.Type(),
  304. FieldName: n,
  305. Reason: "CanSet() is false",
  306. }
  307. }
  308. prop, err := oleutil.GetProperty(src, n)
  309. if err != nil {
  310. if !c.AllowMissingFields {
  311. errFieldMismatch = &ErrFieldMismatch{
  312. StructType: of.Type(),
  313. FieldName: n,
  314. Reason: "no such struct field",
  315. }
  316. }
  317. continue
  318. }
  319. defer prop.Clear()
  320. if isPtr && !(c.PtrNil && prop.VT == 0x1) {
  321. ptr := reflect.New(f.Type().Elem())
  322. f.Set(ptr)
  323. f = f.Elem()
  324. }
  325. if prop.VT == 0x1 { //VT_NULL
  326. continue
  327. }
  328. switch val := prop.Value().(type) {
  329. case int8, int16, int32, int64, int:
  330. v := reflect.ValueOf(val).Int()
  331. switch f.Kind() {
  332. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
  333. f.SetInt(v)
  334. case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
  335. f.SetUint(uint64(v))
  336. default:
  337. return &ErrFieldMismatch{
  338. StructType: of.Type(),
  339. FieldName: n,
  340. Reason: "not an integer class",
  341. }
  342. }
  343. case uint8, uint16, uint32, uint64:
  344. v := reflect.ValueOf(val).Uint()
  345. switch f.Kind() {
  346. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
  347. f.SetInt(int64(v))
  348. case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
  349. f.SetUint(v)
  350. default:
  351. return &ErrFieldMismatch{
  352. StructType: of.Type(),
  353. FieldName: n,
  354. Reason: "not an integer class",
  355. }
  356. }
  357. case string:
  358. switch f.Kind() {
  359. case reflect.String:
  360. f.SetString(val)
  361. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
  362. iv, err := strconv.ParseInt(val, 10, 64)
  363. if err != nil {
  364. return err
  365. }
  366. f.SetInt(iv)
  367. case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
  368. uv, err := strconv.ParseUint(val, 10, 64)
  369. if err != nil {
  370. return err
  371. }
  372. f.SetUint(uv)
  373. case reflect.Struct:
  374. switch f.Type() {
  375. case timeType:
  376. if len(val) == 25 {
  377. mins, err := strconv.Atoi(val[22:])
  378. if err != nil {
  379. return err
  380. }
  381. val = val[:22] + fmt.Sprintf("%02d%02d", mins/60, mins%60)
  382. }
  383. t, err := time.Parse("20060102150405.000000-0700", val)
  384. if err != nil {
  385. return err
  386. }
  387. f.Set(reflect.ValueOf(t))
  388. }
  389. }
  390. case bool:
  391. switch f.Kind() {
  392. case reflect.Bool:
  393. f.SetBool(val)
  394. default:
  395. return &ErrFieldMismatch{
  396. StructType: of.Type(),
  397. FieldName: n,
  398. Reason: "not a bool",
  399. }
  400. }
  401. case float32:
  402. switch f.Kind() {
  403. case reflect.Float32:
  404. f.SetFloat(float64(val))
  405. default:
  406. return &ErrFieldMismatch{
  407. StructType: of.Type(),
  408. FieldName: n,
  409. Reason: "not a Float32",
  410. }
  411. }
  412. case float64:
  413. switch f.Kind() {
  414. case reflect.Float32, reflect.Float64:
  415. f.SetFloat(val)
  416. default:
  417. return &ErrFieldMismatch{
  418. StructType: of.Type(),
  419. FieldName: n,
  420. Reason: "not a Float64",
  421. }
  422. }
  423. default:
  424. if f.Kind() == reflect.Slice {
  425. switch f.Type().Elem().Kind() {
  426. case reflect.String:
  427. safeArray := prop.ToArray()
  428. if safeArray != nil {
  429. arr := safeArray.ToValueArray()
  430. fArr := reflect.MakeSlice(f.Type(), len(arr), len(arr))
  431. for i, v := range arr {
  432. s := fArr.Index(i)
  433. s.SetString(v.(string))
  434. }
  435. f.Set(fArr)
  436. }
  437. case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
  438. safeArray := prop.ToArray()
  439. if safeArray != nil {
  440. arr := safeArray.ToValueArray()
  441. fArr := reflect.MakeSlice(f.Type(), len(arr), len(arr))
  442. for i, v := range arr {
  443. s := fArr.Index(i)
  444. s.SetUint(reflect.ValueOf(v).Uint())
  445. }
  446. f.Set(fArr)
  447. }
  448. case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
  449. safeArray := prop.ToArray()
  450. if safeArray != nil {
  451. arr := safeArray.ToValueArray()
  452. fArr := reflect.MakeSlice(f.Type(), len(arr), len(arr))
  453. for i, v := range arr {
  454. s := fArr.Index(i)
  455. s.SetInt(reflect.ValueOf(v).Int())
  456. }
  457. f.Set(fArr)
  458. }
  459. default:
  460. return &ErrFieldMismatch{
  461. StructType: of.Type(),
  462. FieldName: n,
  463. Reason: fmt.Sprintf("unsupported slice type (%T)", val),
  464. }
  465. }
  466. } else {
  467. typeof := reflect.TypeOf(val)
  468. if typeof == nil && (isPtr || c.NonePtrZero) {
  469. if (isPtr && c.PtrNil) || (!isPtr && c.NonePtrZero) {
  470. of.Set(reflect.Zero(of.Type()))
  471. }
  472. break
  473. }
  474. return &ErrFieldMismatch{
  475. StructType: of.Type(),
  476. FieldName: n,
  477. Reason: fmt.Sprintf("unsupported type (%T)", val),
  478. }
  479. }
  480. }
  481. }
  482. return errFieldMismatch
  483. }
  484. type multiArgType int
  485. const (
  486. multiArgTypeInvalid multiArgType = iota
  487. multiArgTypeStruct
  488. multiArgTypeStructPtr
  489. )
  490. // checkMultiArg checks that v has type []S, []*S for some struct type S.
  491. //
  492. // It returns what category the slice's elements are, and the reflect.Type
  493. // that represents S.
  494. func checkMultiArg(v reflect.Value) (m multiArgType, elemType reflect.Type) {
  495. if v.Kind() != reflect.Slice {
  496. return multiArgTypeInvalid, nil
  497. }
  498. elemType = v.Type().Elem()
  499. switch elemType.Kind() {
  500. case reflect.Struct:
  501. return multiArgTypeStruct, elemType
  502. case reflect.Ptr:
  503. elemType = elemType.Elem()
  504. if elemType.Kind() == reflect.Struct {
  505. return multiArgTypeStructPtr, elemType
  506. }
  507. }
  508. return multiArgTypeInvalid, nil
  509. }
  510. func oleInt64(item *ole.IDispatch, prop string) (int64, error) {
  511. v, err := oleutil.GetProperty(item, prop)
  512. if err != nil {
  513. return 0, err
  514. }
  515. defer v.Clear()
  516. i := int64(v.Val)
  517. return i, nil
  518. }
  519. // CreateQuery returns a WQL query string that queries all columns of src. where
  520. // is an optional string that is appended to the query, to be used with WHERE
  521. // clauses. In such a case, the "WHERE" string should appear at the beginning.
  522. // The wmi class is obtained by the name of the type. You can pass a optional
  523. // class throught the variadic class parameter which is useful for anonymous
  524. // structs.
  525. func CreateQuery(src interface{}, where string, class ...string) string {
  526. var b bytes.Buffer
  527. b.WriteString("SELECT ")
  528. s := reflect.Indirect(reflect.ValueOf(src))
  529. t := s.Type()
  530. if s.Kind() == reflect.Slice {
  531. t = t.Elem()
  532. }
  533. if t.Kind() != reflect.Struct {
  534. return ""
  535. }
  536. var fields []string
  537. for i := 0; i < t.NumField(); i++ {
  538. fields = append(fields, t.Field(i).Name)
  539. }
  540. b.WriteString(strings.Join(fields, ", "))
  541. b.WriteString(" FROM ")
  542. if len(class) > 0 {
  543. b.WriteString(class[0])
  544. } else {
  545. b.WriteString(t.Name())
  546. }
  547. b.WriteString(" " + where)
  548. return b.String()
  549. }