192 lines
5 KiB
Go
192 lines
5 KiB
Go
|
//go:build windows
|
||
|
|
||
|
package win_wmi
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"runtime"
|
||
|
"strings"
|
||
|
|
||
|
"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 query struct {
|
||
|
Namespace string `toml:"namespace"`
|
||
|
ClassName string `toml:"class_name"`
|
||
|
Properties []string `toml:"properties"`
|
||
|
Filter string `toml:"filter"`
|
||
|
TagPropertiesInclude []string `toml:"tag_properties"`
|
||
|
|
||
|
host string
|
||
|
query string
|
||
|
connectionParams []interface{}
|
||
|
tagFilter filter.Filter
|
||
|
}
|
||
|
|
||
|
func (q *query) prepare(host string, username, password config.Secret) error {
|
||
|
// Compile the filter
|
||
|
f, err := filter.Compile(q.TagPropertiesInclude)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("compiling tag-filter failed: %w", err)
|
||
|
}
|
||
|
q.tagFilter = f
|
||
|
|
||
|
// Setup the connection parameters
|
||
|
q.host = host
|
||
|
if q.host != "" {
|
||
|
q.connectionParams = append(q.connectionParams, q.host)
|
||
|
} else {
|
||
|
q.connectionParams = append(q.connectionParams, nil)
|
||
|
}
|
||
|
q.connectionParams = append(q.connectionParams, q.Namespace)
|
||
|
if !username.Empty() {
|
||
|
u, err := username.Get()
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("getting username secret failed: %w", err)
|
||
|
}
|
||
|
q.connectionParams = append(q.connectionParams, u.String())
|
||
|
username.Destroy()
|
||
|
}
|
||
|
if !password.Empty() {
|
||
|
p, err := password.Get()
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("getting password secret failed: %w", err)
|
||
|
}
|
||
|
q.connectionParams = append(q.connectionParams, p.String())
|
||
|
password.Destroy()
|
||
|
}
|
||
|
|
||
|
// Construct the overall query from the given parts
|
||
|
wql := fmt.Sprintf("SELECT %s FROM %s", strings.Join(q.Properties, ", "), q.ClassName)
|
||
|
if len(q.Filter) > 0 {
|
||
|
wql += " WHERE " + q.Filter
|
||
|
}
|
||
|
q.query = wql
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (q *query) 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 COM
|
||
|
if err := ole.CoInitializeEx(0, ole.COINIT_MULTITHREADED); err != nil {
|
||
|
var oleCode *ole.OleError
|
||
|
if errors.As(err, &oleCode) && oleCode.Code() != ole.S_OK && oleCode.Code() != sFalse {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
defer ole.CoUninitialize()
|
||
|
|
||
|
unknown, err := oleutil.CreateObject("WbemScripting.SWbemLocator")
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if unknown == nil {
|
||
|
return errors.New("failed to create WbemScripting.SWbemLocator, maybe WMI is broken")
|
||
|
}
|
||
|
defer unknown.Release()
|
||
|
|
||
|
wmi, err := unknown.QueryInterface(ole.IID_IDispatch)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to QueryInterface: %w", err)
|
||
|
}
|
||
|
defer wmi.Release()
|
||
|
|
||
|
// service is a SWbemServices
|
||
|
serviceRaw, err := oleutil.CallMethod(wmi, "ConnectServer", q.connectionParams...)
|
||
|
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed calling method ConnectServer: %w", err)
|
||
|
}
|
||
|
service := serviceRaw.ToIDispatch()
|
||
|
defer serviceRaw.Clear()
|
||
|
|
||
|
// result is a SWBemObjectSet
|
||
|
resultRaw, err := oleutil.CallMethod(service, "ExecQuery", q.query)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed calling method ExecQuery for query %s: %w", q.query, err)
|
||
|
}
|
||
|
result := resultRaw.ToIDispatch()
|
||
|
defer resultRaw.Clear()
|
||
|
|
||
|
countRaw, err := oleutil.GetProperty(result, "Count")
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed getting Count: %w", err)
|
||
|
}
|
||
|
count := countRaw.Val
|
||
|
defer countRaw.Clear()
|
||
|
|
||
|
for i := int64(0); i < count; i++ {
|
||
|
itemRaw, err := oleutil.CallMethod(result, "ItemIndex", i)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed calling method ItemIndex: %w", err)
|
||
|
}
|
||
|
|
||
|
if err := q.extractProperties(acc, itemRaw); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (q *query) extractProperties(acc telegraf.Accumulator, itemRaw *ole.VARIANT) error {
|
||
|
tags, fields := make(map[string]string), make(map[string]interface{})
|
||
|
|
||
|
if q.host != "" {
|
||
|
tags["source"] = q.host
|
||
|
}
|
||
|
|
||
|
item := itemRaw.ToIDispatch()
|
||
|
defer item.Release()
|
||
|
|
||
|
for _, name := range q.Properties {
|
||
|
propertyRaw, err := oleutil.GetProperty(item, name)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("getting property %q failed: %w", name, err)
|
||
|
}
|
||
|
value := propertyRaw.Value()
|
||
|
propertyRaw.Clear()
|
||
|
|
||
|
if q.tagFilter != nil && q.tagFilter.Match(name) {
|
||
|
s, err := internal.ToString(value)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("converting property %q failed: %w", s, err)
|
||
|
}
|
||
|
tags[name] = s
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
switch v := value.(type) {
|
||
|
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
|
||
|
fields[name] = v
|
||
|
case string:
|
||
|
fields[name] = v
|
||
|
case bool:
|
||
|
fields[name] = v
|
||
|
case []byte:
|
||
|
fields[name] = string(v)
|
||
|
case fmt.Stringer:
|
||
|
fields[name] = v.String()
|
||
|
case nil:
|
||
|
fields[name] = nil
|
||
|
default:
|
||
|
return fmt.Errorf("property %q of type \"%T\" unsupported", name, v)
|
||
|
}
|
||
|
}
|
||
|
acc.AddFields(q.ClassName, fields, tags)
|
||
|
return nil
|
||
|
}
|