mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-02-18 18:56:35 +00:00
mi: replace all WMI calls with MI calls (#1714)
This commit is contained in:
283
internal/mi/application.go
Normal file
283
internal/mi/application.go
Normal 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
154
internal/mi/callbacks.go
Normal 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
7
internal/mi/doc.go
Normal 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
10
internal/mi/errors.go
Normal 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
179
internal/mi/instance.go
Normal 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
|
||||
}
|
||||
43
internal/mi/mi_bench_test.go
Normal file
43
internal/mi/mi_bench_test.go
Normal 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
294
internal/mi/mi_test.go
Normal 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
272
internal/mi/operation.go
Normal 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
102
internal/mi/result.go
Normal 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
235
internal/mi/session.go
Normal 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
137
internal/mi/types.go
Normal 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
112
internal/mi/value.go
Normal 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user