Skip to content

Use WMI to implement System API to reduce PowerShell overhead #375

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
57 changes: 57 additions & 0 deletions pkg/cim/system.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//go:build windows
// +build windows

package cim

import (
"fmt"

"github.com/microsoft/wmi/pkg/base/query"
"github.com/microsoft/wmi/server2019/root/cimv2"
)

// QueryBIOSElement retrieves the BIOS element.
//
// The equivalent WMI query is:
//
// SELECT [selectors] FROM CIM_BIOSElement
//
// Refer to https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/cim-bioselement
// for the WMI class definition.
func QueryBIOSElement(selectorList []string) (*cimv2.CIM_BIOSElement, error) {
biosQuery := query.NewWmiQueryWithSelectList("CIM_BIOSElement", selectorList)
instances, err := QueryInstances("", biosQuery)
if err != nil {
return nil, err
}

bios, err := cimv2.NewCIM_BIOSElementEx1(instances[0])
if err != nil {
return nil, fmt.Errorf("failed to get BIOS element: %w", err)
}

return bios, err
}

// QueryServiceByName retrieves a specific service by its name.
//
// The equivalent WMI query is:
//
// SELECT [selectors] FROM Win32_Service
//
// Refer to https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-service
// for the WMI class definition.
func QueryServiceByName(name string, selectorList []string) (*cimv2.Win32_Service, error) {
serviceQuery := query.NewWmiQueryWithSelectList("Win32_Service", selectorList, "Name", name)
instances, err := QueryInstances("", serviceQuery)
if err != nil {
return nil, err
}

service, err := cimv2.NewWin32_ServiceEx1(instances[0])
if err != nil {
return nil, fmt.Errorf("failed to get service %s: %w", name, err)
}

return service, err
}
86 changes: 59 additions & 27 deletions pkg/os/system/api.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package system

import (
"encoding/json"
"fmt"
"os/exec"
"strings"

"github.com/kubernetes-csi/csi-proxy/pkg/cim"
"github.com/kubernetes-csi/csi-proxy/pkg/server/system/impl"
"github.com/kubernetes-csi/csi-proxy/pkg/utils"
)

@@ -25,52 +24,85 @@ type ServiceInfo struct {
Status uint32 `json:"Status"`
}

var (
startModeMappings = map[string]uint32{
"Boot": impl.START_TYPE_BOOT,
"System": impl.START_TYPE_SYSTEM,
"Auto": impl.START_TYPE_AUTOMATIC,
"Manual": impl.START_TYPE_MANUAL,
"Disabled": impl.START_TYPE_DISABLED,
}

statusMappings = map[string]uint32{
"Unknown": impl.SERVICE_STATUS_UNKNOWN,
"Stopped": impl.SERVICE_STATUS_STOPPED,
"Start Pending": impl.SERVICE_STATUS_START_PENDING,
"Stop Pending": impl.SERVICE_STATUS_STOP_PENDING,
"Running": impl.SERVICE_STATUS_RUNNING,
"Continue Pending": impl.SERVICE_STATUS_CONTINUE_PENDING,
"Pause Pending": impl.SERVICE_STATUS_PAUSE_PENDING,
"Paused": impl.SERVICE_STATUS_PAUSED,
}
)

func serviceStartModeToStartType(startMode string) uint32 {
return startModeMappings[startMode]
}

func serviceState(status string) uint32 {
return statusMappings[status]
}

type APIImplementor struct{}

func New() APIImplementor {
return APIImplementor{}
}

func (APIImplementor) GetBIOSSerialNumber() (string, error) {
// Taken from Kubernetes vSphere cloud provider
// https://github.com/kubernetes/kubernetes/blob/103e926604de6f79161b78af3e792d0ed282bc06/staging/src/k8s.io/legacy-cloud-providers/vsphere/vsphere_util_windows.go#L28
result, err := exec.Command("wmic", "bios", "get", "serialnumber").Output()
bios, err := cim.QueryBIOSElement([]string{"SerialNumber"})
if err != nil {
return "", err
return "", fmt.Errorf("failed to get BIOS element: %w", err)
}
lines := strings.FieldsFunc(string(result), func(r rune) bool {
switch r {
case '\n', '\r':
return true
default:
return false
}
})
if len(lines) != 2 {
return "", fmt.Errorf("received unexpected value retrieving host uuid: %q", string(result))

sn, err := bios.GetPropertySerialNumber()
if err != nil {
return "", fmt.Errorf("failed to get BIOS serial number property: %w", err)
}
return lines[1], nil

return sn, nil
}

func (APIImplementor) GetService(name string) (*ServiceInfo, error) {
script := `Get-Service -Name $env:ServiceName | Select-Object DisplayName, Status, StartType | ` +
`ConvertTo-JSON`
cmdEnv := fmt.Sprintf("ServiceName=%s", name)
out, err := utils.RunPowershellCmd(script, cmdEnv)
service, err := cim.QueryServiceByName(name, []string{"DisplayName", "State", "StartMode"})
if err != nil {
return nil, fmt.Errorf("failed to get service %s: %w", name, err)
}

displayName, err := service.GetPropertyDisplayName()
if err != nil {
return nil, fmt.Errorf("failed to get displayName property of service %s: %w", name, err)
}

state, err := service.GetPropertyState()
if err != nil {
return nil, fmt.Errorf("error querying service name=%s. cmd: %s, output: %s, error: %v", name, script, string(out), err)
return nil, fmt.Errorf("failed to get state property of service %s: %w", name, err)
}

var serviceInfo ServiceInfo
err = json.Unmarshal(out, &serviceInfo)
startMode, err := service.GetPropertyStartMode()
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to get startMode property of service %s: %w", name, err)
}

return &serviceInfo, nil
return &ServiceInfo{
DisplayName: displayName,
StartType: serviceStartModeToStartType(startMode),
Status: serviceState(state),
}, nil
}

func (APIImplementor) StartService(name string) error {
// Note: both StartService and StopService are not implemented by WMI
script := `Start-Service -Name $env:ServiceName`
cmdEnv := fmt.Sprintf("ServiceName=%s", name)
out, err := utils.RunPowershellCmd(script, cmdEnv)
260 changes: 260 additions & 0 deletions vendor/github.com/microsoft/wmi/server2019/root/cimv2/ACE.go
334 changes: 334 additions & 0 deletions vendor/github.com/microsoft/wmi/server2019/root/cimv2/CIM_Action.go
110 changes: 110 additions & 0 deletions vendor/github.com/microsoft/wmi/server2019/root/cimv2/CIM_BasedOn.go
Loading