pax_global_header00006660000000000000000000000064151012223130014500gustar00rootroot0000000000000052 comment=c6add3a85bf2205043cc3854c58575f4e206ec1e golang-github-yusufpapurcu-wmi-1.2.4/000077500000000000000000000000001510122231300175765ustar00rootroot00000000000000golang-github-yusufpapurcu-wmi-1.2.4/LICENSE000066400000000000000000000020711510122231300206030ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2013 Stack Exchange Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. golang-github-yusufpapurcu-wmi-1.2.4/README.md000066400000000000000000000002251510122231300210540ustar00rootroot00000000000000wmi === Package wmi provides a WQL interface to Windows WMI. Note: It interfaces with WMI on the local machine, therefore it only runs on Windows. golang-github-yusufpapurcu-wmi-1.2.4/go.mod000066400000000000000000000001251510122231300207020ustar00rootroot00000000000000module github.com/yusufpapurcu/wmi go 1.16 require github.com/go-ole/go-ole v1.2.6 golang-github-yusufpapurcu-wmi-1.2.4/go.sum000066400000000000000000000005661510122231300207400ustar00rootroot00000000000000github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3 h1:7TYNF4UdlohbFwpNH04CoPMp1cHUZgO1Ebq5r2hIjfo= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang-github-yusufpapurcu-wmi-1.2.4/swbemservices.go000066400000000000000000000171531510122231300230150ustar00rootroot00000000000000//go:build windows // +build windows package wmi import ( "fmt" "reflect" "runtime" "sync" "github.com/go-ole/go-ole" "github.com/go-ole/go-ole/oleutil" ) // SWbemServices is used to access wmi. See https://msdn.microsoft.com/en-us/library/aa393719(v=vs.85).aspx type SWbemServices struct { //TODO: track namespace. Not sure if we can re connect to a different namespace using the same instance cWMIClient *Client //This could also be an embedded struct, but then we would need to branch on Client vs SWbemServices in the Query method sWbemLocatorIUnknown *ole.IUnknown sWbemLocatorIDispatch *ole.IDispatch queries chan *queryRequest closeError chan error lQueryorClose sync.Mutex } type queryRequest struct { query string dst interface{} args []interface{} finished chan error } // InitializeSWbemServices will return a new SWbemServices object that can be used to query WMI func InitializeSWbemServices(c *Client, connectServerArgs ...interface{}) (*SWbemServices, error) { //fmt.Println("InitializeSWbemServices: Starting") //TODO: implement connectServerArgs as optional argument for init with connectServer call s := new(SWbemServices) s.cWMIClient = c s.queries = make(chan *queryRequest) initError := make(chan error) go s.process(initError) err, ok := <-initError if ok { return nil, err //Send error to caller } //fmt.Println("InitializeSWbemServices: Finished") return s, nil } // Close will clear and release all of the SWbemServices resources func (s *SWbemServices) Close() error { s.lQueryorClose.Lock() if s == nil || s.sWbemLocatorIDispatch == nil { s.lQueryorClose.Unlock() return fmt.Errorf("SWbemServices is not Initialized") } if s.queries == nil { s.lQueryorClose.Unlock() return fmt.Errorf("SWbemServices has been closed") } //fmt.Println("Close: sending close request") var result error ce := make(chan error) s.closeError = ce //Race condition if multiple callers to close. May need to lock here close(s.queries) //Tell background to shut things down s.lQueryorClose.Unlock() err, ok := <-ce if ok { result = err } //fmt.Println("Close: finished") return result } func (s *SWbemServices) process(initError chan error) { //fmt.Println("process: starting background thread initialization") //All OLE/WMI calls must happen on the same initialized thead, so lock this goroutine runtime.LockOSThread() defer runtime.UnlockOSThread() err := ole.CoInitializeEx(0, ole.COINIT_MULTITHREADED) if err != nil { oleCode := err.(*ole.OleError).Code() if oleCode != ole.S_OK && oleCode != S_FALSE { initError <- fmt.Errorf("ole.CoInitializeEx error: %v", err) return } } defer ole.CoUninitialize() unknown, err := oleutil.CreateObject("WbemScripting.SWbemLocator") if err != nil { initError <- fmt.Errorf("CreateObject SWbemLocator error: %v", err) return } else if unknown == nil { initError <- ErrNilCreateObject return } defer unknown.Release() s.sWbemLocatorIUnknown = unknown dispatch, err := s.sWbemLocatorIUnknown.QueryInterface(ole.IID_IDispatch) if err != nil { initError <- fmt.Errorf("SWbemLocator QueryInterface error: %v", err) return } defer dispatch.Release() s.sWbemLocatorIDispatch = dispatch // we can't do the ConnectServer call outside the loop unless we find a way to track and re-init the connectServerArgs //fmt.Println("process: initialized. closing initError") close(initError) //fmt.Println("process: waiting for queries") for q := range s.queries { //fmt.Printf("process: new query: len(query)=%d\n", len(q.query)) errQuery := s.queryBackground(q) //fmt.Println("process: s.queryBackground finished") if errQuery != nil { q.finished <- errQuery } close(q.finished) } //fmt.Println("process: queries channel closed") s.queries = nil //set channel to nil so we know it is closed //TODO: I think the Release/Clear calls can panic if things are in a bad state. //TODO: May need to recover from panics and send error to method caller instead. close(s.closeError) } // Query runs the WQL query using a SWbemServices instance and appends the values to dst. // // dst must have type *[]S or *[]*S, for some struct type S. Fields selected in // the query must have the same name in dst. Supported types are all signed and // unsigned integers, time.Time, string, bool, or a pointer to one of those. // Array types are not supported. // // By default, the local machine and default namespace are used. These can be // changed using connectServerArgs. See // http://msdn.microsoft.com/en-us/library/aa393720.aspx for details. func (s *SWbemServices) Query(query string, dst interface{}, connectServerArgs ...interface{}) error { s.lQueryorClose.Lock() if s == nil || s.sWbemLocatorIDispatch == nil { s.lQueryorClose.Unlock() return fmt.Errorf("SWbemServices is not Initialized") } if s.queries == nil { s.lQueryorClose.Unlock() return fmt.Errorf("SWbemServices has been closed") } //fmt.Println("Query: Sending query request") qr := queryRequest{ query: query, dst: dst, args: connectServerArgs, finished: make(chan error), } s.queries <- &qr s.lQueryorClose.Unlock() err, ok := <-qr.finished if ok { //fmt.Println("Query: Finished with error") return err //Send error to caller } //fmt.Println("Query: Finished") return nil } func (s *SWbemServices) queryBackground(q *queryRequest) error { if s == nil || s.sWbemLocatorIDispatch == nil { return fmt.Errorf("SWbemServices is not Initialized") } wmi := s.sWbemLocatorIDispatch //Should just rename in the code, but this will help as we break things apart //fmt.Println("queryBackground: Starting") dv := reflect.ValueOf(q.dst) if dv.Kind() != reflect.Ptr || dv.IsNil() { return ErrInvalidEntityType } dv = dv.Elem() mat, elemType := checkMultiArg(dv) if mat == multiArgTypeInvalid { return ErrInvalidEntityType } // service is a SWbemServices serviceRaw, err := oleutil.CallMethod(wmi, "ConnectServer", q.args...) if err != nil { return err } service := serviceRaw.ToIDispatch() defer serviceRaw.Clear() // result is a SWBemObjectSet resultRaw, err := oleutil.CallMethod(service, "ExecQuery", q.query) if err != nil { return err } result := resultRaw.ToIDispatch() defer resultRaw.Clear() count, err := oleInt64(result, "Count") if err != nil { return err } enumProperty, err := result.GetProperty("_NewEnum") if err != nil { return err } defer enumProperty.Clear() enum, err := enumProperty.ToIUnknown().IEnumVARIANT(ole.IID_IEnumVariant) if err != nil { return err } if enum == nil { return fmt.Errorf("can't get IEnumVARIANT, enum is nil") } defer enum.Release() // Initialize a slice with Count capacity dv.Set(reflect.MakeSlice(dv.Type(), 0, int(count))) var errFieldMismatch error for itemRaw, length, err := enum.Next(1); length > 0; itemRaw, length, err = enum.Next(1) { if err != nil { return err } err := func() error { // item is a SWbemObject, but really a Win32_Process item := itemRaw.ToIDispatch() defer item.Release() ev := reflect.New(elemType) if err = s.cWMIClient.loadEntity(ev.Interface(), item); err != nil { if _, ok := err.(*ErrFieldMismatch); ok { // We continue loading entities even in the face of field mismatch errors. // If we encounter any other error, that other error is returned. Otherwise, // an ErrFieldMismatch is returned. errFieldMismatch = err } else { return err } } if mat != multiArgTypeStructPtr { ev = ev.Elem() } dv.Set(reflect.Append(dv, ev)) return nil }() if err != nil { return err } } //fmt.Println("queryBackground: Finished") return errFieldMismatch } golang-github-yusufpapurcu-wmi-1.2.4/swbemservices_test.go000066400000000000000000000102261510122231300240460ustar00rootroot00000000000000//go:build windows // +build windows package wmi import ( "fmt" "runtime" "testing" "time" ) func TestWbemQuery(t *testing.T) { s, err := InitializeSWbemServices(DefaultClient) if err != nil { t.Fatalf("InitializeSWbemServices: %s", err) } var dst []Win32_Process q := CreateQuery(&dst, "WHERE name='lsass.exe'") errQuery := s.Query(q, &dst) if errQuery != nil { t.Fatalf("Query1: %s", errQuery) } count := len(dst) if count < 1 { t.Fatal("Query1: no results found for lsass.exe") } //fmt.Printf("dst[0].ProcessID=%d\n", dst[0].ProcessId) q2 := CreateQuery(&dst, "WHERE name='svchost.exe'") errQuery = s.Query(q2, &dst) if errQuery != nil { t.Fatalf("Query2: %s", errQuery) } count = len(dst) if count < 1 { t.Fatal("Query2: no results found for svchost.exe") } //for index, item := range dst { // fmt.Printf("dst[%d].ProcessID=%d\n", index, item.ProcessId) //} errClose := s.Close() if errClose != nil { t.Fatalf("Close: %s", errClose) } } func TestWbemQueryNamespace(t *testing.T) { s, err := InitializeSWbemServices(DefaultClient) if err != nil { t.Fatalf("InitializeSWbemServices: %s", err) } var dst []MSFT_NetAdapter q := CreateQuery(&dst, "") errQuery := s.Query(q, &dst, nil, "root\\StandardCimv2") if errQuery != nil { t.Fatalf("Query: %s", errQuery) } count := len(dst) if count < 1 { t.Fatal("Query: no results found for MSFT_NetAdapter in root\\StandardCimv2") } errClose := s.Close() if errClose != nil { t.Fatalf("Close: %s", errClose) } } // Run using: go test -run TestWbemMemory -timeout 60m func TestWbemMemory(t *testing.T) { s, err := InitializeSWbemServices(DefaultClient) if err != nil { t.Fatalf("InitializeSWbemServices: %s", err) } start := time.Now() limit := 500000 fmt.Printf("Benchmark Iterations: %d (Memory should stabilize around 7MB after ~3000)\n", limit) var privateMB, allocMB, allocTotalMB float64 for i := 0; i < limit; i++ { privateMB, allocMB, allocTotalMB = WbemGetMemoryUsageMB(s) if i%100 == 0 { privateMB, allocMB, allocTotalMB = WbemGetMemoryUsageMB(s) fmt.Printf("Time: %4ds Count: %5d Private Memory: %5.1fMB MemStats.Alloc: %4.1fMB MemStats.TotalAlloc: %5.1fMB\n", time.Now().Sub(start)/time.Second, i, privateMB, allocMB, allocTotalMB) } } errClose := s.Close() if errClose != nil { t.Fatalf("Close: %s", err) } fmt.Printf("Final Time: %4ds Private Memory: %5.1fMB MemStats.Alloc: %4.1fMB MemStats.TotalAlloc: %5.1fMB\n", time.Now().Sub(start)/time.Second, privateMB, allocMB, allocTotalMB) } func WbemGetMemoryUsageMB(s *SWbemServices) (float64, float64, float64) { runtime.ReadMemStats(&mMemoryUsageMB) errGetMemoryUsageMB = s.Query(qGetMemoryUsageMB, &dstGetMemoryUsageMB) if errGetMemoryUsageMB != nil { fmt.Println("ERROR GetMemoryUsage", errGetMemoryUsageMB) return 0, 0, 0 } return float64(dstGetMemoryUsageMB[0].WorkingSetPrivate) / MB, float64(mMemoryUsageMB.Alloc) / MB, float64(mMemoryUsageMB.TotalAlloc) / MB } //Run all benchmarks (should run for at least 60s to get a stable number): //go test -run=NONE -bench=Version -benchtime=120s // Individual benchmarks: // go test -run=NONE -bench=NewVersion -benchtime=120s func BenchmarkNewVersion(b *testing.B) { s, err := InitializeSWbemServices(DefaultClient) if err != nil { b.Fatalf("InitializeSWbemServices: %s", err) } var dst []Win32_OperatingSystem q := CreateQuery(&dst, "") for n := 0; n < b.N; n++ { errQuery := s.Query(q, &dst) if errQuery != nil { b.Fatalf("Query%d: %s", n, errQuery) } count := len(dst) if count < 1 { b.Fatalf("Query%d: no results found for Win32_OperatingSystem", n) } } errClose := s.Close() if errClose != nil { b.Fatalf("Close: %s", errClose) } } // go test -run=NONE -bench=OldVersion -benchtime=120s func BenchmarkOldVersion(b *testing.B) { var dst []Win32_OperatingSystem q := CreateQuery(&dst, "") for n := 0; n < b.N; n++ { errQuery := Query(q, &dst) if errQuery != nil { b.Fatalf("Query%d: %s", n, errQuery) } count := len(dst) if count < 1 { b.Fatalf("Query%d: no results found for Win32_OperatingSystem", n) } } } type MSFT_NetAdapter struct { Name string InterfaceIndex int DriverDescription string } golang-github-yusufpapurcu-wmi-1.2.4/wmi.go000066400000000000000000000404601510122231300207250ustar00rootroot00000000000000//go:build windows // +build windows /* Package wmi provides a WQL interface for WMI on Windows. Example code to print names of running processes: type Win32_Process struct { Name string } func main() { var dst []Win32_Process q := wmi.CreateQuery(&dst, "") err := wmi.Query(q, &dst) if err != nil { log.Fatal(err) } for i, v := range dst { println(i, v.Name) } } */ package wmi import ( "bytes" "errors" "fmt" "log" "os" "reflect" "runtime" "strconv" "strings" "sync" "time" "github.com/go-ole/go-ole" "github.com/go-ole/go-ole/oleutil" ) var l = log.New(os.Stdout, "", log.LstdFlags) var ( ErrInvalidEntityType = errors.New("wmi: invalid entity type") // ErrNilCreateObject is the error returned if CreateObject returns nil even // if the error was nil. ErrNilCreateObject = errors.New("wmi: create object returned nil") lock sync.Mutex ) // S_FALSE is returned by CoInitializeEx if it was already called on this thread. const S_FALSE = 0x00000001 // QueryNamespace invokes Query with the given namespace on the local machine. func QueryNamespace(query string, dst interface{}, namespace string) error { return Query(query, dst, nil, namespace) } // Query runs the WQL query and appends the values to dst. // // dst must have type *[]S or *[]*S, for some struct type S. Fields selected in // the query must have the same name in dst. Supported types are all signed and // unsigned integers, time.Time, string, bool, or a pointer to one of those. // Array types are not supported. // // By default, the local machine and default namespace are used. These can be // changed using connectServerArgs. See // https://docs.microsoft.com/en-us/windows/desktop/WmiSdk/swbemlocator-connectserver // for details. // // Query is a wrapper around DefaultClient.Query. func Query(query string, dst interface{}, connectServerArgs ...interface{}) error { if DefaultClient.SWbemServicesClient == nil { return DefaultClient.Query(query, dst, connectServerArgs...) } return DefaultClient.SWbemServicesClient.Query(query, dst, connectServerArgs...) } // CallMethod calls a method named methodName on an instance of the class named // className, with the given params. // // CallMethod is a wrapper around DefaultClient.CallMethod. func CallMethod(connectServerArgs []interface{}, className, methodName string, params []interface{}) (int32, error) { return DefaultClient.CallMethod(connectServerArgs, className, methodName, params) } // A Client is an WMI query client. // // Its zero value (DefaultClient) is a usable client. type Client struct { // NonePtrZero specifies if nil values for fields which aren't pointers // should be returned as the field types zero value. // // Setting this to true allows stucts without pointer fields to be used // without the risk failure should a nil value returned from WMI. NonePtrZero bool // PtrNil specifies if nil values for pointer fields should be returned // as nil. // // Setting this to true will set pointer fields to nil where WMI // returned nil, otherwise the types zero value will be returned. PtrNil bool // AllowMissingFields specifies that struct fields not present in the // query result should not result in an error. // // Setting this to true allows custom queries to be used with full // struct definitions instead of having to define multiple structs. AllowMissingFields bool // SWbemServiceClient is an optional SWbemServices object that can be // initialized and then reused across multiple queries. If it is null // then the method will initialize a new temporary client each time. SWbemServicesClient *SWbemServices } // DefaultClient is the default Client and is used by Query, QueryNamespace, and CallMethod. var DefaultClient = &Client{} // coinitService coinitializes WMI service. If no error is returned, a cleanup function // is returned which must be executed (usually deferred) to clean up allocated resources. func (c *Client) coinitService(connectServerArgs ...interface{}) (*ole.IDispatch, func(), error) { var unknown *ole.IUnknown var wmi *ole.IDispatch var serviceRaw *ole.VARIANT // be sure teardown happens in the reverse // order from that which they were created deferFn := func() { if serviceRaw != nil { serviceRaw.Clear() } if wmi != nil { wmi.Release() } if unknown != nil { unknown.Release() } ole.CoUninitialize() } // if we error'ed here, clean up immediately var err error defer func() { if err != nil { deferFn() } }() err = ole.CoInitializeEx(0, ole.COINIT_MULTITHREADED) if err != nil { oleCode := err.(*ole.OleError).Code() if oleCode != ole.S_OK && oleCode != S_FALSE { return nil, nil, err } } unknown, err = oleutil.CreateObject("WbemScripting.SWbemLocator") if err != nil { return nil, nil, err } else if unknown == nil { return nil, nil, ErrNilCreateObject } wmi, err = unknown.QueryInterface(ole.IID_IDispatch) if err != nil { return nil, nil, err } // service is a SWbemServices serviceRaw, err = oleutil.CallMethod(wmi, "ConnectServer", connectServerArgs...) if err != nil { return nil, nil, err } return serviceRaw.ToIDispatch(), deferFn, nil } // CallMethod calls a WMI method named methodName on an instance // of the class named className. It passes in the arguments given // in params. Use connectServerArgs to customize the machine and // namespace; by default, the local machine and default namespace // are used. See // https://docs.microsoft.com/en-us/windows/desktop/WmiSdk/swbemlocator-connectserver // for details. func (c *Client) CallMethod(connectServerArgs []interface{}, className, methodName string, params []interface{}) (int32, error) { service, cleanup, err := c.coinitService(connectServerArgs...) if err != nil { return 0, fmt.Errorf("coinit: %v", err) } defer cleanup() // Get class classRaw, err := oleutil.CallMethod(service, "Get", className) if err != nil { return 0, fmt.Errorf("CallMethod Get class %s: %v", className, err) } class := classRaw.ToIDispatch() defer classRaw.Clear() // Run method resultRaw, err := oleutil.CallMethod(class, methodName, params...) if err != nil { return 0, fmt.Errorf("CallMethod %s.%s: %v", className, methodName, err) } resultInt, ok := resultRaw.Value().(int32) if !ok { return 0, fmt.Errorf("return value was not an int32: %v (%T)", resultRaw, resultRaw) } return resultInt, nil } // Query runs the WQL query and appends the values to dst. // // dst must have type *[]S or *[]*S, for some struct type S. Fields selected in // the query must have the same name in dst. Supported types are all signed and // unsigned integers, time.Time, string, bool, or a pointer to one of those. // Array types are not supported. // // By default, the local machine and default namespace are used. These can be // changed using connectServerArgs. See // https://docs.microsoft.com/en-us/windows/desktop/WmiSdk/swbemlocator-connectserver // for details. func (c *Client) Query(query string, dst interface{}, connectServerArgs ...interface{}) error { dv := reflect.ValueOf(dst) if dv.Kind() != reflect.Ptr || dv.IsNil() { return ErrInvalidEntityType } dv = dv.Elem() mat, elemType := checkMultiArg(dv) if mat == multiArgTypeInvalid { return ErrInvalidEntityType } lock.Lock() defer lock.Unlock() runtime.LockOSThread() defer runtime.UnlockOSThread() service, cleanup, err := c.coinitService(connectServerArgs...) if err != nil { return err } defer cleanup() // result is a SWBemObjectSet resultRaw, err := oleutil.CallMethod(service, "ExecQuery", query) if err != nil { return err } result := resultRaw.ToIDispatch() defer resultRaw.Clear() count, err := oleInt64(result, "Count") if err != nil { return err } enumProperty, err := result.GetProperty("_NewEnum") if err != nil { return err } defer enumProperty.Clear() enum, err := enumProperty.ToIUnknown().IEnumVARIANT(ole.IID_IEnumVariant) if err != nil { return err } if enum == nil { return fmt.Errorf("can't get IEnumVARIANT, enum is nil") } defer enum.Release() // Initialize a slice with Count capacity dv.Set(reflect.MakeSlice(dv.Type(), 0, int(count))) var errFieldMismatch error for itemRaw, length, err := enum.Next(1); length > 0; itemRaw, length, err = enum.Next(1) { if err != nil { return err } err := func() error { // item is a SWbemObject, but really a Win32_Process item := itemRaw.ToIDispatch() defer item.Release() ev := reflect.New(elemType) if err = c.loadEntity(ev.Interface(), item); err != nil { if _, ok := err.(*ErrFieldMismatch); ok { // We continue loading entities even in the face of field mismatch errors. // If we encounter any other error, that other error is returned. Otherwise, // an ErrFieldMismatch is returned. errFieldMismatch = err } else { return err } } if mat != multiArgTypeStructPtr { ev = ev.Elem() } dv.Set(reflect.Append(dv, ev)) return nil }() if err != nil { return err } } return errFieldMismatch } // ErrFieldMismatch is returned when a field is to be loaded into a different // type than the one it was stored from, or when a field is missing or // unexported in the destination struct. // StructType is the type of the struct pointed to by the destination argument. type ErrFieldMismatch struct { StructType reflect.Type FieldName string Reason string } func (e *ErrFieldMismatch) Error() string { return fmt.Sprintf("wmi: cannot load field %q into a %q: %s", e.FieldName, e.StructType, e.Reason) } var timeType = reflect.TypeOf(time.Time{}) // loadEntity loads a SWbemObject into a struct pointer. func (c *Client) loadEntity(dst interface{}, src *ole.IDispatch) (errFieldMismatch error) { v := reflect.ValueOf(dst).Elem() for i := 0; i < v.NumField(); i++ { f := v.Field(i) of := f isPtr := f.Kind() == reflect.Ptr n := v.Type().Field(i).Name if n[0] < 'A' || n[0] > 'Z' { continue } if !f.CanSet() { return &ErrFieldMismatch{ StructType: of.Type(), FieldName: n, Reason: "CanSet() is false", } } prop, err := oleutil.GetProperty(src, n) if err != nil { if !c.AllowMissingFields { errFieldMismatch = &ErrFieldMismatch{ StructType: of.Type(), FieldName: n, Reason: "no such struct field", } } continue } defer prop.Clear() if isPtr && !(c.PtrNil && prop.VT == 0x1) { ptr := reflect.New(f.Type().Elem()) f.Set(ptr) f = f.Elem() } if prop.VT == 0x1 { //VT_NULL continue } switch val := prop.Value().(type) { case int8, int16, int32, int64, int: v := reflect.ValueOf(val).Int() switch f.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: f.SetInt(v) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: f.SetUint(uint64(v)) default: return &ErrFieldMismatch{ StructType: of.Type(), FieldName: n, Reason: "not an integer class", } } case uint8, uint16, uint32, uint64: v := reflect.ValueOf(val).Uint() switch f.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: f.SetInt(int64(v)) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: f.SetUint(v) default: return &ErrFieldMismatch{ StructType: of.Type(), FieldName: n, Reason: "not an integer class", } } case string: switch f.Kind() { case reflect.String: f.SetString(val) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: iv, err := strconv.ParseInt(val, 10, 64) if err != nil { return err } f.SetInt(iv) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: uv, err := strconv.ParseUint(val, 10, 64) if err != nil { return err } f.SetUint(uv) case reflect.Struct: switch f.Type() { case timeType: if len(val) == 25 { mins, err := strconv.Atoi(val[22:]) if err != nil { return err } val = val[:22] + fmt.Sprintf("%02d%02d", mins/60, mins%60) } t, err := time.Parse("20060102150405.000000-0700", val) if err != nil { return err } f.Set(reflect.ValueOf(t)) } } case bool: switch f.Kind() { case reflect.Bool: f.SetBool(val) default: return &ErrFieldMismatch{ StructType: of.Type(), FieldName: n, Reason: "not a bool", } } case float32: switch f.Kind() { case reflect.Float32: f.SetFloat(float64(val)) default: return &ErrFieldMismatch{ StructType: of.Type(), FieldName: n, Reason: "not a Float32", } } case float64: switch f.Kind() { case reflect.Float32, reflect.Float64: f.SetFloat(val) default: return &ErrFieldMismatch{ StructType: of.Type(), FieldName: n, Reason: "not a Float64", } } default: if f.Kind() == reflect.Slice { switch f.Type().Elem().Kind() { case reflect.String: safeArray := prop.ToArray() if safeArray != nil { arr := safeArray.ToValueArray() fArr := reflect.MakeSlice(f.Type(), len(arr), len(arr)) for i, v := range arr { s := fArr.Index(i) s.SetString(v.(string)) } f.Set(fArr) } case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: safeArray := prop.ToArray() if safeArray != nil { arr := safeArray.ToValueArray() fArr := reflect.MakeSlice(f.Type(), len(arr), len(arr)) for i, v := range arr { s := fArr.Index(i) s.SetUint(reflect.ValueOf(v).Uint()) } f.Set(fArr) } case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: safeArray := prop.ToArray() if safeArray != nil { arr := safeArray.ToValueArray() fArr := reflect.MakeSlice(f.Type(), len(arr), len(arr)) for i, v := range arr { s := fArr.Index(i) s.SetInt(reflect.ValueOf(v).Int()) } f.Set(fArr) } default: return &ErrFieldMismatch{ StructType: of.Type(), FieldName: n, Reason: fmt.Sprintf("unsupported slice type (%T)", val), } } } else { typeof := reflect.TypeOf(val) if typeof == nil && (isPtr || c.NonePtrZero) { if (isPtr && c.PtrNil) || (!isPtr && c.NonePtrZero) { of.Set(reflect.Zero(of.Type())) } break } return &ErrFieldMismatch{ StructType: of.Type(), FieldName: n, Reason: fmt.Sprintf("unsupported type (%T)", val), } } } } return errFieldMismatch } type multiArgType int const ( multiArgTypeInvalid multiArgType = iota multiArgTypeStruct multiArgTypeStructPtr ) // checkMultiArg checks that v has type []S, []*S for some struct type S. // // It returns what category the slice's elements are, and the reflect.Type // that represents S. func checkMultiArg(v reflect.Value) (m multiArgType, elemType reflect.Type) { if v.Kind() != reflect.Slice { return multiArgTypeInvalid, nil } elemType = v.Type().Elem() switch elemType.Kind() { case reflect.Struct: return multiArgTypeStruct, elemType case reflect.Ptr: elemType = elemType.Elem() if elemType.Kind() == reflect.Struct { return multiArgTypeStructPtr, elemType } } return multiArgTypeInvalid, nil } func oleInt64(item *ole.IDispatch, prop string) (int64, error) { v, err := oleutil.GetProperty(item, prop) if err != nil { return 0, err } defer v.Clear() i := int64(v.Val) return i, nil } // CreateQuery returns a WQL query string that queries all columns of src. where // is an optional string that is appended to the query, to be used with WHERE // clauses. In such a case, the "WHERE" string should appear at the beginning. // The wmi class is obtained by the name of the type. You can pass a optional // class throught the variadic class parameter which is useful for anonymous // structs. func CreateQuery(src interface{}, where string, class ...string) string { var b bytes.Buffer b.WriteString("SELECT ") s := reflect.Indirect(reflect.ValueOf(src)) t := s.Type() if s.Kind() == reflect.Slice { t = t.Elem() } if t.Kind() != reflect.Struct { return "" } var fields []string for i := 0; i < t.NumField(); i++ { fields = append(fields, t.Field(i).Name) } b.WriteString(strings.Join(fields, ", ")) b.WriteString(" FROM ") if len(class) > 0 { b.WriteString(class[0]) } else { b.WriteString(t.Name()) } b.WriteString(" " + where) return b.String() } golang-github-yusufpapurcu-wmi-1.2.4/wmi_test.go000066400000000000000000000454631510122231300217740ustar00rootroot00000000000000//go:build windows // +build windows package wmi import ( "encoding/json" "fmt" "os" "reflect" "runtime" "runtime/debug" "sync" "testing" "time" ole "github.com/go-ole/go-ole" "github.com/go-ole/go-ole/oleutil" ) func TestQuery(t *testing.T) { var dst []Win32_Process q := CreateQuery(&dst, "") err := Query(q, &dst) if err != nil { t.Fatal(err) } } func TestFieldMismatch(t *testing.T) { type s struct { Name string HandleCount uint32 Blah uint32 } var dst []s err := Query("SELECT Name, HandleCount FROM Win32_Process", &dst) if err == nil || err.Error() != `wmi: cannot load field "Blah" into a "uint32": no such struct field` { t.Error("Expected err field mismatch") } } func TestMissingFields(t *testing.T) { type s struct { Name string Missing uint32 MissingPointer *uint32 } var dst []s client := &Client{ AllowMissingFields: true, } err := client.Query("SELECT Name FROM Win32_Process", &dst) if err != nil { t.Fatal(err) } for i := range dst { if dst[i].Missing != 0 { t.Fatal("Expected Missing field to be 0") } if dst[i].MissingPointer != nil { t.Fatal("Expected MissingPointer field to be nil") } } // NonePtrZero and PtrNil should only affect the behavior of fields that // exist as result properties, not missing fields. client = &Client{ NonePtrZero: true, PtrNil: true, AllowMissingFields: true, } dst = []s{} err = client.Query("SELECT Name FROM Win32_Process", &dst) if err != nil { t.Fatal(err) } for i := range dst { if dst[i].Missing != 0 { t.Fatal("Expected Missing field to be 0") } if dst[i].MissingPointer != nil { t.Fatal("Expected MissingPointer field to be nil") } } } func TestNullPointerField(t *testing.T) { type s struct { Name string Status *string } var dst []s client := &Client{} err := client.Query("SELECT Name, Status FROM Win32_Process WHERE Status IS NULL", &dst) if err != nil { t.Fatal(err) } for i := range dst { if dst[i].Status == nil { t.Fatal("Expected Status field to not be nil") } } client = &Client{ PtrNil: true, } dst = []s{} err = client.Query("SELECT Name, Status FROM Win32_Process WHERE Status IS NULL", &dst) if err != nil { t.Fatal(err) } for i := range dst { if dst[i].Status != nil { t.Fatal("Expected Status field to be nil") } } } func TestStrings(t *testing.T) { printed := false f := func() { var dst []Win32_Process zeros := 0 q := CreateQuery(&dst, "") for i := 0; i < 5; i++ { err := Query(q, &dst) if err != nil { t.Fatal(err, q) } for _, d := range dst { v := reflect.ValueOf(d) for j := 0; j < v.NumField(); j++ { f := v.Field(j) if f.Kind() != reflect.String { continue } s := f.Interface().(string) if len(s) > 0 && s[0] == '\u0000' { zeros++ if !printed { printed = true j, _ := json.MarshalIndent(&d, "", " ") t.Log("Example with \\u0000:\n", string(j)) } } } } fmt.Println("iter", i, "zeros:", zeros) } if zeros > 0 { t.Error("> 0 zeros") } } fmt.Println("Disabling GC") debug.SetGCPercent(-1) f() fmt.Println("Enabling GC") debug.SetGCPercent(100) f() } func TestNamespace(t *testing.T) { var dst []Win32_Process q := CreateQuery(&dst, "") err := QueryNamespace(q, &dst, `root\CIMV2`) if err != nil { t.Fatal(err) } dst = nil err = QueryNamespace(q, &dst, `broken\nothing`) if err == nil { t.Fatal("expected error") } } func TestCreateQuery(t *testing.T) { type TestStruct struct { Name string Count int } var dst []TestStruct output := "SELECT Name, Count FROM TestStruct WHERE Count > 2" tests := []interface{}{ &dst, dst, TestStruct{}, &TestStruct{}, } for i, test := range tests { if o := CreateQuery(test, "WHERE Count > 2"); o != output { t.Error("bad output on", i, o) } } if CreateQuery(3, "") != "" { t.Error("expected empty string") } } // Run using: go test -run TestMemoryWMISimple -timeout 60m func _TestMemoryWMISimple(t *testing.T) { start := time.Now() limit := 500000 fmt.Printf("Benchmark Iterations: %d (Memory should stabilize around 7MB after ~3000)\n", limit) var privateMB, allocMB, allocTotalMB float64 //var dst []Win32_PerfRawData_PerfDisk_LogicalDisk //q := CreateQuery(&dst, "") for i := 0; i < limit; i++ { privateMB, allocMB, allocTotalMB = GetMemoryUsageMB() if i%1000 == 0 { //privateMB, allocMB, allocTotalMB = GetMemoryUsageMB() fmt.Printf("Time: %4ds Count: %5d Private Memory: %5.1fMB MemStats.Alloc: %4.1fMB MemStats.TotalAlloc: %5.1fMB\n", time.Now().Sub(start)/time.Second, i, privateMB, allocMB, allocTotalMB) } //Query(q, &dst) } //privateMB, allocMB, allocTotalMB = GetMemoryUsageMB() fmt.Printf("Final Time: %4ds Private Memory: %5.1fMB MemStats.Alloc: %4.1fMB MemStats.TotalAlloc: %5.1fMB\n", time.Now().Sub(start)/time.Second, privateMB, allocMB, allocTotalMB) } func _TestMemoryWMIConcurrent(t *testing.T) { if testing.Short() { return } start := time.Now() limit := 50000 fmt.Println("Total Iterations:", limit) fmt.Println("No panics mean it succeeded. Other errors are OK. Memory should stabilize after ~1500 iterations.") runtime.GOMAXPROCS(2) wg := sync.WaitGroup{} wg.Add(2) go func() { for i := 0; i < limit; i++ { if i%500 == 0 { privateMB, allocMB, allocTotalMB := GetMemoryUsageMB() fmt.Printf("Time: %4ds Count: %4d Private Memory: %5.1fMB MemStats.Alloc: %4.1fMB MemStats.TotalAlloc: %5.1fMB\n", time.Now().Sub(start)/time.Second, i, privateMB, allocMB, allocTotalMB) } var dst []Win32_PerfRawData_PerfDisk_LogicalDisk q := CreateQuery(&dst, "") err := Query(q, &dst) if err != nil { fmt.Println("ERROR disk", err) } } wg.Done() }() go func() { for i := 0; i > -limit; i-- { //if i%500 == 0 { // fmt.Println(i) //} var dst []Win32_OperatingSystem q := CreateQuery(&dst, "") err := Query(q, &dst) if err != nil { fmt.Println("ERROR OS", err) } } wg.Done() }() wg.Wait() //privateMB, allocMB, allocTotalMB := GetMemoryUsageMB() //fmt.Printf("Final Private Memory: %5.1fMB MemStats.Alloc: %4.1fMB MemStats.TotalAlloc: %5.1fMB\n", privateMB, allocMB, allocTotalMB) } var lockthread sync.Mutex var refcount1 int32 var refcount2 int32 var refcount3 int32 // Test function showing memory leak in unknown.QueryInterface call on Server2016/Windows10 func getRSS(url string, xmlhttp *ole.IDispatch, MinimalTest bool) (int, error) { // call using url,nil to see memory leak if xmlhttp == nil { //Initialize inside loop if not passed in from outer section lockthread.Lock() defer lockthread.Unlock() runtime.LockOSThread() defer runtime.UnlockOSThread() err := ole.CoInitializeEx(0, ole.COINIT_MULTITHREADED) if err != nil { oleCode := err.(*ole.OleError).Code() if oleCode != ole.S_OK && oleCode != S_FALSE { return 0, err } } defer ole.CoUninitialize() //fmt.Println("CreateObject Microsoft.XMLHTTP") unknown, err := oleutil.CreateObject("Microsoft.XMLHTTP") if err != nil { return 0, err } defer func() { refcount1 += xmlhttp.Release() }() //Memory leak occurs here xmlhttp, err = unknown.QueryInterface(ole.IID_IDispatch) if err != nil { return 0, err } defer func() { refcount2 += xmlhttp.Release() }() //Nothing below this really matters. Can be removed if you want a tighter loop } //fmt.Printf("Download %s\n", url) openRaw, err := oleutil.CallMethod(xmlhttp, "open", "GET", url, false) if err != nil { return 0, err } defer openRaw.Clear() if MinimalTest { return 1, nil } //Initiate http request sendRaw, err := oleutil.CallMethod(xmlhttp, "send", nil) if err != nil { return 0, err } defer sendRaw.Clear() state := -1 // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/readyState for state != 4 { time.Sleep(5 * time.Millisecond) stateRaw := oleutil.MustGetProperty(xmlhttp, "readyState") state = int(stateRaw.Val) stateRaw.Clear() } responseXMLRaw := oleutil.MustGetProperty(xmlhttp, "responseXml") responseXML := responseXMLRaw.ToIDispatch() defer responseXMLRaw.Clear() itemsRaw := oleutil.MustCallMethod(responseXML, "selectNodes", "/rdf:RDF/item") items := itemsRaw.ToIDispatch() defer itemsRaw.Clear() lengthRaw := oleutil.MustGetProperty(items, "length") defer lengthRaw.Clear() length := int(lengthRaw.Val) /* This just bloats the TotalAlloc and slows the test down. Doesn't effect Private Working Set for n := 0; n < length; n++ { itemRaw := oleutil.MustGetProperty(items, "item", n) item := itemRaw.ToIDispatch() title := oleutil.MustCallMethod(item, "selectSingleNode", "title").ToIDispatch() //fmt.Println(oleutil.MustGetProperty(title, "text").ToString()) textRaw := oleutil.MustGetProperty(title, "text") textRaw.ToString() link := oleutil.MustCallMethod(item, "selectSingleNode", "link").ToIDispatch() //fmt.Println(" " + oleutil.MustGetProperty(link, "text").ToString()) textRaw2 := oleutil.MustGetProperty(link, "text") textRaw2.ToString() textRaw2.Clear() link.Release() textRaw.Clear() title.Release() itemRaw.Clear() } */ return length, nil } // Testing go-ole/oleutil // Run using: go test -run TestMemoryOLE -timeout 60m // Code from https://github.com/go-ole/go-ole/blob/master/example/msxml/rssreader.go func _TestMemoryOLE(t *testing.T) { defer func() { if r := recover(); r != nil { t.Error(r) } }() start := time.Now() limit := 50000000 url := "http://localhost/slashdot.xml" //http://rss.slashdot.org/Slashdot/slashdot" fmt.Printf("Benchmark Iterations: %d (Memory should stabilize around 8MB to 12MB after ~2k full or 250k minimal)\n", limit) //On Server 2016 or Windows 10 changing leakMemory=true will cause it to leak ~1.5MB per 10000 calls to unknown.QueryInterface leakMemory := true //////////////////////////////////////// //Start outer section var unknown *ole.IUnknown var xmlhttp *ole.IDispatch if !leakMemory { runtime.LockOSThread() defer runtime.UnlockOSThread() err := ole.CoInitializeEx(0, ole.COINIT_MULTITHREADED) if err != nil { oleCode := err.(*ole.OleError).Code() if oleCode != ole.S_OK && oleCode != S_FALSE { t.Fatal(err) } } defer ole.CoUninitialize() //fmt.Println("CreateObject Microsoft.XMLHTTP") unknown, err = oleutil.CreateObject("Microsoft.XMLHTTP") if err != nil { t.Fatal(err) } defer unknown.Release() //Memory leak starts here xmlhttp, err = unknown.QueryInterface(ole.IID_IDispatch) if err != nil { t.Fatal(err) } defer xmlhttp.Release() } //End outer section //////////////////////////////////////// totalItems := uint64(0) for i := 0; i < limit; i++ { if i%2000 == 0 { privateMB, allocMB, allocTotalMB := GetMemoryUsageMB() fmt.Printf("Time: %4ds Count: %7d Private Memory: %5.1fMB MemStats.Alloc: %4.1fMB MemStats.TotalAlloc: %5.1fMB %7d/%7d\n", time.Now().Sub(start)/time.Second, i, privateMB, allocMB, allocTotalMB, refcount1, refcount2) } //This should use less than 10MB for 1 million iterations if xmlhttp was initialized above //On Server 2016 or Windows 10 changing leakMemory=true above will cause it to leak ~1.5MB per 10000 calls to unknown.QueryInterface count, err := getRSS(url, xmlhttp, true) //last argument is for Minimal test. Doesn't effect leak just overall allocations/time if err != nil { t.Fatal(err) } totalItems += uint64(count) } privateMB, allocMB, allocTotalMB := GetMemoryUsageMB() fmt.Printf("Final totalItems: %d Private Memory: %5.1fMB MemStats.Alloc: %4.1fMB MemStats.TotalAlloc: %5.1fMB\n", totalItems, privateMB, allocMB, allocTotalMB) } const MB = 1024 * 1024 var ( mMemoryUsageMB runtime.MemStats errGetMemoryUsageMB error dstGetMemoryUsageMB []Win32_PerfRawData_PerfProc_Process filterProcessID = fmt.Sprintf("WHERE IDProcess = %d", os.Getpid()) qGetMemoryUsageMB = CreateQuery(&dstGetMemoryUsageMB, filterProcessID) ) func GetMemoryUsageMB() (float64, float64, float64) { runtime.ReadMemStats(&mMemoryUsageMB) //errGetMemoryUsageMB = nil //Query(qGetMemoryUsageMB, &dstGetMemoryUsageMB) float64(dstGetMemoryUsageMB[0].WorkingSetPrivate) errGetMemoryUsageMB = Query(qGetMemoryUsageMB, &dstGetMemoryUsageMB) if errGetMemoryUsageMB != nil { fmt.Println("ERROR GetMemoryUsage", errGetMemoryUsageMB) return 0, 0, 0 } return float64(dstGetMemoryUsageMB[0].WorkingSetPrivate) / MB, float64(mMemoryUsageMB.Alloc) / MB, float64(mMemoryUsageMB.TotalAlloc) / MB } type Win32_PerfRawData_PerfProc_Process struct { IDProcess uint32 WorkingSetPrivate uint64 } type Win32_Process struct { CSCreationClassName string CSName string Caption *string CommandLine *string CreationClassName string CreationDate *time.Time Description *string ExecutablePath *string ExecutionState *uint16 Handle string HandleCount uint32 InstallDate *time.Time KernelModeTime uint64 MaximumWorkingSetSize *uint32 MinimumWorkingSetSize *uint32 Name string OSCreationClassName string OSName string OtherOperationCount uint64 OtherTransferCount uint64 PageFaults uint32 PageFileUsage uint32 ParentProcessId uint32 PeakPageFileUsage uint32 PeakVirtualSize uint64 PeakWorkingSetSize uint32 Priority uint32 PrivatePageCount uint64 ProcessId uint32 QuotaNonPagedPoolUsage uint32 QuotaPagedPoolUsage uint32 QuotaPeakNonPagedPoolUsage uint32 QuotaPeakPagedPoolUsage uint32 ReadOperationCount uint64 ReadTransferCount uint64 SessionId uint32 Status *string TerminationDate *time.Time ThreadCount uint32 UserModeTime uint64 VirtualSize uint64 WindowsVersion string WorkingSetSize uint64 WriteOperationCount uint64 WriteTransferCount uint64 } type Win32_PerfRawData_PerfDisk_LogicalDisk struct { AvgDiskBytesPerRead uint64 AvgDiskBytesPerRead_Base uint32 AvgDiskBytesPerTransfer uint64 AvgDiskBytesPerTransfer_Base uint32 AvgDiskBytesPerWrite uint64 AvgDiskBytesPerWrite_Base uint32 AvgDiskQueueLength uint64 AvgDiskReadQueueLength uint64 AvgDiskSecPerRead uint32 AvgDiskSecPerRead_Base uint32 AvgDiskSecPerTransfer uint32 AvgDiskSecPerTransfer_Base uint32 AvgDiskSecPerWrite uint32 AvgDiskSecPerWrite_Base uint32 AvgDiskWriteQueueLength uint64 Caption *string CurrentDiskQueueLength uint32 Description *string DiskBytesPerSec uint64 DiskReadBytesPerSec uint64 DiskReadsPerSec uint32 DiskTransfersPerSec uint32 DiskWriteBytesPerSec uint64 DiskWritesPerSec uint32 FreeMegabytes uint32 Frequency_Object uint64 Frequency_PerfTime uint64 Frequency_Sys100NS uint64 Name string PercentDiskReadTime uint64 PercentDiskReadTime_Base uint64 PercentDiskTime uint64 PercentDiskTime_Base uint64 PercentDiskWriteTime uint64 PercentDiskWriteTime_Base uint64 PercentFreeSpace uint32 PercentFreeSpace_Base uint32 PercentIdleTime uint64 PercentIdleTime_Base uint64 SplitIOPerSec uint32 Timestamp_Object uint64 Timestamp_PerfTime uint64 Timestamp_Sys100NS uint64 } type Win32_OperatingSystem struct { BootDevice string BuildNumber string BuildType string Caption *string CodeSet string CountryCode string CreationClassName string CSCreationClassName string CSDVersion *string CSName string CurrentTimeZone int16 DataExecutionPrevention_Available bool DataExecutionPrevention_32BitApplications bool DataExecutionPrevention_Drivers bool DataExecutionPrevention_SupportPolicy *uint8 Debug bool Description *string Distributed bool EncryptionLevel uint32 ForegroundApplicationBoost *uint8 FreePhysicalMemory uint64 FreeSpaceInPagingFiles uint64 FreeVirtualMemory uint64 InstallDate time.Time LargeSystemCache *uint32 LastBootUpTime time.Time LocalDateTime time.Time Locale string Manufacturer string MaxNumberOfProcesses uint32 MaxProcessMemorySize uint64 MUILanguages *[]string Name string NumberOfLicensedUsers *uint32 NumberOfProcesses uint32 NumberOfUsers uint32 OperatingSystemSKU uint32 Organization string OSArchitecture string OSLanguage uint32 OSProductSuite uint32 OSType uint16 OtherTypeDescription *string PAEEnabled *bool PlusProductID *string PlusVersionNumber *string PortableOperatingSystem bool Primary bool ProductType uint32 RegisteredUser string SerialNumber string ServicePackMajorVersion uint16 ServicePackMinorVersion uint16 SizeStoredInPagingFiles uint64 Status string SuiteMask uint32 SystemDevice string SystemDirectory string SystemDrive string TotalSwapSpaceSize *uint64 TotalVirtualMemorySize uint64 TotalVisibleMemorySize uint64 Version string WindowsDirectory string }