1
0
Fork 0

Adding upstream version 1.34.4.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-24 07:26:29 +02:00
parent e393c3af3f
commit 4978089aab
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
4963 changed files with 677545 additions and 0 deletions

View file

@ -0,0 +1,383 @@
# Windows Management Instrumentation Input Plugin
This document presents the input plugin to read WMI classes on Windows
operating systems. With the win_wmi plugin, it is possible to
capture and filter virtually any configuration or metric value exposed
through the Windows Management Instrumentation ([WMI][WMIdoc])
service. At minimum, the telegraf service user must have permission
to [read][ACL] the WMI namespace that is being queried.
[ACL]: https://learn.microsoft.com/en-us/windows/win32/wmisdk/access-to-wmi-namespaces
[WMIdoc]: https://learn.microsoft.com/en-us/windows/win32/wmisdk/wmi-start-page
## Global configuration options <!-- @/docs/includes/plugin_config.md -->
In addition to the plugin-specific configuration settings, plugins support
additional global and plugin configuration settings. These settings are used to
modify metrics, tags, and field or create aliases and configure ordering, etc.
See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins
## Secret-store support
This plugin supports secrets from secret-stores for the `username` and
`password` option.
See the [secret-store documentation][SECRETSTORE] for more details on how
to use them.
[SECRETSTORE]: ../../../docs/CONFIGURATION.md#secret-store-secrets
## Configuration
```toml @sample.conf
# Input plugin to query Windows Management Instrumentation
# This plugin ONLY supports Windows
[[inputs.win_wmi]]
## Hostname or IP for remote connections, by default the local machine is queried
# host = ""
## Credentials for the connection, by default no credentials are used
# username = ""
# password = ""
## WMI query to execute, multiple methods are possible
[[inputs.win_wmi.query]]
## Namespace, class and a list of properties to use in the WMI query
namespace = "root\\cimv2"
class_name = "Win32_Volume"
properties = ["Name", "Capacity", "FreeSpace"]
## Optional WHERE clause for the WQL query
# filter = 'NOT Name LIKE "\\\\?\\%"'
## Returned properties to use as tags instead of fields
# tag_properties = ["Name"]
# ## WMI method to invoke, multiple methods are possible
# [[inputs.win_wmi.method]]
# ## WMI namespace, class and method to use
# namespace = 'root\default'
# class_name = "StdRegProv"
# method = "GetStringValue"
# ## Returned WMI method values to use as tags instead of fields
# # tag_properties = ["ReturnValue"]
# ## Named arguments for the method call
# [inputs.win_wmi.method.arguments]
# hDefKey = '2147483650'
# sSubKeyName = 'Software\Microsoft\windows NT\CurrentVersion'
# sValueName = 'ProductName'
# ## Mapping of the name of the returned property to a field-name
# [inputs.win_wmi.method.fields]
# sValue = "product_name"
```
### Remote execution
This plugin allows to execute queries and methods on a remote host. To do so,
you need to provide the `host` as a hostname or IP-address as well as the
credentials to execute the query or method as.
Please note, the remote machine must be configured to allow remote execution and
the user needs to have sufficient permission to execute the query or method!
Check the [Microsoft guide][remotedoc] for how to do this and test the
connection with the `Get-WmiObject` method first.
[remotedoc]: https://learn.microsoft.com/en-us/windows/win32/wmisdk/connecting-to-wmi-on-a-remote-computer#configuring-a-computer-for-a-remote-connection
### Query settings
To issue a query you need to provide the `namespace` (e.g. `root\cimv2`) and the
`class_name` (e.g. `Win32_Processor`) for the WMI query. Furthermore, you need
to define which `properties` to output. An asterix (`*`) will output all values
provided by the query.
The `filter` setting specifies a WHERE clause passed to the query in the
WMI Query Language (WQL). See [WHERE Clause][WHERE] for more information.
The `tag_properties` allows to provide a list of returned properties that should
be provided as tags instead of fields in the metric.
[WHERE]: https://learn.microsoft.com/en-us/windows/win32/wmisdk/where-clause?source=recommendations
As an example
```toml
[[inputs.win_wmi]]
[[inputs.win_wmi.query]]
namespace = "root\\cimv2"
class_name = "Win32_Processor"
properties = ["Name"]
```
corresponds to executing
```powershell
Get-WmiObject -Namespace "root\cimv2" -Class "Win32_Processor" -Property "Name"
```
### Method settings
To invoke a method you need to provide the `namespace` (e.g. `root\default`),
the `class_name` (e.g. `StdRegProv`) and the `method` name
(e.g. `GetStringValue`)for the method to invoke. Furthermore, you may need to
provide `arguments` as key-value pair(s) to the method. The number and type of
arguments depends on the method specified above.
Check the [WMI reference][wmireferenc] for available methods and their
arguments.
The `tag_properties` allows to provide a list of returned properties that should
be provided as tags instead of fields in the metric.
[wmireferenc]: https://learn.microsoft.com/en-us/windows/win32/wmisdk/wmi-reference
As an example
```toml
[[inputs.win_wmi]]
[[inputs.win_wmi.method]]
namespace = 'root\default'
class_name = "StdRegProv"
method = "GetStringValue"
[inputs.win_wmi.method.arguments]
hDefKey = '2147483650'
sSubKeyName = 'Software\Microsoft\windows NT\CurrentVersion'
sValueName = 'ProductName'
```
corresponds to executing
```powershell
Invoke-WmiMethod -Namespace "root\default" -Class "StdRegProv" -Name "GetStringValue" @(2147483650,"Software\Microsoft\windows NT\CurrentVersion", "ProductName")
```
## Metrics
By default, a WMI class property's value is used as a metric field. If a class
property's value is specified in `tag_properties`, then the value is
instead included with the metric as a tag.
## Troubleshooting
### Errors
If you are getting an error about an invalid WMI namespace, class, or property,
use the `Get-WmiObject` or `Get-CimInstance` PowerShell commands in order to
verify their validity. For example:
```powershell
Get-WmiObject -Namespace root\cimv2 -Class Win32_Volume -Property Capacity, FreeSpace, Name -Filter 'NOT Name LIKE "\\\\?\\%"'
```
```powershell
Get-CimInstance -Namespace root\cimv2 -ClassName Win32_Volume -Property Capacity, FreeSpace, Name -Filter 'NOT Name LIKE "\\\\?\\%"'
```
### Data types
Some WMI classes will return the incorrect data type for a field. In those
cases, it is necessary to use a processor to convert the data type. For
example, the Capacity and FreeSpace properties of the Win32_Volume class must
be converted to integers:
```toml
[[processors.converter]]
namepass = ["win_wmi_Win32_Volume"]
[processors.converter.fields]
integer = ["Capacity", "FreeSpace"]
```
## Example Output
### Physical Memory
This query provides metrics for the speed and capacity of each physical memory
device, along with tags describing the manufacturer, part number, and device
locator of each device.
```toml
[[inputs.win_wmi]]
name_prefix = "win_wmi_"
[[inputs.win_wmi.query]]
namespace = "root\\cimv2"
class_name = "Win32_PhysicalMemory"
properties = [
"Name",
"Capacity",
"DeviceLocator",
"Manufacturer",
"PartNumber",
"Speed",
]
tag_properties = ["Name","DeviceLocator","Manufacturer","PartNumber"]
```
Example Output:
```text
win_wmi_Win32_PhysicalMemory,DeviceLocator=DIMM1,Manufacturer=80AD000080AD,Name=Physical\ Memory,PartNumber=HMA82GU6DJR8N-XN\ \ \ \ ,host=foo Capacity=17179869184i,Speed=3200i 1654269272000000000
```
### Processor
This query provides metrics for the number of cores in each physical processor.
Since the Name property of the WMI class is included by default, the metrics
will also contain a tag value describing the model of each CPU.
```toml
[[inputs.win_wmi]]
name_prefix = "win_wmi_"
[[inputs.win_wmi.query]]
namespace = "root\\cimv2"
class_name = "Win32_Processor"
properties = ["Name","NumberOfCores"]
tag_properties = ["Name"]
```
Example Output:
```text
win_wmi_Win32_Processor,Name=Intel(R)\ Core(TM)\ i9-10900\ CPU\ @\ 2.80GHz,host=foo NumberOfCores=10i 1654269272000000000
```
### Computer System
This query provides metrics for the number of socketted processors, number of
logical cores on each processor, and the total physical memory in the computer.
The metrics include tag values for the domain, manufacturer, and model of the
computer.
```toml
[[inputs.win_wmi]]
name_prefix = "win_wmi_"
[[inputs.win_wmi.query]]
namespace = "root\\cimv2"
class_name = "Win32_ComputerSystem"
properties = [
"Name",
"Domain",
"Manufacturer",
"Model",
"NumberOfLogicalProcessors",
"NumberOfProcessors",
"TotalPhysicalMemory"
]
tag_properties = ["Name","Domain","Manufacturer","Model"]
```
Example Output:
```text
win_wmi_Win32_ComputerSystem,Domain=company.com,Manufacturer=Lenovo,Model=X1\ Carbon,Name=FOO,host=foo NumberOfLogicalProcessors=20i,NumberOfProcessors=1i,TotalPhysicalMemory=34083926016i 1654269272000000000
```
### Operating System
This query provides metrics for the paging file's free space, the operating
system's free virtual memory, the operating system SKU installed on the
computer, and the Windows product type. The OS architecture is included as a
tagged value to describe whether the installation is 32-bit or 64-bit.
```toml
[[inputs.win_wmi]]
name_prefix = "win_wmi_"
[[inputs.win_wmi.query]]
class_name = "Win32_OperatingSystem"
namespace = "root\\cimv2"
properties = [
"Name",
"Caption",
"FreeSpaceInPagingFiles",
"FreeVirtualMemory",
"OperatingSystemSKU",
"OSArchitecture",
"ProductType"
]
tag_properties = ["Name","Caption","OSArchitecture"]
```
Example Output:
```text
win_wmi_Win32_OperatingSystem,Caption=Microsoft\ Windows\ 10\ Enterprise,InstallationType=Client,Name=Microsoft\ Windows\ 10\ Enterprise|C:\WINDOWS|\Device\Harddisk0\Partition3,OSArchitecture=64-bit,host=foo FreeSpaceInPagingFiles=5203244i,FreeVirtualMemory=16194496i,OperatingSystemSKU=4i,ProductType=1i 1654269272000000000
```
### Failover Clusters
This query provides a boolean metric describing whether Dynamic Quorum is
enabled for the cluster. The tag values for the metric also include the name of
the Windows Server Failover Cluster and the type of Quorum in use.
```toml
[[inputs.win_wmi]]
name_prefix = "win_wmi_"
[[inputs.win_wmi.query]]
namespace = "root\\mscluster"
class_name = "MSCluster_Cluster"
properties = [
"Name",
"QuorumType",
"DynamicQuorumEnabled"
]
tag_properties = ["Name","QuorumType"]
```
Example Output:
```text
win_wmi_MSCluster_Cluster,Name=testcluster1,QuorumType=Node\ and\ File\ Share\ Majority,host=testnode1 DynamicQuorumEnabled=1i 1671553260000000000
```
### Bitlocker
This query provides a list of volumes which are eligible for bitlocker
encryption and their compliance status. Because the MBAM_Volume class does not
include a Name property, the ExcludeNameKey configuration is included. The
VolumeName property is included in the metric as a tagged value.
```toml
[[inputs.win_wmi]]
name_prefix = "win_wmi_"
[[inputs.win_wmi.query]]
namespace = "root\\Microsoft\\MBAM"
class_name = "MBAM_Volume"
properties = [
"Compliant",
"VolumeName"
]
tag_properties = ["VolumeName"]
```
Example Output:
```text
win_wmi_MBAM_Volume,VolumeName=C:,host=foo Compliant=1i 1654269272000000000
```
### SQL Server
This query provides metrics which contain tags describing the version and SKU
of SQL Server. These properties are useful for creating a dashboard of your SQL
Server inventory, which includes the patch level and edition of SQL Server that
is installed.
```toml
[[inputs.win_wmi]]
name_prefix = "win_wmi_"
[[inputs.win_wmi.query]]
namespace = "Root\\Microsoft\\SqlServer\\ComputerManagement15"
class_name = "SqlServiceAdvancedProperty"
properties = [
"PropertyName",
"ServiceName",
"PropertyStrValue",
"SqlServiceType"
]
filter = "ServiceName LIKE 'MSSQLSERVER' AND SqlServiceType = 1 AND (PropertyName LIKE 'FILEVERSION' OR PropertyName LIKE 'SKUNAME')"
tag_properties = ["PropertyName","ServiceName","PropertyStrValue"]
```
Example Output:
```text
win_wmi_SqlServiceAdvancedProperty,PropertyName=FILEVERSION,PropertyStrValue=2019.150.4178.1,ServiceName=MSSQLSERVER,host=foo,sqlinstance=foo SqlServiceType=1i 1654269272000000000
win_wmi_SqlServiceAdvancedProperty,PropertyName=SKUNAME,PropertyStrValue=Developer\ Edition\ (64-bit),ServiceName=MSSQLSERVER,host=foo,sqlinstance=foo SqlServiceType=1i 1654269272000000000
```

View file

@ -0,0 +1,228 @@
//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
}

View file

@ -0,0 +1,191 @@
//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
}

View file

@ -0,0 +1,36 @@
# Input plugin to query Windows Management Instrumentation
# This plugin ONLY supports Windows
[[inputs.win_wmi]]
## Hostname or IP for remote connections, by default the local machine is queried
# host = ""
## Credentials for the connection, by default no credentials are used
# username = ""
# password = ""
## WMI query to execute, multiple methods are possible
[[inputs.win_wmi.query]]
## Namespace, class and a list of properties to use in the WMI query
namespace = "root\\cimv2"
class_name = "Win32_Volume"
properties = ["Name", "Capacity", "FreeSpace"]
## Optional WHERE clause for the WQL query
# filter = 'NOT Name LIKE "\\\\?\\%"'
## Returned properties to use as tags instead of fields
# tag_properties = ["Name"]
# ## WMI method to invoke, multiple methods are possible
# [[inputs.win_wmi.method]]
# ## WMI namespace, class and method to use
# namespace = 'root\default'
# class_name = "StdRegProv"
# method = "GetStringValue"
# ## Returned WMI method values to use as tags instead of fields
# # tag_properties = ["ReturnValue"]
# ## Named arguments for the method call
# [inputs.win_wmi.method.arguments]
# hDefKey = '2147483650'
# sSubKeyName = 'Software\Microsoft\windows NT\CurrentVersion'
# sValueName = 'ProductName'
# ## Mapping of the name of the returned property to a field-name
# [inputs.win_wmi.method.fields]
# sValue = "product_name"

View file

@ -0,0 +1,78 @@
//go:generate ../../../tools/readme_config_includer/generator
//go:build windows
package win_wmi
import (
_ "embed"
"fmt"
"sync"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/plugins/inputs"
)
//go:embed sample.conf
var sampleConfig string
// S_FALSE is returned by CoInitializeEx if it was already called on this thread.
const sFalse = 0x00000001
type Wmi struct {
Host string `toml:"host"`
Username config.Secret `toml:"username"`
Password config.Secret `toml:"password"`
Queries []query `toml:"query"`
Methods []method `toml:"method"`
Log telegraf.Logger `toml:"-"`
}
func (*Wmi) SampleConfig() string {
return sampleConfig
}
func (w *Wmi) Init() error {
for i := range w.Queries {
q := &w.Queries[i]
if err := q.prepare(w.Host, w.Username, w.Password); err != nil {
return fmt.Errorf("preparing query %q failed: %w", q.ClassName, err)
}
}
for i := range w.Methods {
m := &w.Methods[i]
if err := m.prepare(w.Host, w.Username, w.Password); err != nil {
return fmt.Errorf("preparing method %q failed: %w", m.Method, err)
}
}
return nil
}
func (w *Wmi) Gather(acc telegraf.Accumulator) error {
var wg sync.WaitGroup
for _, q := range w.Queries {
wg.Add(1)
go func(q query) {
defer wg.Done()
acc.AddError(q.execute(acc))
}(q)
}
for _, m := range w.Methods {
wg.Add(1)
go func(m method) {
defer wg.Done()
acc.AddError(m.execute(acc))
}(m)
}
wg.Wait()
return nil
}
func init() {
inputs.Add("win_wmi", func() telegraf.Input { return &Wmi{} })
}

View file

@ -0,0 +1,31 @@
//go:generate ../../../tools/readme_config_includer/generator
//go:build !windows
package win_wmi
import (
_ "embed"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/inputs"
)
//go:embed sample.conf
var sampleConfig string
type Wmi struct {
Log telegraf.Logger `toml:"-"`
}
func (*Wmi) SampleConfig() string { return sampleConfig }
func (w *Wmi) Init() error {
w.Log.Warn("Current platform is not supported")
return nil
}
func (*Wmi) Gather(telegraf.Accumulator) error { return nil }
func init() {
inputs.Add("win_wmi", func() telegraf.Input { return &Wmi{} })
}

View file

@ -0,0 +1,119 @@
//go:build windows
package win_wmi
import (
"fmt"
"os"
"regexp"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/metric"
"github.com/influxdata/telegraf/testutil"
)
var sysDrive = os.Getenv("SystemDrive") + `\` // C:\
func TestBuildWqlStatements(t *testing.T) {
plugin := &Wmi{
Queries: []query{
{
Namespace: "ROOT\\cimv2",
ClassName: "Win32_Volume",
Properties: []string{"Name", "FreeSpace", "Purpose"},
//nolint:gocritic // sprintfQuotedString - "%s" used by purpose, string escaping is done by special function
Filter: fmt.Sprintf(`NOT Name LIKE "\\\\?\\%%" AND Name LIKE "%s"`, regexp.QuoteMeta(sysDrive)),
TagPropertiesInclude: []string{"Name"},
},
},
Log: testutil.Logger{},
}
require.NoError(t, plugin.Init())
require.NotEmpty(t, plugin.Queries)
//nolint:gocritic // sprintfQuotedString - "%s" used by purpose, string escaping is done by special function
expected := fmt.Sprintf(
`SELECT Name, FreeSpace, Purpose FROM Win32_Volume WHERE NOT Name LIKE "\\\\?\\%%" AND Name LIKE "%s"`,
regexp.QuoteMeta(sysDrive),
)
require.Equal(t, expected, plugin.Queries[0].query)
}
func TestInit(t *testing.T) {
plugin := &Wmi{}
require.NoError(t, plugin.Init())
}
func TestQueryIntegration(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
plugin := &Wmi{
Queries: []query{
{
Namespace: "ROOT\\cimv2",
ClassName: "Win32_Volume",
Properties: []string{"Name", "FreeSpace", "Purpose"},
//nolint:gocritic // sprintfQuotedString - "%s" used by purpose, string escaping is done by special function
Filter: fmt.Sprintf(`NOT Name LIKE "\\\\?\\%%" AND Name LIKE "%s"`, regexp.QuoteMeta(sysDrive)),
TagPropertiesInclude: []string{"Name"},
},
},
Log: testutil.Logger{},
}
require.NoError(t, plugin.Init())
var acc testutil.Accumulator
require.NoError(t, plugin.Gather(&acc))
require.Empty(t, acc.Errors)
// Only one metric was returned (because we filtered for SystemDrive)
require.Len(t, acc.Metrics, 1)
// Name property collected and is the SystemDrive
require.Equal(t, sysDrive, acc.Metrics[0].Tags["Name"])
// FreeSpace property was collected as a field
require.NotEmpty(t, acc.Metrics[0].Fields["FreeSpace"])
}
func TestMethodIntegration(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
plugin := &Wmi{
Methods: []method{
{
Namespace: "ROOT\\default",
ClassName: "StdRegProv",
Method: "GetStringValue",
Arguments: map[string]interface{}{
"hDefKey": `2147483650`,
"sSubKeyName": `software\microsoft\windows nt\currentversion`,
"sValueName": `ProductName`,
},
TagPropertiesInclude: []string{"ReturnValue"},
},
},
Log: testutil.Logger{},
}
require.NoError(t, plugin.Init())
expected := []telegraf.Metric{
metric.New(
"StdRegProv",
map[string]string{"ReturnValue": "0"},
map[string]interface{}{"sValue": "Windows ..."},
time.Unix(0, 0),
),
}
var acc testutil.Accumulator
require.NoError(t, plugin.Gather(&acc))
require.Empty(t, acc.Errors)
actual := acc.GetTelegrafMetrics()
testutil.RequireMetricsStructureEqual(t, expected, actual, testutil.IgnoreTime())
}