1// Copyright 2012 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// +build windows
6
7// Package mgr can be used to manage Windows service programs.
8// It can be used to install and remove them. It can also start,
9// stop and pause them. The package can query / change current
10// service state and config parameters.
11//
12package mgr
13
14import (
15	"syscall"
16	"unicode/utf16"
17	"unsafe"
18
19	"golang.org/x/sys/windows"
20)
21
22// Mgr is used to manage Windows service.
23type Mgr struct {
24	Handle windows.Handle
25}
26
27// Connect establishes a connection to the service control manager.
28func Connect() (*Mgr, error) {
29	return ConnectRemote("")
30}
31
32// ConnectRemote establishes a connection to the
33// service control manager on computer named host.
34func ConnectRemote(host string) (*Mgr, error) {
35	var s *uint16
36	if host != "" {
37		s = syscall.StringToUTF16Ptr(host)
38	}
39	h, err := windows.OpenSCManager(s, nil, windows.SC_MANAGER_ALL_ACCESS)
40	if err != nil {
41		return nil, err
42	}
43	return &Mgr{Handle: h}, nil
44}
45
46// Disconnect closes connection to the service control manager m.
47func (m *Mgr) Disconnect() error {
48	return windows.CloseServiceHandle(m.Handle)
49}
50
51func toPtr(s string) *uint16 {
52	if len(s) == 0 {
53		return nil
54	}
55	return syscall.StringToUTF16Ptr(s)
56}
57
58// toStringBlock terminates strings in ss with 0, and then
59// concatenates them together. It also adds extra 0 at the end.
60func toStringBlock(ss []string) *uint16 {
61	if len(ss) == 0 {
62		return nil
63	}
64	t := ""
65	for _, s := range ss {
66		if s != "" {
67			t += s + "\x00"
68		}
69	}
70	if t == "" {
71		return nil
72	}
73	t += "\x00"
74	return &utf16.Encode([]rune(t))[0]
75}
76
77// CreateService installs new service name on the system.
78// The service will be executed by running exepath binary.
79// Use config c to specify service parameters.
80// Any args will be passed as command-line arguments when
81// the service is started; these arguments are distinct from
82// the arguments passed to Service.Start or via the "Start
83// parameters" field in the service's Properties dialog box.
84func (m *Mgr) CreateService(name, exepath string, c Config, args ...string) (*Service, error) {
85	if c.StartType == 0 {
86		c.StartType = StartManual
87	}
88	if c.ErrorControl == 0 {
89		c.ErrorControl = ErrorNormal
90	}
91	if c.ServiceType == 0 {
92		c.ServiceType = windows.SERVICE_WIN32_OWN_PROCESS
93	}
94	s := syscall.EscapeArg(exepath)
95	for _, v := range args {
96		s += " " + syscall.EscapeArg(v)
97	}
98	h, err := windows.CreateService(m.Handle, toPtr(name), toPtr(c.DisplayName),
99		windows.SERVICE_ALL_ACCESS, c.ServiceType,
100		c.StartType, c.ErrorControl, toPtr(s), toPtr(c.LoadOrderGroup),
101		nil, toStringBlock(c.Dependencies), toPtr(c.ServiceStartName), toPtr(c.Password))
102	if err != nil {
103		return nil, err
104	}
105	if c.Description != "" {
106		err = updateDescription(h, c.Description)
107		if err != nil {
108			return nil, err
109		}
110	}
111	return &Service{Name: name, Handle: h}, nil
112}
113
114// OpenService retrieves access to service name, so it can
115// be interrogated and controlled.
116func (m *Mgr) OpenService(name string) (*Service, error) {
117	h, err := windows.OpenService(m.Handle, syscall.StringToUTF16Ptr(name), windows.SERVICE_ALL_ACCESS)
118	if err != nil {
119		return nil, err
120	}
121	return &Service{Name: name, Handle: h}, nil
122}
123
124// ListServices enumerates services in the specified
125// service control manager database m.
126// If the caller does not have the SERVICE_QUERY_STATUS
127// access right to a service, the service is silently
128// omitted from the list of services returned.
129func (m *Mgr) ListServices() ([]string, error) {
130	var err error
131	var bytesNeeded, servicesReturned uint32
132	var buf []byte
133	for {
134		var p *byte
135		if len(buf) > 0 {
136			p = &buf[0]
137		}
138		err = windows.EnumServicesStatusEx(m.Handle, windows.SC_ENUM_PROCESS_INFO,
139			windows.SERVICE_WIN32, windows.SERVICE_STATE_ALL,
140			p, uint32(len(buf)), &bytesNeeded, &servicesReturned, nil, nil)
141		if err == nil {
142			break
143		}
144		if err != syscall.ERROR_MORE_DATA {
145			return nil, err
146		}
147		if bytesNeeded <= uint32(len(buf)) {
148			return nil, err
149		}
150		buf = make([]byte, bytesNeeded)
151	}
152	if servicesReturned == 0 {
153		return nil, nil
154	}
155	services := (*[1 << 20]windows.ENUM_SERVICE_STATUS_PROCESS)(unsafe.Pointer(&buf[0]))[:servicesReturned]
156	var names []string
157	for _, s := range services {
158		name := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(s.ServiceName))[:])
159		names = append(names, name)
160	}
161	return names, nil
162}
163