diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 41bd98f41bdd..c1f8497402c3 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -166,6 +166,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Allow module configurations to have variants {pull}9118[9118] - Add `timeseries.instance` field calculation. {pull}10293[10293] - Added new disk states and raid level to the system/raid metricset. {pull}11613[11613] +- Added `path_name` and `start_name` to service metricset on windows module {issue}8364[8364] {pull}11877[11877] - Add check on object name in the counter path if the instance name is missing {issue}6528[6528] {pull}11878[11878] *Packetbeat* diff --git a/metricbeat/docs/fields.asciidoc b/metricbeat/docs/fields.asciidoc index 2a694ef73982..c27b1c3000df 100644 --- a/metricbeat/docs/fields.asciidoc +++ b/metricbeat/docs/fields.asciidoc @@ -26726,6 +26726,30 @@ type: keyword The startup type of the service. The possible values are `Automatic`, `Boot`, `Disabled`, `Manual`, and `System`. +-- + +*`windows.service.start_name`*:: ++ +-- +type: keyword + +example: NT AUTHORITY\LocalService + +Account name under which a service runs. + + +-- + +*`windows.service.path_name`*:: ++ +-- +type: keyword + +example: C:\WINDOWS\system32\svchost.exe -k LocalService -p + +Fully qualified path to the file that implements the service, including arguments. + + -- *`windows.service.state`*:: diff --git a/metricbeat/module/windows/fields.go b/metricbeat/module/windows/fields.go index 947863e707fa..db3aaeccc64c 100644 --- a/metricbeat/module/windows/fields.go +++ b/metricbeat/module/windows/fields.go @@ -32,5 +32,5 @@ func init() { // AssetWindows returns asset data. // This is the base64 encoded gzipped contents of ../metricbeat/module/windows. func AssetWindows() string { - return "eJysVM1uIjkQvvMUpVxySdBm97QcVsqGyYiRMoomM+JIG3dBl+K2Pa4yhLcfuX9I00AgUXyInLL7+ytT1/CMmxGsyeZuzQMAITE4gotpXbkYAOTIOpAXcnYE/w0AAB5cHg3CwgWYbj/lwgWZaWcXtBzBQhnGAUBAg4pxBEs1AFgQmpxHFcg1WFVilzwt2fh0Objom8oB/l2gGmxLNEdRnXpNwhhWpHFbP0R0lKxeWYORgXZWFFkGKRBYlETuZtGS8bDz/W4O7erb6EqmfKfcKn7GzdqF/hm+qNKnzhXTf75/u9c3t7534w1nad1CtPQ7IkzGlZfKWu1jCBMBYlBQKC7ALarDUumCLF4yfP01GYOyeSrv4TYYlafXPA4aTn8/YnmKmlf6fXZ/vto7R1pO7I3azD4ssXkZX1ZoBe6cMajFhfdrboRUstpOtG162wKLCjJLat9h4JwUE270FUxfUXXBO2aaG4SVMhEZVEDIbqO4Ugnp7GoPNfvfOcmuIBsTq7nBPO0flI3KZFfVQ8ueNixYZqctyye7VVqiMjXy+W7vnBWykezykN1HFbk6qre13x/R2qb4lBLe7p339b4OIv2P+akk8IXSaM4/MY17F17ptyMPpCBOwyIFgyG4AIkWpFCy/cEF9C4I70GuC7T1eyK7BHHAjdsULTGsyRiYY4W9RIuBdH/m7mF2NERrkLnbMvDBrShPbWpL1+xR04J058sT4fojk9o4uzw2EG7++vfvD+TdvorDeStmp0kJ5smYTmYfJ+MT6qMXKnFY9ttx1MPChVLJCPIYVBLbOybro8zaSyUZQ4za2bxPcP6EvuRGJTTNwRzI7mAPB38CAAD//+JTaOY=" + return "eJysVcFu2zgQvfsrBr30Ehvb9rQ+LOCNN7tebNOiSWEsECBiyJE1CEWqnKEd/31BSXZk2akdIzwY9JB8897MoziER1yPYUXO+BUPAITE4hjezZvIuwGAQdaBKiHvxvDHAADgszfRIuQ+wHx7lAsf5F57l9NiDLmyjAOAgBYV4xgWagCQE1rD4xpkCE6V2E2ehqyrtDn4WLWRA/l3gRqwbaIHFNWJN0kYw5I0buOHEr2YrBlZi5GB9k4UOQYpEFiURO7WYpOMR53zu3XYjL6MLmUyO+EN40dcr3zor+GTKqvUuWL+6frfK/1hUvV2/EJZGhOIjn5EhNm01lJLa3SMYCZADAoKxQX4vF4slS7I4XuGv7/PpqCcSeE93Baj1vRcj4OC0+85kueoealfJ/f2Wd4p1AxxZdX6/myKrTP+WqITuPTWohYfXs+5JVLT2nRi06ZfS2BRQe4T21cIOKWKCTdWNUyfUb2h8sz0YBGWykZkUAEhm0TxpRLS2cUeavan95JdQDYlVg8WTZp/Vi4qm13URstu1ixYZidJPrdn17cw+X77z5dvs9v/7/7zWtmbvY/ICTWaaO2jk6Zj0RkMsCpIF6C2BgzR8REplZLibCWX47v57Hr6ZX5zx3XhPn2846UuPMsInxCGj9DVB8NXfjuuorVr+BGVpZzQ1GRBfG2FnCyCFEqAEpkSnXDXI/vtJ6dtNOQWoMIi1geO91ne2NVKS1S2QT7d1ZfeCblIbnHI1l9V5HqpmTa+/hada4M3ya7bua+qZt4YPv1Hc8zx+ETpCTZvWI0rH57Tb582kII4PQqpMBiCD5DSNp3e+horH4T3IFcFuuZypi6LB27VptISw4qshQessRfoMJDuv617mB0O0VnkHZNBFfySTGrTJjTkCjXlpDsnj93BF15k693ipav34bffP55R740rDtdbMXtNStJVC14nsV9n0yPsYyVU4qjst+NFDbkPpZIxmBhUIttbJldFud9sKslaYtTemX6C01/i99yyhLY5aIDcDvZo8DMAAP//0yHt8w==" } diff --git a/metricbeat/module/windows/service/_meta/fields.yml b/metricbeat/module/windows/service/_meta/fields.yml index 0266d6609d49..9bdd8e59bf7f 100644 --- a/metricbeat/module/windows/service/_meta/fields.yml +++ b/metricbeat/module/windows/service/_meta/fields.yml @@ -29,6 +29,19 @@ The startup type of the service. The possible values are `Automatic`, `Boot`, `Disabled`, `Manual`, and `System`. + - name: start_name + type: keyword + example: NT AUTHORITY\LocalService + description: > + Account name under which a service runs. + + - name: path_name + type: keyword + example: C:\WINDOWS\system32\svchost.exe -k LocalService -p + description: > + Fully qualified path to the file that implements the service, + including arguments. + - name: state type: keyword description: > diff --git a/metricbeat/module/windows/service/service_integration_windows_test.go b/metricbeat/module/windows/service/service_integration_windows_test.go index 6bba4bab0a94..c3ac032c03b1 100644 --- a/metricbeat/module/windows/service/service_integration_windows_test.go +++ b/metricbeat/module/windows/service/service_integration_windows_test.go @@ -35,6 +35,8 @@ type Win32Service struct { ProcessId uint32 DisplayName string State string + StartName string + PathName string } func TestData(t *testing.T) { @@ -58,7 +60,8 @@ func TestReadService(t *testing.T) { var wmiSrc []Win32Service - // Get services from WMI. + // Get services from WMI, set NonePtrZero so nil fields are turned to empty strings + wmi.DefaultClient.NonePtrZero = true err = wmi.Query("SELECT * FROM Win32_Service ", &wmiSrc) if err != nil { t.Fatal(err) @@ -87,6 +90,15 @@ func TestReadService(t *testing.T) { assert.Equal(t, w.DisplayName, s["display_name"], "Display name of service %v does not match", w.Name) } + // some services come back without PathName or StartName from WMI, just skip them + if s["path_name"] != nil && w.PathName != "" { + assert.Equal(t, w.PathName, s["path_name"], + "Path name of service %v does not match", w.Name) + } + if s["start_name"] != nil && w.StartName != "" { + assert.Equal(t, w.StartName, s["start_name"], + "Start name of service %v does not match", w.Name) + } // Some services have changed state before the second retrieval. if w.State != s["state"] { changed := s diff --git a/metricbeat/module/windows/service/service_windows.go b/metricbeat/module/windows/service/service_windows.go index 661320594afc..a96b1c386868 100644 --- a/metricbeat/module/windows/service/service_windows.go +++ b/metricbeat/module/windows/service/service_windows.go @@ -134,13 +134,15 @@ var errorNames = map[uint32]string{ } type ServiceStatus struct { - DisplayName string - ServiceName string - CurrentState string - StartType ServiceStartType - PID uint32 // ID of the associated process. - Uptime time.Duration - ExitCode uint32 // Exit code for stopped services. + DisplayName string + ServiceName string + CurrentState string + StartType ServiceStartType + PID uint32 // ID of the associated process. + Uptime time.Duration + ExitCode uint32 // Exit code for stopped services. + ServiceStartName string + BinaryPathName string } type ServiceReader struct { @@ -373,6 +375,21 @@ func getAdditionalServiceInfo(serviceHandle ServiceHandle, service *ServiceStatu } serviceQueryConfig := (*QueryServiceConfig)(unsafe.Pointer(&buffer[0])) service.StartType = ServiceStartType(serviceQueryConfig.DwStartType) + serviceStartNameOffset := uintptr(unsafe.Pointer(serviceQueryConfig.LpServiceStartName)) - (uintptr)(unsafe.Pointer(&buffer[0])) + binaryPathNameOffset := uintptr(unsafe.Pointer(serviceQueryConfig.LpBinaryPathName)) - (uintptr)(unsafe.Pointer(&buffer[0])) + + strBuf := new(bytes.Buffer) + if err := sys.UTF16ToUTF8Bytes(buffer[serviceStartNameOffset:], strBuf); err != nil { + return err + } + service.ServiceStartName = strBuf.String() + + strBuf.Reset() + if err := sys.UTF16ToUTF8Bytes(buffer[binaryPathNameOffset:], strBuf); err != nil { + return err + } + service.BinaryPathName = strBuf.String() + break } @@ -476,6 +493,8 @@ func (reader *ServiceReader) Read() ([]common.MapStr, error) { "name": service.ServiceName, "state": service.CurrentState, "start_type": service.StartType.String(), + "start_name": service.ServiceStartName, + "path_name": service.BinaryPathName, } if service.CurrentState == "Stopped" {