mi: replace all WMI calls with MI calls (#1714)

This commit is contained in:
Jan-Otto Kröpke
2024-11-03 17:23:26 +01:00
committed by GitHub
parent 45d3eabab9
commit bf233ad3e3
82 changed files with 2771 additions and 738 deletions

283
internal/mi/application.go Normal file
View File

@@ -0,0 +1,283 @@
//go:build windows
package mi
import (
"errors"
"fmt"
"syscall"
"time"
"unsafe"
"golang.org/x/sys/windows"
)
const (
applicationID = "windows_exporter"
LocaleEnglish = "en-us"
)
var (
// DestinationOptionsTimeout is the key for the timeout option.
//
// https://github.com/microsoft/win32metadata/blob/527806d20d83d3abd43d16cd3fa8795d8deba343/generation/WinSDK/RecompiledIdlHeaders/um/mi.h#L7830
DestinationOptionsTimeout = UTF16PtrFromString[*uint16]("__MI_DESTINATIONOPTIONS_TIMEOUT")
// DestinationOptionsUILocale is the key for the UI locale option.
//
// https://github.com/microsoft/win32metadata/blob/527806d20d83d3abd43d16cd3fa8795d8deba343/generation/WinSDK/RecompiledIdlHeaders/um/mi.h#L8248
DestinationOptionsUILocale = UTF16PtrFromString[*uint16]("__MI_DESTINATIONOPTIONS_UI_LOCALE")
)
var (
modMi = windows.NewLazySystemDLL("mi.dll")
procMIApplicationInitialize = modMi.NewProc("MI_Application_InitializeV1")
)
// Application represents the MI application.
// https://learn.microsoft.com/de-de/windows/win32/api/mi/ns-mi-mi_application
type Application struct {
reserved1 uint64
reserved2 uintptr
ft *ApplicationFT
}
// ApplicationFT represents the function table of the MI application.
// https://learn.microsoft.com/de-de/windows/win32/api/mi/ns-mi-mi_applicationft
type ApplicationFT struct {
Close uintptr
NewSession uintptr
NewHostedProvider uintptr
NewInstance uintptr
NewDestinationOptions uintptr
NewOperationOptions uintptr
NewSubscriptionDeliveryOptions uintptr
NewSerializer uintptr
NewDeserializer uintptr
NewInstanceFromClass uintptr
NewClass uintptr
}
type DestinationOptions struct {
reserved1 uint64
reserved2 uintptr
ft *DestinationOptionsFT
}
type DestinationOptionsFT struct {
Delete uintptr
SetString uintptr
SetNumber uintptr
AddCredentials uintptr
GetString uintptr
GetNumber uintptr
GetOptionCount uintptr
GetOptionAt uintptr
GetOption uintptr
GetCredentialsCount uintptr
GetCredentialsAt uintptr
GetCredentialsPasswordAt uintptr
Clone uintptr
SetInterval uintptr
GetInterval uintptr
}
// Application_Initialize initializes the MI [Application].
// It is recommended to have only one Application per process.
//
// https://learn.microsoft.com/en-us/windows/win32/api/mi/nf-mi-mi_application_initializev1
func Application_Initialize() (*Application, error) {
application := &Application{}
applicationId, err := windows.UTF16PtrFromString(applicationID)
if err != nil {
return nil, err
}
r0, _, err := procMIApplicationInitialize.Call(
0,
uintptr(unsafe.Pointer(applicationId)),
0,
uintptr(unsafe.Pointer(application)),
)
if !errors.Is(err, windows.NOERROR) {
return nil, fmt.Errorf("syscall returned: %w", err)
}
if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) {
return nil, result
}
return application, nil
}
// Close deinitializes the management infrastructure client API that was initialized through a call to Application_Initialize.
//
// https://learn.microsoft.com/en-us/windows/win32/api/mi/nf-mi-mi_application_close
func (application *Application) Close() error {
if application == nil || application.ft == nil {
return ErrNotInitialized
}
r0, _, _ := syscall.SyscallN(application.ft.Close, uintptr(unsafe.Pointer(application)))
if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) {
return result
}
return nil
}
// NewSession creates a session used to share connections for a set of operations to a single destination.
//
// https://learn.microsoft.com/en-us/windows/win32/api/mi/nf-mi-mi_application_newsession
func (application *Application) NewSession(options *DestinationOptions) (*Session, error) {
if application == nil || application.ft == nil {
return nil, ErrNotInitialized
}
session := &Session{}
r0, _, _ := syscall.SyscallN(
application.ft.NewSession,
uintptr(unsafe.Pointer(application)),
0,
0,
uintptr(unsafe.Pointer(options)),
0,
0,
uintptr(unsafe.Pointer(session)),
)
if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) {
return nil, result
}
defaultOperationOptions, err := application.NewOperationOptions()
if err != nil {
return nil, fmt.Errorf("failed to create default operation options: %w", err)
}
if err = defaultOperationOptions.SetTimeout(5 * time.Second); err != nil {
return nil, fmt.Errorf("failed to set timeout: %w", err)
}
session.defaultOperationOptions = defaultOperationOptions
return session, nil
}
// NewOperationOptions creates an OperationOptions object that can be used with the operation functions on the Session object.
//
// https://learn.microsoft.com/en-us/windows/win32/api/mi/nf-mi-mi_application_newoperationoptions
func (application *Application) NewOperationOptions() (*OperationOptions, error) {
if application == nil || application.ft == nil {
return nil, ErrNotInitialized
}
operationOptions := &OperationOptions{}
mustUnderstand := True
r0, _, _ := syscall.SyscallN(
application.ft.NewOperationOptions,
uintptr(unsafe.Pointer(application)),
uintptr(mustUnderstand),
uintptr(unsafe.Pointer(operationOptions)),
)
if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) {
return nil, result
}
return operationOptions, nil
}
// NewDestinationOptions creates an DestinationOptions object that can be used with the Application.NewSession function.
//
// https://learn.microsoft.com/en-us/windows/win32/api/mi/nf-mi-mi_application_newdestinationoptions
func (application *Application) NewDestinationOptions() (*DestinationOptions, error) {
if application == nil || application.ft == nil {
return nil, ErrNotInitialized
}
operationOptions := &DestinationOptions{}
r0, _, _ := syscall.SyscallN(
application.ft.NewDestinationOptions,
uintptr(unsafe.Pointer(application)),
uintptr(unsafe.Pointer(operationOptions)),
)
if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) {
return nil, result
}
return operationOptions, nil
}
// SetTimeout sets the timeout for the destination options.
//
// https://learn.microsoft.com/en-us/windows/win32/api/mi/nf-mi-mi_destinationoptions_settimeout
func (do *DestinationOptions) SetTimeout(timeout time.Duration) error {
if do == nil || do.ft == nil {
return ErrNotInitialized
}
r0, _, _ := syscall.SyscallN(
do.ft.SetInterval,
uintptr(unsafe.Pointer(do)),
uintptr(unsafe.Pointer(DestinationOptionsTimeout)),
uintptr(unsafe.Pointer(NewInterval(timeout))),
0,
)
if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) {
return result
}
return nil
}
// SetLocale sets the locale for the destination options.
//
// https://learn.microsoft.com/en-us/windows/win32/api/mi/nf-mi-mi_destinationoptions_setuilocale
func (do *DestinationOptions) SetLocale(locale string) error {
if do == nil || do.ft == nil {
return ErrNotInitialized
}
localeUTF16, err := windows.UTF16PtrFromString(locale)
if err != nil {
return fmt.Errorf("failed to convert locale: %w", err)
}
r0, _, _ := syscall.SyscallN(
do.ft.SetString,
uintptr(unsafe.Pointer(do)),
uintptr(unsafe.Pointer(DestinationOptionsUILocale)),
uintptr(unsafe.Pointer(localeUTF16)),
0,
)
if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) {
return result
}
return nil
}
func (do *DestinationOptions) Delete() error {
r0, _, _ := syscall.SyscallN(
do.ft.Delete,
uintptr(unsafe.Pointer(do)),
)
if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) {
return result
}
return nil
}

154
internal/mi/callbacks.go Normal file
View File

@@ -0,0 +1,154 @@
//go:build windows
package mi
import (
"errors"
"fmt"
"reflect"
"sync"
"unsafe"
"golang.org/x/sys/windows"
)
// We have to registry a global callback function, since the amount of callbacks is limited.
var operationUnmarshalCallbacksInstanceResult = sync.OnceValue[uintptr](func() uintptr {
return windows.NewCallback(func(
operation *Operation,
callbacks *OperationUnmarshalCallbacks,
instance *Instance,
moreResults Boolean,
instanceResult ResultError,
errorMessageUTF16 *uint16,
errorDetails *Instance,
_ uintptr,
) uintptr {
if moreResults == False {
defer operation.Close()
}
return callbacks.InstanceResult(operation, instance, moreResults, instanceResult, errorMessageUTF16, errorDetails)
})
})
type OperationUnmarshalCallbacks struct {
dst any
dv reflect.Value
errCh chan<- error
elemType reflect.Type
elemValue reflect.Value
}
func NewUnmarshalOperationsCallbacks(dst any, errCh chan<- error) (*OperationCallbacks[OperationUnmarshalCallbacks], error) {
dv := reflect.ValueOf(dst)
if dv.Kind() != reflect.Ptr || dv.IsNil() {
return nil, ErrInvalidEntityType
}
dv = dv.Elem()
elemType := dv.Type().Elem()
elemValue := reflect.ValueOf(reflect.New(elemType).Interface()).Elem()
if dv.Kind() != reflect.Slice || elemType.Kind() != reflect.Struct {
return nil, ErrInvalidEntityType
}
dv.Set(reflect.MakeSlice(dv.Type(), 0, 0))
return &OperationCallbacks[OperationUnmarshalCallbacks]{
CallbackContext: &OperationUnmarshalCallbacks{
errCh: errCh,
dst: dst,
dv: dv,
elemType: elemType,
elemValue: elemValue,
},
InstanceResult: operationUnmarshalCallbacksInstanceResult(),
}, nil
}
func (o *OperationUnmarshalCallbacks) InstanceResult(
_ *Operation,
instance *Instance,
moreResults Boolean,
instanceResult ResultError,
errorMessageUTF16 *uint16,
_ *Instance,
) uintptr {
defer func() {
if moreResults == False {
close(o.errCh)
}
}()
if !errors.Is(instanceResult, MI_RESULT_OK) {
o.errCh <- fmt.Errorf("%w: %s", instanceResult, windows.UTF16PtrToString(errorMessageUTF16))
return 0
}
if instance == nil {
return 0
}
counter, err := instance.GetElementCount()
if err != nil {
o.errCh <- fmt.Errorf("failed to get element count: %w", err)
return 0
}
if counter == 0 {
return 0
}
for i := range o.elemType.NumField() {
field := o.elemValue.Field(i)
// Check if the field has an `mi` tag
miTag := o.elemType.Field(i).Tag.Get("mi")
if miTag == "" {
continue
}
element, err := instance.GetElement(miTag)
if err != nil {
o.errCh <- fmt.Errorf("failed to get element: %w", err)
return 0
}
switch element.valueType {
case ValueTypeBOOLEAN:
field.SetBool(element.value == 1)
case ValueTypeUINT8, ValueTypeUINT16, ValueTypeUINT32, ValueTypeUINT64:
field.SetUint(uint64(element.value))
case ValueTypeSINT8, ValueTypeSINT16, ValueTypeSINT32, ValueTypeSINT64:
field.SetInt(int64(element.value))
case ValueTypeSTRING:
if element.value == 0 {
o.errCh <- fmt.Errorf("%s: invalid pointer: value is nil", miTag)
return 0
}
// Convert the UTF-16 string to a Go string
stringValue := windows.UTF16PtrToString((*uint16)(unsafe.Pointer(element.value)))
field.SetString(stringValue)
case ValueTypeREAL32, ValueTypeREAL64:
field.SetFloat(float64(element.value))
default:
o.errCh <- fmt.Errorf("unsupported value type: %d", element.valueType)
return 0
}
}
o.dv.Set(reflect.Append(o.dv, o.elemValue))
return 0
}

7
internal/mi/doc.go Normal file
View File

@@ -0,0 +1,7 @@
//go:build windows
// mi is a package that provides a Go API for Windows Management Infrastructure (MI) functions.
// It requires Windows Management Framework 3.0 or later.
//
// https://learn.microsoft.com/de-de/previous-versions/windows/desktop/wmi_v2/why-use-mi-
package mi

10
internal/mi/errors.go Normal file
View File

@@ -0,0 +1,10 @@
//go:build windows
package mi
import "errors"
var (
ErrNotInitialized = errors.New("not initialized")
ErrInvalidEntityType = errors.New("invalid entity type")
)

179
internal/mi/instance.go Normal file
View File

@@ -0,0 +1,179 @@
//go:build windows
package mi
import (
"errors"
"fmt"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
type Instance struct {
ft *InstanceFT
classDecl *ClassDecl
serverName *uint16
nameSpace *uint16
_ [4]uintptr
}
type InstanceFT struct {
Clone uintptr
Destruct uintptr
Delete uintptr
IsA uintptr
GetClassName uintptr
SetNameSpace uintptr
GetNameSpace uintptr
GetElementCount uintptr
AddElement uintptr
SetElement uintptr
SetElementAt uintptr
GetElement uintptr
GetElementAt uintptr
ClearElement uintptr
ClearElementAt uintptr
GetServerName uintptr
SetServerName uintptr
GetClass uintptr
}
type ClassDecl struct {
Flags uint32
Code uint32
Name *uint16
Mqualifiers uintptr
NumQualifiers uint32
Mproperties uintptr
NumProperties uint32
Size uint32
SuperClass *uint16
SuperClassDecl uintptr
Methods uintptr
NumMethods uint32
Schema uintptr
ProviderFT uintptr
OwningClass uintptr
}
func (instance *Instance) Delete() error {
if instance == nil || instance.ft == nil {
return ErrNotInitialized
}
r0, _, _ := syscall.SyscallN(instance.ft.Delete, uintptr(unsafe.Pointer(instance)))
if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) {
return result
}
return nil
}
func (instance *Instance) GetElement(elementName string) (*Element, error) {
if instance == nil || instance.ft == nil {
return nil, ErrNotInitialized
}
elementNameUTF16, err := windows.UTF16PtrFromString(elementName)
if err != nil {
return nil, fmt.Errorf("failed to convert element name %s to UTF-16: %w", elementName, err)
}
var (
value uintptr
valueType ValueType
)
r0, _, _ := syscall.SyscallN(
instance.ft.GetElement,
uintptr(unsafe.Pointer(instance)),
uintptr(unsafe.Pointer(elementNameUTF16)),
uintptr(unsafe.Pointer(&value)),
uintptr(unsafe.Pointer(&valueType)),
0,
0,
)
if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) {
return nil, result
}
return &Element{
value: value,
valueType: valueType,
}, nil
}
func (instance *Instance) GetElementCount() (uint32, error) {
if instance == nil || instance.ft == nil {
return 0, ErrNotInitialized
}
var count uint32
r0, _, _ := syscall.SyscallN(
instance.ft.GetElementCount,
uintptr(unsafe.Pointer(instance)),
uintptr(unsafe.Pointer(&count)),
)
if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) {
return 0, result
}
return count, nil
}
func (instance *Instance) GetClassName() (string, error) {
if instance == nil || instance.ft == nil {
return "", ErrNotInitialized
}
var classNameUTF16 *uint16
r0, _, _ := syscall.SyscallN(
instance.ft.GetClassName,
uintptr(unsafe.Pointer(instance)),
uintptr(unsafe.Pointer(&classNameUTF16)),
)
if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) {
return "", result
}
if classNameUTF16 == nil {
return "", errors.New("class name is nil")
}
return windows.UTF16PtrToString(classNameUTF16), nil
}
func Instance_Print(instance *Instance) (string, error) {
elementMap := map[string]any{}
properties := instance.classDecl.Properties()
count, err := instance.GetElementCount()
if err != nil {
return "", err
}
if count == 0 {
return "", nil
}
for _, property := range properties {
name := windows.UTF16PtrToString(property.Name)
element, _ := instance.GetElement(name)
value, _ := element.GetValue()
elementMap[windows.UTF16PtrToString(property.Name)] = value
}
return fmt.Sprintf("%v", elementMap), nil
}

View File

@@ -0,0 +1,43 @@
//go:build windows
package mi_test
import (
"testing"
"github.com/prometheus-community/windows_exporter/internal/mi"
"github.com/stretchr/testify/require"
)
func Benchmark_MI_Query_Unmarshal(b *testing.B) {
application, err := mi.Application_Initialize()
require.NoError(b, err)
require.NotEmpty(b, application)
session, err := application.NewSession(nil)
require.NoError(b, err)
require.NotEmpty(b, session)
b.ResetTimer()
var processes []win32Process
query, err := mi.NewQuery("SELECT Name FROM Win32_Process WHERE Handle = 0 OR Handle = 4")
require.NoError(b, err)
for i := 0; i < b.N; i++ {
err := session.QueryUnmarshal(&processes, mi.OperationFlagsStandardRTTI, nil, mi.NamespaceRootCIMv2, mi.QueryDialectWQL, query)
require.NoError(b, err)
require.Equal(b, []win32Process{{Name: "System Idle Process"}, {Name: "System"}}, processes)
}
b.StopTimer()
err = session.Close()
require.NoError(b, err)
err = application.Close()
require.NoError(b, err)
b.ReportAllocs()
}

294
internal/mi/mi_test.go Normal file
View File

@@ -0,0 +1,294 @@
//go:build windows
package mi_test
import (
"testing"
"time"
"github.com/prometheus-community/windows_exporter/internal/mi"
"github.com/prometheus-community/windows_exporter/internal/testutils"
"github.com/stretchr/testify/require"
"golang.org/x/sys/windows"
)
type win32Process struct {
Name string `mi:"Name"`
}
type wmiPrinter struct {
Name string `mi:"Name"`
Default bool `mi:"Default"`
PrinterStatus uint16 `mi:"PrinterStatus"`
JobCountSinceLastReset uint32 `mi:"JobCountSinceLastReset"`
}
type wmiPrintJob struct {
Name string `mi:"Name"`
Status string `mi:"Status"`
}
func Test_MI_Application_Initialize(t *testing.T) {
application, err := mi.Application_Initialize()
require.NoError(t, err)
require.NotEmpty(t, application)
err = application.Close()
require.NoError(t, err)
}
func Test_MI_Application_TestConnection(t *testing.T) {
application, err := mi.Application_Initialize()
require.NoError(t, err)
require.NotEmpty(t, application)
destinationOptions, err := application.NewDestinationOptions()
require.NoError(t, err)
require.NotEmpty(t, destinationOptions)
err = destinationOptions.SetTimeout(1 * time.Second)
require.NoError(t, err)
err = destinationOptions.SetLocale(mi.LocaleEnglish)
require.NoError(t, err)
session, err := application.NewSession(destinationOptions)
require.NoError(t, err)
require.NotEmpty(t, session)
err = session.TestConnection()
require.NoError(t, err)
require.NotEmpty(t, session)
err = session.Close()
require.NoError(t, err)
err = application.Close()
require.NoError(t, err)
}
func Test_MI_Query(t *testing.T) {
application, err := mi.Application_Initialize()
require.NoError(t, err)
require.NotEmpty(t, application)
destinationOptions, err := application.NewDestinationOptions()
require.NoError(t, err)
require.NotEmpty(t, destinationOptions)
err = destinationOptions.SetTimeout(1 * time.Second)
require.NoError(t, err)
err = destinationOptions.SetLocale(mi.LocaleEnglish)
require.NoError(t, err)
session, err := application.NewSession(destinationOptions)
require.NoError(t, err)
require.NotEmpty(t, session)
operation, err := session.QueryInstances(mi.OperationFlagsStandardRTTI, nil, mi.NamespaceRootCIMv2, mi.QueryDialectWQL, "select Name from win32_process where handle = 0")
require.NoError(t, err)
require.NotEmpty(t, operation)
instance, moreResults, err := operation.GetInstance()
require.NoError(t, err)
require.NotEmpty(t, instance)
count, err := instance.GetElementCount()
require.NoError(t, err)
require.NotZero(t, count)
element, err := instance.GetElement("Name")
require.NoError(t, err)
require.NotEmpty(t, element)
value, err := element.GetValue()
require.NoError(t, err)
require.Equal(t, "System Idle Process", value)
require.NotEmpty(t, value)
require.False(t, moreResults)
err = operation.Close()
require.NoError(t, err)
err = session.Close()
require.NoError(t, err)
err = application.Close()
require.NoError(t, err)
}
func Test_MI_QueryUnmarshal(t *testing.T) {
application, err := mi.Application_Initialize()
require.NoError(t, err)
require.NotEmpty(t, application)
destinationOptions, err := application.NewDestinationOptions()
require.NoError(t, err)
require.NotEmpty(t, destinationOptions)
err = destinationOptions.SetTimeout(1 * time.Second)
require.NoError(t, err)
err = destinationOptions.SetLocale(mi.LocaleEnglish)
require.NoError(t, err)
session, err := application.NewSession(destinationOptions)
require.NoError(t, err)
require.NotEmpty(t, session)
var processes []win32Process
queryProcess, err := mi.NewQuery("select Name from win32_process where handle = 0")
require.NoError(t, err)
err = session.QueryUnmarshal(&processes, mi.OperationFlagsStandardRTTI, nil, mi.NamespaceRootCIMv2, mi.QueryDialectWQL, queryProcess)
require.NoError(t, err)
require.Equal(t, []win32Process{{Name: "System Idle Process"}}, processes)
err = session.Close()
require.NoError(t, err)
err = application.Close()
require.NoError(t, err)
}
func Test_MI_EmptyQuery(t *testing.T) {
application, err := mi.Application_Initialize()
require.NoError(t, err)
require.NotEmpty(t, application)
destinationOptions, err := application.NewDestinationOptions()
require.NoError(t, err)
require.NotEmpty(t, destinationOptions)
err = destinationOptions.SetTimeout(1 * time.Second)
require.NoError(t, err)
err = destinationOptions.SetLocale(mi.LocaleEnglish)
require.NoError(t, err)
session, err := application.NewSession(destinationOptions)
require.NoError(t, err)
require.NotEmpty(t, session)
operation, err := session.QueryInstances(mi.OperationFlagsStandardRTTI, nil, mi.NamespaceRootCIMv2, mi.QueryDialectWQL, "SELECT Name, Status FROM win32_PrintJob")
require.NoError(t, err)
require.NotEmpty(t, operation)
instance, moreResults, err := operation.GetInstance()
require.NoError(t, err)
require.Empty(t, instance)
require.False(t, moreResults)
err = operation.Close()
require.NoError(t, err)
err = session.Close()
require.NoError(t, err)
err = application.Close()
require.NoError(t, err)
}
func Test_MI_Query_Unmarshal(t *testing.T) {
application, err := mi.Application_Initialize()
require.NoError(t, err)
require.NotEmpty(t, application)
destinationOptions, err := application.NewDestinationOptions()
require.NoError(t, err)
require.NotEmpty(t, destinationOptions)
err = destinationOptions.SetTimeout(1 * time.Second)
require.NoError(t, err)
err = destinationOptions.SetLocale(mi.LocaleEnglish)
require.NoError(t, err)
session, err := application.NewSession(destinationOptions)
require.NoError(t, err)
require.NotEmpty(t, session)
operation, err := session.QueryInstances(mi.OperationFlagsStandardRTTI, nil, mi.NamespaceRootCIMv2, mi.QueryDialectWQL, "SELECT Name FROM Win32_Process WHERE Handle = 0 OR Handle = 4")
require.NoError(t, err)
require.NotEmpty(t, operation)
var processes []win32Process
err = operation.Unmarshal(&processes)
require.NoError(t, err)
require.Equal(t, []win32Process{{Name: "System Idle Process"}, {Name: "System"}}, processes)
err = operation.Close()
require.NoError(t, err)
err = session.Close()
require.NoError(t, err)
err = application.Close()
require.NoError(t, err)
}
func Test_MI_FD_Leak(t *testing.T) {
t.Skip("This test is disabled because it is not deterministic and may fail on some systems.")
application, err := mi.Application_Initialize()
require.NoError(t, err)
require.NotEmpty(t, application)
session, err := application.NewSession(nil)
require.NoError(t, err)
require.NotEmpty(t, session)
currentFileHandle, err := testutils.GetProcessHandleCount(windows.CurrentProcess())
require.NoError(t, err)
t.Log("Current File Handle Count: ", currentFileHandle)
queryPrinter, err := mi.NewQuery("SELECT Name, Default, PrinterStatus, JobCountSinceLastReset FROM win32_Printer")
require.NoError(t, err)
queryPrinterJob, err := mi.NewQuery("SELECT Name, Status FROM win32_PrintJob")
require.NoError(t, err)
for range 1000 {
var wmiPrinters []wmiPrinter
err := session.Query(&wmiPrinters, mi.NamespaceRootCIMv2, queryPrinter)
require.NoError(t, err)
var wmiPrintJobs []wmiPrintJob
err = session.Query(&wmiPrintJobs, mi.NamespaceRootCIMv2, queryPrinterJob)
require.NoError(t, err)
currentFileHandle, err = testutils.GetProcessHandleCount(windows.CurrentProcess())
require.NoError(t, err)
t.Log("Current File Handle Count: ", currentFileHandle)
}
currentFileHandle, err = testutils.GetProcessHandleCount(windows.CurrentProcess())
require.NoError(t, err)
t.Log("Current File Handle Count: ", currentFileHandle)
err = session.Close()
require.NoError(t, err)
currentFileHandle, err = testutils.GetProcessHandleCount(windows.CurrentProcess())
require.NoError(t, err)
t.Log("Current File Handle Count: ", currentFileHandle)
err = application.Close()
require.NoError(t, err)
currentFileHandle, err = testutils.GetProcessHandleCount(windows.CurrentProcess())
require.NoError(t, err)
t.Log("Current File Handle Count: ", currentFileHandle)
}

272
internal/mi/operation.go Normal file
View File

@@ -0,0 +1,272 @@
//go:build windows
package mi
import (
"errors"
"fmt"
"reflect"
"syscall"
"time"
"unsafe"
"golang.org/x/sys/windows"
)
// OperationOptionsTimeout is the key for the timeout option.
//
// https://github.com/microsoft/win32metadata/blob/527806d20d83d3abd43d16cd3fa8795d8deba343/generation/WinSDK/RecompiledIdlHeaders/um/mi.h#L9240
var OperationOptionsTimeout = UTF16PtrFromString[*uint16]("__MI_OPERATIONOPTIONS_TIMEOUT")
// OperationFlags represents the flags for an operation.
//
// https://learn.microsoft.com/en-us/previous-versions/windows/desktop/wmi_v2/mi-flags
type OperationFlags uint32
const (
OperationFlagsDefaultRTTI OperationFlags = 0x0000
OperationFlagsBasicRTTI OperationFlags = 0x0002
OperationFlagsNoRTTI OperationFlags = 0x0400
OperationFlagsStandardRTTI OperationFlags = 0x0800
OperationFlagsFullRTTI OperationFlags = 0x0004
)
// Operation represents an operation.
// https://learn.microsoft.com/en-us/windows/win32/api/mi/ns-mi-mi_operation
type Operation struct {
reserved1 uint64
reserved2 uintptr
ft *OperationFT
}
// OperationFT represents the function table for Operation.
// https://learn.microsoft.com/en-us/windows/win32/api/mi/ns-mi-mi_operationft
type OperationFT struct {
Close uintptr
Cancel uintptr
GetSession uintptr
GetInstance uintptr
GetIndication uintptr
GetClass uintptr
}
type OperationOptions struct {
reserved1 uint64
reserved2 uintptr
ft *OperationOptionsFT
}
type OperationOptionsFT struct {
Delete uintptr
SetString uintptr
SetNumber uintptr
SetCustomOption uintptr
GetString uintptr
GetNumber uintptr
GetOptionCount uintptr
GetOptionAt uintptr
GetOption uintptr
GetEnabledChannels uintptr
Clone uintptr
SetInterval uintptr
GetInterval uintptr
}
type OperationCallbacks[T any] struct {
CallbackContext *T
PromptUser uintptr
WriteError uintptr
WriteMessage uintptr
WriteProgress uintptr
InstanceResult uintptr
IndicationResult uintptr
ClassResult uintptr
StreamedParameterResult uintptr
}
// Close closes an operation handle.
//
// https://learn.microsoft.com/en-us/windows/win32/api/mi/nf-mi-mi_operation_close
func (o *Operation) Close() error {
if o == nil || o.ft == nil {
return ErrNotInitialized
}
r0, _, _ := syscall.SyscallN(o.ft.Close, uintptr(unsafe.Pointer(o)))
if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) {
return result
}
return nil
}
func (o *Operation) Cancel() error {
if o == nil || o.ft == nil {
return ErrNotInitialized
}
r0, _, _ := syscall.SyscallN(o.ft.Close, uintptr(unsafe.Pointer(o)), 0)
if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) {
return result
}
return nil
}
func (o *Operation) GetInstance() (*Instance, bool, error) {
if o == nil || o.ft == nil {
return nil, false, ErrNotInitialized
}
var (
instance *Instance
errorDetails *Instance
moreResults Boolean
instanceResult ResultError
errorMessageUTF16 *uint16
)
r0, _, _ := syscall.SyscallN(
o.ft.GetInstance,
uintptr(unsafe.Pointer(o)),
uintptr(unsafe.Pointer(&instance)),
uintptr(unsafe.Pointer(&moreResults)),
uintptr(unsafe.Pointer(&instanceResult)),
uintptr(unsafe.Pointer(&errorMessageUTF16)),
uintptr(unsafe.Pointer(&errorDetails)),
)
if !errors.Is(instanceResult, MI_RESULT_OK) {
return nil, false, fmt.Errorf("instance result: %w (%s)", instanceResult, windows.UTF16PtrToString(errorMessageUTF16))
}
if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) {
return nil, false, result
}
return instance, moreResults == True, nil
}
func (o *Operation) Unmarshal(dst any) error {
if o == nil || o.ft == nil {
return ErrNotInitialized
}
dv := reflect.ValueOf(dst)
if dv.Kind() != reflect.Ptr || dv.IsNil() {
return ErrInvalidEntityType
}
dv = dv.Elem()
elemType := dv.Type().Elem()
elemValue := reflect.ValueOf(reflect.New(elemType).Interface()).Elem()
if dv.Kind() != reflect.Slice || elemType.Kind() != reflect.Struct {
return ErrInvalidEntityType
}
dv.Set(reflect.MakeSlice(dv.Type(), 0, 0))
for {
instance, moreResults, err := o.GetInstance()
if err != nil {
return fmt.Errorf("failed to get instance: %w", err)
}
// If WMI returns nil, it means there are no more results.
if instance == nil {
break
}
counter, err := instance.GetElementCount()
if err != nil {
return fmt.Errorf("failed to get element count: %w", err)
}
if counter == 0 {
break
}
for i := range elemType.NumField() {
field := elemValue.Field(i)
// Check if the field has an `mi` tag
miTag := elemType.Field(i).Tag.Get("mi")
if miTag == "" {
continue
}
element, err := instance.GetElement(miTag)
if err != nil {
return fmt.Errorf("failed to get element: %w", err)
}
switch element.valueType {
case ValueTypeBOOLEAN:
field.SetBool(element.value == 1)
case ValueTypeUINT8, ValueTypeUINT16, ValueTypeUINT32, ValueTypeUINT64:
field.SetUint(uint64(element.value))
case ValueTypeSINT8, ValueTypeSINT16, ValueTypeSINT32, ValueTypeSINT64:
field.SetInt(int64(element.value))
case ValueTypeSTRING:
if element.value == 0 {
return fmt.Errorf("%s: invalid pointer: value is nil", miTag)
}
// Convert the UTF-16 string to a Go string
stringValue := windows.UTF16PtrToString((*uint16)(unsafe.Pointer(element.value)))
field.SetString(stringValue)
case ValueTypeREAL32, ValueTypeREAL64:
field.SetFloat(float64(element.value))
default:
return fmt.Errorf("unsupported value type: %d", element.valueType)
}
}
dv.Set(reflect.Append(dv, elemValue))
if !moreResults {
break
}
}
return nil
}
func (o *OperationOptions) SetTimeout(timeout time.Duration) error {
if o == nil || o.ft == nil {
return ErrNotInitialized
}
r0, _, _ := syscall.SyscallN(
o.ft.SetInterval,
uintptr(unsafe.Pointer(o)),
uintptr(unsafe.Pointer(OperationOptionsTimeout)),
uintptr(unsafe.Pointer(NewInterval(timeout))),
0,
)
if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) {
return result
}
return nil
}
func (o *OperationOptions) Delete() error {
if o == nil || o.ft == nil {
return ErrNotInitialized
}
r0, _, _ := syscall.SyscallN(o.ft.Delete, uintptr(unsafe.Pointer(o)))
if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) {
return result
}
return nil
}

102
internal/mi/result.go Normal file
View File

@@ -0,0 +1,102 @@
//go:build windows
package mi
import "errors"
type ResultError uint32
const (
MI_RESULT_OK ResultError = iota
MI_RESULT_FAILED
MI_RESULT_ACCESS_DENIED
MI_RESULT_INVALID_NAMESPACE
MI_RESULT_INVALID_PARAMETER
MI_RESULT_INVALID_CLASS
MI_RESULT_NOT_FOUND
MI_RESULT_NOT_SUPPORTED
MI_RESULT_CLASS_HAS_CHILDREN
MI_RESULT_CLASS_HAS_INSTANCES
MI_RESULT_INVALID_SUPERCLASS
MI_RESULT_ALREADY_EXISTS
MI_RESULT_NO_SUCH_PROPERTY
MI_RESULT_TYPE_MISMATCH
MI_RESULT_QUERY_LANGUAGE_NOT_SUPPORTED
MI_RESULT_INVALID_QUERY
MI_RESULT_METHOD_NOT_AVAILABLE
MI_RESULT_METHOD_NOT_FOUND
MI_RESULT_NAMESPACE_NOT_EMPTY
MI_RESULT_INVALID_ENUMERATION_CONTEXT
MI_RESULT_INVALID_OPERATION_TIMEOUT
MI_RESULT_PULL_HAS_BEEN_ABANDONED
MI_RESULT_PULL_CANNOT_BE_ABANDONED
MI_RESULT_FILTERED_ENUMERATION_NOT_SUPPORTED
MI_RESULT_CONTINUATION_ON_ERROR_NOT_SUPPORTED
MI_RESULT_SERVER_LIMITS_EXCEEDED
MI_RESULT_SERVER_IS_SHUTTING_DOWN
)
func (r ResultError) Error() string {
return r.String()
}
func (r ResultError) String() string {
switch {
case errors.Is(r, MI_RESULT_OK):
return "MI_RESULT_OK"
case errors.Is(r, MI_RESULT_FAILED):
return "MI_RESULT_FAILED"
case errors.Is(r, MI_RESULT_ACCESS_DENIED):
return "MI_RESULT_ACCESS_DENIED"
case errors.Is(r, MI_RESULT_INVALID_NAMESPACE):
return "MI_RESULT_INVALID_NAMESPACE"
case errors.Is(r, MI_RESULT_INVALID_PARAMETER):
return "MI_RESULT_INVALID_PARAMETER"
case errors.Is(r, MI_RESULT_INVALID_CLASS):
return "MI_RESULT_INVALID_CLASS"
case errors.Is(r, MI_RESULT_NOT_FOUND):
return "MI_RESULT_NOT_FOUND"
case errors.Is(r, MI_RESULT_NOT_SUPPORTED):
return "MI_RESULT_NOT_SUPPORTED"
case errors.Is(r, MI_RESULT_CLASS_HAS_CHILDREN):
return "MI_RESULT_CLASS_HAS_CHILDREN"
case errors.Is(r, MI_RESULT_CLASS_HAS_INSTANCES):
return "MI_RESULT_CLASS_HAS_INSTANCES"
case errors.Is(r, MI_RESULT_INVALID_SUPERCLASS):
return "MI_RESULT_INVALID_SUPERCLASS"
case errors.Is(r, MI_RESULT_ALREADY_EXISTS):
return "MI_RESULT_ALREADY_EXISTS"
case errors.Is(r, MI_RESULT_NO_SUCH_PROPERTY):
return "MI_RESULT_NO_SUCH_PROPERTY"
case errors.Is(r, MI_RESULT_TYPE_MISMATCH):
return "MI_RESULT_TYPE_MISMATCH"
case errors.Is(r, MI_RESULT_QUERY_LANGUAGE_NOT_SUPPORTED):
return "MI_RESULT_QUERY_LANGUAGE_NOT_SUPPORTED"
case errors.Is(r, MI_RESULT_INVALID_QUERY):
return "MI_RESULT_INVALID_QUERY"
case errors.Is(r, MI_RESULT_METHOD_NOT_AVAILABLE):
return "MI_RESULT_METHOD_NOT_AVAILABLE"
case errors.Is(r, MI_RESULT_METHOD_NOT_FOUND):
return "MI_RESULT_METHOD_NOT_FOUND"
case errors.Is(r, MI_RESULT_NAMESPACE_NOT_EMPTY):
return "MI_RESULT_NAMESPACE_NOT_EMPTY"
case errors.Is(r, MI_RESULT_INVALID_ENUMERATION_CONTEXT):
return "MI_RESULT_INVALID_ENUMERATION_CONTEXT"
case errors.Is(r, MI_RESULT_INVALID_OPERATION_TIMEOUT):
return "MI_RESULT_INVALID_OPERATION_TIMEOUT"
case errors.Is(r, MI_RESULT_PULL_HAS_BEEN_ABANDONED):
return "MI_RESULT_PULL_HAS_BEEN_ABANDONED"
case errors.Is(r, MI_RESULT_PULL_CANNOT_BE_ABANDONED):
return "MI_RESULT_PULL_CANNOT_BE_ABANDONED"
case errors.Is(r, MI_RESULT_FILTERED_ENUMERATION_NOT_SUPPORTED):
return "MI_RESULT_FILTERED_ENUMERATION_NOT_SUPPORTED"
case errors.Is(r, MI_RESULT_CONTINUATION_ON_ERROR_NOT_SUPPORTED):
return "MI_RESULT_CONTINUATION_ON_ERROR_NOT_SUPPORTED"
case errors.Is(r, MI_RESULT_SERVER_LIMITS_EXCEEDED):
return "MI_RESULT_SERVER_LIMITS_EXCEEDED"
case errors.Is(r, MI_RESULT_SERVER_IS_SHUTTING_DOWN):
return "MI_RESULT_SERVER_IS_SHUTTING_DOWN"
default:
return "MI_RESULT_UNKNOWN"
}
}

235
internal/mi/session.go Normal file
View File

@@ -0,0 +1,235 @@
//go:build windows
package mi
import (
"errors"
"fmt"
"runtime"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
// Session represents a session.
//
// https://learn.microsoft.com/en-us/windows/win32/api/mi/ns-mi-mi_session
type Session struct {
reserved1 uint64
reserved2 uintptr
ft *SessionFT
defaultOperationOptions *OperationOptions
}
// SessionFT represents the function table for Session.
//
// https://learn.microsoft.com/en-us/windows/win32/api/mi/ns-mi-mi_session
type SessionFT struct {
Close uintptr
GetApplication uintptr
GetInstance uintptr
ModifyInstance uintptr
CreateInstance uintptr
DeleteInstance uintptr
Invoke uintptr
EnumerateInstances uintptr
QueryInstances uintptr
AssociatorInstances uintptr
ReferenceInstances uintptr
Subscribe uintptr
GetClass uintptr
EnumerateClasses uintptr
TestConnection uintptr
}
// Close closes a session and releases all associated memory.
//
// https://learn.microsoft.com/en-us/windows/win32/api/mi/nf-mi-mi_session_close
func (s *Session) Close() error {
if s == nil || s.ft == nil {
return ErrNotInitialized
}
if s.defaultOperationOptions != nil {
_ = s.defaultOperationOptions.Delete()
}
r0, _, _ := syscall.SyscallN(s.ft.Close,
uintptr(unsafe.Pointer(s)),
0,
0,
)
if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) {
return result
}
return nil
}
// TestConnection queries instances. It is used to test the connection.
// The function returns an operation that can be used to retrieve the result with [Operation.GetInstance]. The operation must be closed with [Operation.Close].
// The instance returned by [Operation.GetInstance] is always nil.
//
// https://learn.microsoft.com/en-us/windows/win32/api/mi/nf-mi-mi_session_testconnection
func (s *Session) TestConnection() error {
if s == nil || s.ft == nil {
return ErrNotInitialized
}
operation := &Operation{}
// ref: https://github.com/KurtDeGreeff/omi/blob/9caa55032a1070a665e14fd282a091f6247d13c3/Unix/scriptext/py/PMI_Session.c#L92-L105
r0, _, _ := syscall.SyscallN(
s.ft.TestConnection,
uintptr(unsafe.Pointer(s)),
0,
0,
uintptr(unsafe.Pointer(operation)),
)
if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) {
return result
}
var err error
if _, _, err = operation.GetInstance(); err != nil {
return fmt.Errorf("failed to get instance: %w", err)
}
if err = operation.Close(); err != nil {
return fmt.Errorf("failed to close operation: %w", err)
}
return nil
}
// GetApplication gets the Application handle that was used to create the specified session.
//
// https://learn.microsoft.com/en-us/windows/win32/api/mi/nf-mi-mi_session_getapplication
func (s *Session) GetApplication() (*Application, error) {
if s == nil || s.ft == nil {
return nil, ErrNotInitialized
}
application := &Application{}
r0, _, _ := syscall.SyscallN(
s.ft.GetApplication,
uintptr(unsafe.Pointer(s)),
uintptr(unsafe.Pointer(application)),
)
if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) {
return nil, result
}
return application, nil
}
// QueryInstances queries for a set of instances based on a query expression.
//
// https://learn.microsoft.com/en-us/windows/win32/api/mi/nf-mi-mi_session_queryinstances
func (s *Session) QueryInstances(flags OperationFlags, operationOptions *OperationOptions, namespaceName Namespace,
queryDialect QueryDialect, queryExpression string,
) (*Operation, error) {
if s == nil || s.ft == nil {
return nil, ErrNotInitialized
}
queryExpressionUTF16, err := windows.UTF16PtrFromString(queryExpression)
if err != nil {
return nil, err
}
operation := &Operation{}
if operationOptions == nil {
operationOptions = s.defaultOperationOptions
}
r0, _, _ := syscall.SyscallN(
s.ft.QueryInstances,
uintptr(unsafe.Pointer(s)),
uintptr(flags),
uintptr(unsafe.Pointer(operationOptions)),
uintptr(unsafe.Pointer(namespaceName)),
uintptr(unsafe.Pointer(queryDialect)),
uintptr(unsafe.Pointer(queryExpressionUTF16)),
0,
uintptr(unsafe.Pointer(operation)),
)
if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) {
return nil, result
}
return operation, nil
}
// QueryUnmarshal queries for a set of instances based on a query expression.
//
// https://learn.microsoft.com/en-us/windows/win32/api/mi/nf-mi-mi_session_queryinstances
func (s *Session) QueryUnmarshal(dst any,
flags OperationFlags, operationOptions *OperationOptions,
namespaceName Namespace, queryDialect QueryDialect, queryExpression Query,
) error {
if s == nil || s.ft == nil {
return ErrNotInitialized
}
operation := &Operation{}
if operationOptions == nil {
operationOptions = s.defaultOperationOptions
}
errCh := make(chan error, 1)
operationCallbacks, err := NewUnmarshalOperationsCallbacks(dst, errCh)
if err != nil {
return err
}
r0, _, _ := syscall.SyscallN(
s.ft.QueryInstances,
uintptr(unsafe.Pointer(s)),
uintptr(flags),
uintptr(unsafe.Pointer(operationOptions)),
uintptr(unsafe.Pointer(namespaceName)),
uintptr(unsafe.Pointer(queryDialect)),
uintptr(unsafe.Pointer(queryExpression)),
uintptr(unsafe.Pointer(operationCallbacks)),
uintptr(unsafe.Pointer(operation)),
)
if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) {
return result
}
errs := make([]error, 0)
for err := range errCh {
if err != nil {
errs = append(errs, err)
}
}
// KeepAlive is used to ensure that the callbacks are not garbage collected before the operation is closed.
runtime.KeepAlive(operationCallbacks.CallbackContext)
return errors.Join(errs...)
}
// Query queries for a set of instances based on a query expression.
func (s *Session) Query(dst any, namespaceName Namespace, queryExpression Query) error {
err := s.QueryUnmarshal(dst, OperationFlagsStandardRTTI, nil, namespaceName, QueryDialectWQL, queryExpression)
if err != nil {
return fmt.Errorf("WMI query failed: %w", err)
}
return nil
}

137
internal/mi/types.go Normal file
View File

@@ -0,0 +1,137 @@
//go:build windows
package mi
import (
"time"
"unsafe"
"github.com/prometheus-community/windows_exporter/internal/utils"
"golang.org/x/sys/windows"
)
type Boolean uint8
const (
False Boolean = 0
True Boolean = 1
)
type QueryDialect *uint16
func NewQueryDialect(queryDialect string) (QueryDialect, error) {
return windows.UTF16PtrFromString(queryDialect)
}
var (
QueryDialectWQL = utils.Must(NewQueryDialect("WQL"))
QueryDialectCQL = utils.Must(NewQueryDialect("CQL"))
)
type Namespace *uint16
func NewNamespace(namespace string) (Namespace, error) {
return windows.UTF16PtrFromString(namespace)
}
var (
NamespaceRootCIMv2 = utils.Must(NewNamespace("root/CIMv2"))
NamespaceRootWindowsFSRM = utils.Must(NewNamespace("root/microsoft/windows/fsrm"))
NamespaceRootWebAdministration = utils.Must(NewNamespace("root/WebAdministration"))
NamespaceRootMSCluster = utils.Must(NewNamespace("root/MSCluster"))
)
type Query *uint16
func NewQuery(query string) (Query, error) {
return windows.UTF16PtrFromString(query)
}
// UTF16PtrFromString converts a string to a UTF-16 pointer at initialization time.
//
//nolint:ireturn
func UTF16PtrFromString[T *uint16](s string) T {
val, err := windows.UTF16PtrFromString(s)
if err != nil {
panic(err)
}
return val
}
type Timestamp struct {
Year uint32
Month uint32
Day uint32
Hour uint32
Minute uint32
Second uint32
Microseconds uint32
UTC int32
}
type Interval struct {
Days uint32
Hours uint32
Minutes uint32
Seconds uint32
Microseconds uint32
Padding1 uint32
Padding2 uint32
Padding3 uint32
}
func NewInterval(interval time.Duration) *Interval {
// Convert the duration to a number of microseconds
microseconds := interval.Microseconds()
// Create a new interval with the microseconds
return &Interval{
Days: uint32(microseconds / (24 * 60 * 60 * 1000000)),
Hours: uint32(microseconds / (60 * 60 * 1000000)),
Minutes: uint32(microseconds / (60 * 1000000)),
Seconds: uint32(microseconds / 1000000),
Microseconds: uint32(microseconds % 1000000),
}
}
type Datetime struct {
IsTimestamp bool
Timestamp *Timestamp // Used when IsTimestamp is true
Interval *Interval // Used when IsTimestamp is false
}
type PropertyDecl struct {
Flags uint32
Code uint32
Name *uint16
Mqualifiers uintptr
NumQualifiers uint32
PropertyType ValueType
ClassName *uint16
Subscript uint32
Offset uint32
Origin *uint16
Propagator *uint16
Value uintptr
}
func (c *ClassDecl) Properties() []*PropertyDecl {
// Create a slice to hold the properties
properties := make([]*PropertyDecl, c.NumProperties)
// Mproperties is a pointer to an array of pointers to PropertyDecl
propertiesArray := (**PropertyDecl)(unsafe.Pointer(c.Mproperties))
// Iterate over the number of properties and fetch each property
for i := range c.NumProperties {
// Get the property pointer at index i
propertyPtr := *(**PropertyDecl)(unsafe.Pointer(uintptr(unsafe.Pointer(propertiesArray)) + uintptr(i)*unsafe.Sizeof(uintptr(0))))
// Append the property to the slice
properties[i] = propertyPtr
}
// Return the slice of properties
return properties
}

112
internal/mi/value.go Normal file
View File

@@ -0,0 +1,112 @@
//go:build windows
package mi
import (
"errors"
"fmt"
"unsafe"
"golang.org/x/sys/windows"
)
type ValueType int
const (
ValueTypeBOOLEAN ValueType = iota
ValueTypeUINT8
ValueTypeSINT8
ValueTypeUINT16
ValueTypeSINT16
ValueTypeUINT32
ValueTypeSINT32
ValueTypeUINT64
ValueTypeSINT64
ValueTypeREAL32
ValueTypeREAL64
ValueTypeCHAR16
ValueTypeDATETIME
ValueTypeSTRING
ValueTypeREFERENCE
ValueTypeINSTANCE
ValueTypeBOOLEANA
ValueTypeUINT8A
ValueTypeSINT8A
ValueTypeUINT16A
ValueTypeSINT16A
ValueTypeUINT32A
ValueTypeSINT32A
ValueTypeUINT64A
ValueTypeSINT64A
ValueTypeREAL32A
ValueTypeREAL64A
ValueTypeCHAR16A
ValueTypeDATETIMEA
ValueTypeSTRINGA
ValueTypeREFERENCEA
ValueTypeINSTANCEA
ValueTypeARRAY ValueType = 16
)
type Element struct {
value uintptr
valueType ValueType
}
func (e *Element) GetValue() (any, error) {
switch e.valueType {
case ValueTypeBOOLEAN:
return e.value == 1, nil
case ValueTypeUINT8:
return uint8(e.value), nil
case ValueTypeSINT8:
return int8(e.value), nil
case ValueTypeUINT16:
return uint16(e.value), nil
case ValueTypeSINT16:
return int16(e.value), nil
case ValueTypeUINT32:
return uint32(e.value), nil
case ValueTypeSINT32:
return int32(e.value), nil
case ValueTypeUINT64:
return uint64(e.value), nil
case ValueTypeSINT64:
return int64(e.value), nil
case ValueTypeREAL32:
return float32(e.value), nil
case ValueTypeREAL64:
return float64(e.value), nil
case ValueTypeCHAR16:
return uint16(e.value), nil
case ValueTypeDATETIME:
if e.value == 0 {
return nil, errors.New("invalid pointer: value is nil")
}
return *(*Datetime)(unsafe.Pointer(e.value)), nil
case ValueTypeSTRING:
if e.value == 0 {
return nil, errors.New("invalid pointer: value is nil")
}
// Convert the UTF-16 string to a Go string
return windows.UTF16PtrToString((*uint16)(unsafe.Pointer(e.value))), nil
case ValueTypeSTRINGA:
if e.value == 0 {
return nil, errors.New("invalid pointer: value is nil")
}
// Assuming array of pointers to UTF-16 strings
ptrArray := *(*[]*uint16)(unsafe.Pointer(e.value))
strArray := make([]string, len(ptrArray))
for i, ptr := range ptrArray {
strArray[i] = windows.UTF16PtrToString(ptr)
}
return strArray, nil
default:
return nil, fmt.Errorf("unsupported value type: %d", e.valueType)
}
}