swbemservices.go 7.7 KB

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