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