// SPDX-License-Identifier: Apache-2.0 // // Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //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 } 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) // We need an active go routine to prevent a // fatal error: all goroutines are asleep - deadlock! // ref: https://github.com/golang/go/issues/55015 // go time.Sleep(5 * time.Second) for { if err, ok := <-errCh; err != nil { errs = append(errs, err) } else if !ok { break } } // 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 }