swbemservices.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. // +build windows
  2. package wmi
  3. import (
  4. "fmt"
  5. "reflect"
  6. "runtime"
  7. "sync"
  8. "github.com/go-ole/go-ole"
  9. "github.com/go-ole/go-ole/oleutil"
  10. )
  11. // SWbemServices is used to access wmi. See https://msdn.microsoft.com/en-us/library/aa393719(v=vs.85).aspx
  12. type SWbemServices struct {
  13. //TODO: track namespace. Not sure if we can re connect to a different namespace using the same instance
  14. cWMIClient *Client //This could also be an embedded struct, but then we would need to branch on Client vs SWbemServices in the Query method
  15. sWbemLocatorIUnknown *ole.IUnknown
  16. sWbemLocatorIDispatch *ole.IDispatch
  17. queries chan *queryRequest
  18. closeError chan error
  19. lQueryorClose sync.Mutex
  20. }
  21. type queryRequest struct {
  22. query string
  23. dst interface{}
  24. args []interface{}
  25. finished chan error
  26. }
  27. // InitializeSWbemServices will return a new SWbemServices object that can be used to query WMI
  28. func InitializeSWbemServices(c *Client, connectServerArgs ...interface{}) (*SWbemServices, error) {
  29. //fmt.Println("InitializeSWbemServices: Starting")
  30. //TODO: implement connectServerArgs as optional argument for init with connectServer call
  31. s := new(SWbemServices)
  32. s.cWMIClient = c
  33. s.queries = make(chan *queryRequest)
  34. initError := make(chan error)
  35. go s.process(initError)
  36. err, ok := <-initError
  37. if ok {
  38. return nil, err //Send error to caller
  39. }
  40. //fmt.Println("InitializeSWbemServices: Finished")
  41. return s, nil
  42. }
  43. // Close will clear and release all of the SWbemServices resources
  44. func (s *SWbemServices) Close() error {
  45. s.lQueryorClose.Lock()
  46. if s == nil || s.sWbemLocatorIDispatch == nil {
  47. s.lQueryorClose.Unlock()
  48. return fmt.Errorf("SWbemServices is not Initialized")
  49. }
  50. if s.queries == nil {
  51. s.lQueryorClose.Unlock()
  52. return fmt.Errorf("SWbemServices has been closed")
  53. }
  54. //fmt.Println("Close: sending close request")
  55. var result error
  56. ce := make(chan error)
  57. s.closeError = ce //Race condition if multiple callers to close. May need to lock here
  58. close(s.queries) //Tell background to shut things down
  59. s.lQueryorClose.Unlock()
  60. err, ok := <-ce
  61. if ok {
  62. result = err
  63. }
  64. //fmt.Println("Close: finished")
  65. return result
  66. }
  67. func (s *SWbemServices) process(initError chan error) {
  68. //fmt.Println("process: starting background thread initialization")
  69. //All OLE/WMI calls must happen on the same initialized thead, so lock this goroutine
  70. runtime.LockOSThread()
  71. defer runtime.UnlockOSThread()
  72. err := ole.CoInitializeEx(0, ole.COINIT_MULTITHREADED)
  73. if err != nil {
  74. oleCode := err.(*ole.OleError).Code()
  75. if oleCode != ole.S_OK && oleCode != S_FALSE {
  76. initError <- fmt.Errorf("ole.CoInitializeEx error: %v", err)
  77. return
  78. }
  79. }
  80. defer ole.CoUninitialize()
  81. unknown, err := oleutil.CreateObject("WbemScripting.SWbemLocator")
  82. if err != nil {
  83. initError <- fmt.Errorf("CreateObject SWbemLocator error: %v", err)
  84. return
  85. } else if unknown == nil {
  86. initError <- ErrNilCreateObject
  87. return
  88. }
  89. defer unknown.Release()
  90. s.sWbemLocatorIUnknown = unknown
  91. dispatch, err := s.sWbemLocatorIUnknown.QueryInterface(ole.IID_IDispatch)
  92. if err != nil {
  93. initError <- fmt.Errorf("SWbemLocator QueryInterface error: %v", err)
  94. return
  95. }
  96. defer dispatch.Release()
  97. s.sWbemLocatorIDispatch = dispatch
  98. // we can't do the ConnectServer call outside the loop unless we find a way to track and re-init the connectServerArgs
  99. //fmt.Println("process: initialized. closing initError")
  100. close(initError)
  101. //fmt.Println("process: waiting for queries")
  102. for q := range s.queries {
  103. //fmt.Printf("process: new query: len(query)=%d\n", len(q.query))
  104. errQuery := s.queryBackground(q)
  105. //fmt.Println("process: s.queryBackground finished")
  106. if errQuery != nil {
  107. q.finished <- errQuery
  108. }
  109. close(q.finished)
  110. }
  111. //fmt.Println("process: queries channel closed")
  112. s.queries = nil //set channel to nil so we know it is closed
  113. //TODO: I think the Release/Clear calls can panic if things are in a bad state.
  114. //TODO: May need to recover from panics and send error to method caller instead.
  115. close(s.closeError)
  116. }
  117. // Query runs the WQL query using a SWbemServices instance and appends the values to dst.
  118. //
  119. // dst must have type *[]S or *[]*S, for some struct type S. Fields selected in
  120. // the query must have the same name in dst. Supported types are all signed and
  121. // unsigned integers, time.Time, string, bool, or a pointer to one of those.
  122. // Array types are not supported.
  123. //
  124. // By default, the local machine and default namespace are used. These can be
  125. // changed using connectServerArgs. See
  126. // http://msdn.microsoft.com/en-us/library/aa393720.aspx for details.
  127. func (s *SWbemServices) Query(query string, dst interface{}, connectServerArgs ...interface{}) error {
  128. s.lQueryorClose.Lock()
  129. if s == nil || s.sWbemLocatorIDispatch == nil {
  130. s.lQueryorClose.Unlock()
  131. return fmt.Errorf("SWbemServices is not Initialized")
  132. }
  133. if s.queries == nil {
  134. s.lQueryorClose.Unlock()
  135. return fmt.Errorf("SWbemServices has been closed")
  136. }
  137. //fmt.Println("Query: Sending query request")
  138. qr := queryRequest{
  139. query: query,
  140. dst: dst,
  141. args: connectServerArgs,
  142. finished: make(chan error),
  143. }
  144. s.queries <- &qr
  145. s.lQueryorClose.Unlock()
  146. err, ok := <-qr.finished
  147. if ok {
  148. //fmt.Println("Query: Finished with error")
  149. return err //Send error to caller
  150. }
  151. //fmt.Println("Query: Finished")
  152. return nil
  153. }
  154. func (s *SWbemServices) queryBackground(q *queryRequest) error {
  155. if s == nil || s.sWbemLocatorIDispatch == nil {
  156. return fmt.Errorf("SWbemServices is not Initialized")
  157. }
  158. wmi := s.sWbemLocatorIDispatch //Should just rename in the code, but this will help as we break things apart
  159. //fmt.Println("queryBackground: Starting")
  160. dv := reflect.ValueOf(q.dst)
  161. if dv.Kind() != reflect.Ptr || dv.IsNil() {
  162. return ErrInvalidEntityType
  163. }
  164. dv = dv.Elem()
  165. mat, elemType := checkMultiArg(dv)
  166. if mat == multiArgTypeInvalid {
  167. return ErrInvalidEntityType
  168. }
  169. // service is a SWbemServices
  170. serviceRaw, err := oleutil.CallMethod(wmi, "ConnectServer", q.args...)
  171. if err != nil {
  172. return err
  173. }
  174. service := serviceRaw.ToIDispatch()
  175. defer serviceRaw.Clear()
  176. // result is a SWBemObjectSet
  177. resultRaw, err := oleutil.CallMethod(service, "ExecQuery", q.query)
  178. if err != nil {
  179. return err
  180. }
  181. result := resultRaw.ToIDispatch()
  182. defer resultRaw.Clear()
  183. count, err := oleInt64(result, "Count")
  184. if err != nil {
  185. return err
  186. }
  187. enumProperty, err := result.GetProperty("_NewEnum")
  188. if err != nil {
  189. return err
  190. }
  191. defer enumProperty.Clear()
  192. enum, err := enumProperty.ToIUnknown().IEnumVARIANT(ole.IID_IEnumVariant)
  193. if err != nil {
  194. return err
  195. }
  196. if enum == nil {
  197. return fmt.Errorf("can't get IEnumVARIANT, enum is nil")
  198. }
  199. defer enum.Release()
  200. // Initialize a slice with Count capacity
  201. dv.Set(reflect.MakeSlice(dv.Type(), 0, int(count)))
  202. var errFieldMismatch error
  203. for itemRaw, length, err := enum.Next(1); length > 0; itemRaw, length, err = enum.Next(1) {
  204. if err != nil {
  205. return err
  206. }
  207. err := func() error {
  208. // item is a SWbemObject, but really a Win32_Process
  209. item := itemRaw.ToIDispatch()
  210. defer item.Release()
  211. ev := reflect.New(elemType)
  212. if err = s.cWMIClient.loadEntity(ev.Interface(), item); err != nil {
  213. if _, ok := err.(*ErrFieldMismatch); ok {
  214. // We continue loading entities even in the face of field mismatch errors.
  215. // If we encounter any other error, that other error is returned. Otherwise,
  216. // an ErrFieldMismatch is returned.
  217. errFieldMismatch = err
  218. } else {
  219. return err
  220. }
  221. }
  222. if mat != multiArgTypeStructPtr {
  223. ev = ev.Elem()
  224. }
  225. dv.Set(reflect.Append(dv, ev))
  226. return nil
  227. }()
  228. if err != nil {
  229. return err
  230. }
  231. }
  232. //fmt.Println("queryBackground: Finished")
  233. return errFieldMismatch
  234. }