229 lines
6.8 KiB
Go
229 lines
6.8 KiB
Go
|
//go:build windows
|
||
|
|
||
|
package win_wmi
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"runtime"
|
||
|
|
||
|
"github.com/go-ole/go-ole"
|
||
|
"github.com/go-ole/go-ole/oleutil"
|
||
|
|
||
|
"github.com/influxdata/telegraf"
|
||
|
"github.com/influxdata/telegraf/config"
|
||
|
"github.com/influxdata/telegraf/filter"
|
||
|
"github.com/influxdata/telegraf/internal"
|
||
|
)
|
||
|
|
||
|
type method struct {
|
||
|
Namespace string `toml:"namespace"`
|
||
|
ClassName string `toml:"class_name"`
|
||
|
Method string `toml:"method"`
|
||
|
Arguments map[string]interface{} `toml:"arguments"`
|
||
|
FieldMapping map[string]string `toml:"fields"`
|
||
|
Filter string `toml:"filter"`
|
||
|
TagPropertiesInclude []string `toml:"tag_properties"`
|
||
|
|
||
|
host string
|
||
|
connectionParams []interface{}
|
||
|
tagFilter filter.Filter
|
||
|
}
|
||
|
|
||
|
func (m *method) prepare(host string, username, password config.Secret) error {
|
||
|
// Compile the filter
|
||
|
f, err := filter.Compile(m.TagPropertiesInclude)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("compiling tag-filter failed: %w", err)
|
||
|
}
|
||
|
m.tagFilter = f
|
||
|
|
||
|
// Setup the connection parameters
|
||
|
m.host = host
|
||
|
if m.host != "" {
|
||
|
m.connectionParams = append(m.connectionParams, m.host)
|
||
|
} else {
|
||
|
m.connectionParams = append(m.connectionParams, nil)
|
||
|
}
|
||
|
m.connectionParams = append(m.connectionParams, m.Namespace)
|
||
|
if !username.Empty() {
|
||
|
u, err := username.Get()
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("getting username secret failed: %w", err)
|
||
|
}
|
||
|
m.connectionParams = append(m.connectionParams, u.String())
|
||
|
username.Destroy()
|
||
|
}
|
||
|
if !password.Empty() {
|
||
|
p, err := password.Get()
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("getting password secret failed: %w", err)
|
||
|
}
|
||
|
m.connectionParams = append(m.connectionParams, p.String())
|
||
|
password.Destroy()
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (m *method) execute(acc telegraf.Accumulator) error {
|
||
|
// The only way to run WMI queries in parallel while being thread-safe is to
|
||
|
// ensure the CoInitialize[Ex]() call is bound to its current OS thread.
|
||
|
// Otherwise, attempting to initialize and run parallel queries across
|
||
|
// goroutines will result in protected memory errors.
|
||
|
runtime.LockOSThread()
|
||
|
defer runtime.UnlockOSThread()
|
||
|
|
||
|
// Init the COM client
|
||
|
if err := ole.CoInitializeEx(0, ole.COINIT_APARTMENTTHREADED); err != nil {
|
||
|
var oleCode *ole.OleError
|
||
|
if !errors.As(err, &oleCode) || (oleCode.Code() != ole.S_OK && oleCode.Code() != sFalse) {
|
||
|
return fmt.Errorf("initialization of COM object failed: %w", err)
|
||
|
}
|
||
|
}
|
||
|
defer ole.CoUninitialize()
|
||
|
|
||
|
// Initialize the WMI service
|
||
|
locator, err := oleutil.CreateObject("WbemScripting.SWbemLocator")
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("creation of OLE object failed: %w", err)
|
||
|
}
|
||
|
if locator == nil {
|
||
|
return errors.New("failed to create WbemScripting.SWbemLocator, maybe WMI is broken")
|
||
|
}
|
||
|
defer locator.Release()
|
||
|
wmi, err := locator.QueryInterface(ole.IID_IDispatch)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to query interface: %w", err)
|
||
|
}
|
||
|
defer wmi.Release()
|
||
|
|
||
|
serviceRaw, err := oleutil.CallMethod(wmi, "ConnectServer", m.connectionParams...)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed calling method ConnectServer: %w", err)
|
||
|
}
|
||
|
service := serviceRaw.ToIDispatch()
|
||
|
defer serviceRaw.Clear()
|
||
|
|
||
|
// Get the specified class-method
|
||
|
classRaw, err := oleutil.CallMethod(service, "Get", m.ClassName)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to get class %s: %w", m.ClassName, err)
|
||
|
}
|
||
|
class := classRaw.ToIDispatch()
|
||
|
defer classRaw.Clear()
|
||
|
|
||
|
classMethodsRaw, err := class.GetProperty("Methods_")
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to call method %s: %w", m.Method, err)
|
||
|
}
|
||
|
classMethods := classMethodsRaw.ToIDispatch()
|
||
|
defer classMethodsRaw.Clear()
|
||
|
|
||
|
methodRaw, err := classMethods.CallMethod("Item", m.Method)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to call method %s: %w", m.Method, err)
|
||
|
}
|
||
|
method := methodRaw.ToIDispatch()
|
||
|
defer methodRaw.Clear()
|
||
|
|
||
|
// Fill the input parameters of the method
|
||
|
inputParamsRaw, err := oleutil.GetProperty(method, "InParameters")
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to get input parameters for %s: %w", m.Method, err)
|
||
|
}
|
||
|
inputParams := inputParamsRaw.ToIDispatch()
|
||
|
defer inputParamsRaw.Clear()
|
||
|
for k, v := range m.Arguments {
|
||
|
if _, err := inputParams.PutProperty(k, v); err != nil {
|
||
|
return fmt.Errorf("setting param %q for method %q failed: %w", k, m.Method, err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Get the output parameters of the method
|
||
|
outputParamsRaw, err := oleutil.GetProperty(method, "OutParameters")
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to get output parameters for %s: %w", m.Method, err)
|
||
|
}
|
||
|
outputParams := outputParamsRaw.ToIDispatch()
|
||
|
defer outputParamsRaw.Clear()
|
||
|
|
||
|
// Execute the method
|
||
|
outputRaw, err := service.CallMethod("ExecMethod", m.ClassName, m.Method, inputParamsRaw)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to execute method %s: %w", m.Method, err)
|
||
|
}
|
||
|
output := outputRaw.ToIDispatch()
|
||
|
defer outputRaw.Clear()
|
||
|
|
||
|
outputPropertiesRaw, err := oleutil.GetProperty(outputParams, "Properties_")
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to get output properties for method %s: %w", m.Method, err)
|
||
|
}
|
||
|
outputProperties := outputPropertiesRaw.ToIDispatch()
|
||
|
defer outputPropertiesRaw.Clear()
|
||
|
|
||
|
// Convert the results to fields and tags
|
||
|
tags, fields := make(map[string]string), make(map[string]interface{})
|
||
|
|
||
|
// Add a source tag if we use remote queries
|
||
|
if m.host != "" {
|
||
|
tags["source"] = m.host
|
||
|
}
|
||
|
|
||
|
err = oleutil.ForEach(outputProperties, func(p *ole.VARIANT) error {
|
||
|
// Name of the returned result item
|
||
|
nameProperty, err := p.ToIDispatch().GetProperty("Name")
|
||
|
if err != nil {
|
||
|
return errors.New("cannot get output property name")
|
||
|
}
|
||
|
name := nameProperty.ToString()
|
||
|
defer nameProperty.Clear()
|
||
|
|
||
|
// Value of the returned result item
|
||
|
property, err := output.GetProperty(name)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to get value for output property %s: %w", name, err)
|
||
|
}
|
||
|
|
||
|
// Map the fieldname if provided
|
||
|
if n, found := m.FieldMapping[name]; found {
|
||
|
name = n
|
||
|
}
|
||
|
|
||
|
// We might get either scalar values or an array of values...
|
||
|
if value := property.Value(); value != nil {
|
||
|
if m.tagFilter != nil && m.tagFilter.Match(name) {
|
||
|
if s, err := internal.ToString(value); err == nil && s != "" {
|
||
|
tags[name] = s
|
||
|
}
|
||
|
} else {
|
||
|
fields[name] = value
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
if array := property.ToArray(); array != nil {
|
||
|
if m.tagFilter != nil && m.tagFilter.Match(name) {
|
||
|
for i, v := range array.ToValueArray() {
|
||
|
if s, err := internal.ToString(v); err == nil && s != "" {
|
||
|
tags[fmt.Sprintf("%s_%d", name, i)] = s
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
for i, v := range array.ToValueArray() {
|
||
|
fields[fmt.Sprintf("%s_%d", name, i)] = v
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
return fmt.Errorf("cannot handle property %q with value %v", name, property)
|
||
|
})
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("cannot iterate the output properties: %w", err)
|
||
|
}
|
||
|
|
||
|
acc.AddFields(m.ClassName, fields, tags)
|
||
|
|
||
|
return nil
|
||
|
}
|