windows/svc/mgr: add Mgr.ListServices

Add API to list services installed on the system.

Fixes golang/go#20596

Change-Id: Ifa2f20ef15ccb962bd21d03788ce931dd45f2630
Reviewed-on: https://go-review.googlesource.com/45711
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
diff --git a/windows/service.go b/windows/service.go
index 1c11d39..a500dd7 100644
--- a/windows/service.go
+++ b/windows/service.go
@@ -95,6 +95,8 @@
 	SERVICE_CONFIG_FAILURE_ACTIONS = 2
 
 	NO_ERROR = 0
+
+	SC_ENUM_PROCESS_INFO = 0
 )
 
 type SERVICE_STATUS struct {
@@ -128,6 +130,24 @@
 	Description *uint16
 }
 
+type SERVICE_STATUS_PROCESS struct {
+	ServiceType             uint32
+	CurrentState            uint32
+	ControlsAccepted        uint32
+	Win32ExitCode           uint32
+	ServiceSpecificExitCode uint32
+	CheckPoint              uint32
+	WaitHint                uint32
+	ProcessId               uint32
+	ServiceFlags            uint32
+}
+
+type ENUM_SERVICE_STATUS_PROCESS struct {
+	ServiceName          *uint16
+	DisplayName          *uint16
+	ServiceStatusProcess SERVICE_STATUS_PROCESS
+}
+
 //sys	CloseServiceHandle(handle Handle) (err error) = advapi32.CloseServiceHandle
 //sys	CreateService(mgr Handle, serviceName *uint16, displayName *uint16, access uint32, srvType uint32, startType uint32, errCtl uint32, pathName *uint16, loadOrderGroup *uint16, tagId *uint32, dependencies *uint16, serviceStartName *uint16, password *uint16) (handle Handle, err error) [failretval==0] = advapi32.CreateServiceW
 //sys	OpenService(mgr Handle, serviceName *uint16, access uint32) (handle Handle, err error) [failretval==0] = advapi32.OpenServiceW
@@ -141,3 +161,4 @@
 //sys	QueryServiceConfig(service Handle, serviceConfig *QUERY_SERVICE_CONFIG, bufSize uint32, bytesNeeded *uint32) (err error) = advapi32.QueryServiceConfigW
 //sys	ChangeServiceConfig2(service Handle, infoLevel uint32, info *byte) (err error) = advapi32.ChangeServiceConfig2W
 //sys	QueryServiceConfig2(service Handle, infoLevel uint32, buff *byte, buffSize uint32, bytesNeeded *uint32) (err error) = advapi32.QueryServiceConfig2W
+//sys	EnumServicesStatusEx(mgr Handle, infoLevel uint32, serviceType uint32, serviceState uint32, services *byte, bufSize uint32, bytesNeeded *uint32, servicesReturned *uint32, resumeHandle *uint32, groupName *uint16) (err error) = advapi32.EnumServicesStatusExW
diff --git a/windows/svc/mgr/mgr.go b/windows/svc/mgr/mgr.go
index e20a1fa..76965b5 100644
--- a/windows/svc/mgr/mgr.go
+++ b/windows/svc/mgr/mgr.go
@@ -14,6 +14,7 @@
 import (
 	"syscall"
 	"unicode/utf16"
+	"unsafe"
 
 	"golang.org/x/sys/windows"
 )
@@ -119,3 +120,43 @@
 	}
 	return &Service{Name: name, Handle: h}, nil
 }
+
+// ListServices enumerates services in the specified
+// service control manager database m.
+// If the caller does not have the SERVICE_QUERY_STATUS
+// access right to a service, the service is silently
+// omitted from the list of services returned.
+func (m *Mgr) ListServices() ([]string, error) {
+	var err error
+	var bytesNeeded, servicesReturned uint32
+	var buf []byte
+	for {
+		var p *byte
+		if len(buf) > 0 {
+			p = &buf[0]
+		}
+		err = windows.EnumServicesStatusEx(m.Handle, windows.SC_ENUM_PROCESS_INFO,
+			windows.SERVICE_WIN32, windows.SERVICE_STATE_ALL,
+			p, uint32(len(buf)), &bytesNeeded, &servicesReturned, nil, nil)
+		if err == nil {
+			break
+		}
+		if err != syscall.ERROR_MORE_DATA {
+			return nil, err
+		}
+		if bytesNeeded <= uint32(len(buf)) {
+			return nil, err
+		}
+		buf = make([]byte, bytesNeeded)
+	}
+	if servicesReturned == 0 {
+		return nil, nil
+	}
+	services := (*[1 << 20]windows.ENUM_SERVICE_STATUS_PROCESS)(unsafe.Pointer(&buf[0]))[:servicesReturned]
+	var names []string
+	for _, s := range services {
+		name := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(s.ServiceName))[:])
+		names = append(names, name)
+	}
+	return names, nil
+}
diff --git a/windows/svc/mgr/mgr_test.go b/windows/svc/mgr/mgr_test.go
index 78be970..e67407c 100644
--- a/windows/svc/mgr/mgr_test.go
+++ b/windows/svc/mgr/mgr_test.go
@@ -150,5 +150,20 @@
 
 	testConfig(t, s, c)
 
+	svcnames, err := m.ListServices()
+	if err != nil {
+		t.Fatalf("ListServices failed: %v", err)
+	}
+	var myserviceIsInstalled bool
+	for _, sn := range svcnames {
+		if sn == name {
+			myserviceIsInstalled = true
+			break
+		}
+	}
+	if !myserviceIsInstalled {
+		t.Errorf("ListServices failed to find %q service", name)
+	}
+
 	remove(t, s)
 }
diff --git a/windows/svc/mgr/service.go b/windows/svc/mgr/service.go
index ac9fba5..fdc46af 100644
--- a/windows/svc/mgr/service.go
+++ b/windows/svc/mgr/service.go
@@ -15,8 +15,6 @@
 
 // TODO(brainman): Use EnumDependentServices to enumerate dependent services.
 
-// TODO(brainman): Use EnumServicesStatus to enumerate services in the specified service control manager database.
-
 // Service is used to access Windows service.
 type Service struct {
 	Name   string
diff --git a/windows/zsyscall_windows.go b/windows/zsyscall_windows.go
index f7bc8d6..b9ee827 100644
--- a/windows/zsyscall_windows.go
+++ b/windows/zsyscall_windows.go
@@ -64,6 +64,7 @@
 	procQueryServiceConfigW                = modadvapi32.NewProc("QueryServiceConfigW")
 	procChangeServiceConfig2W              = modadvapi32.NewProc("ChangeServiceConfig2W")
 	procQueryServiceConfig2W               = modadvapi32.NewProc("QueryServiceConfig2W")
+	procEnumServicesStatusExW              = modadvapi32.NewProc("EnumServicesStatusExW")
 	procGetLastError                       = modkernel32.NewProc("GetLastError")
 	procLoadLibraryW                       = modkernel32.NewProc("LoadLibraryW")
 	procLoadLibraryExW                     = modkernel32.NewProc("LoadLibraryExW")
@@ -430,6 +431,18 @@
 	return
 }
 
+func EnumServicesStatusEx(mgr Handle, infoLevel uint32, serviceType uint32, serviceState uint32, services *byte, bufSize uint32, bytesNeeded *uint32, servicesReturned *uint32, resumeHandle *uint32, groupName *uint16) (err error) {
+	r1, _, e1 := syscall.Syscall12(procEnumServicesStatusExW.Addr(), 10, uintptr(mgr), uintptr(infoLevel), uintptr(serviceType), uintptr(serviceState), uintptr(unsafe.Pointer(services)), uintptr(bufSize), uintptr(unsafe.Pointer(bytesNeeded)), uintptr(unsafe.Pointer(servicesReturned)), uintptr(unsafe.Pointer(resumeHandle)), uintptr(unsafe.Pointer(groupName)), 0, 0)
+	if r1 == 0 {
+		if e1 != 0 {
+			err = errnoErr(e1)
+		} else {
+			err = syscall.EINVAL
+		}
+	}
+	return
+}
+
 func GetLastError() (lasterr error) {
 	r0, _, _ := syscall.Syscall(procGetLastError.Addr(), 0, 0, 0, 0)
 	if r0 != 0 {