golang.org/x/sys@v0.20.1-0.20240517151509-673e0f94c16d/windows/svc/mgr/mgr.go (about) 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 //go: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 package mgr 12 13 import ( 14 "syscall" 15 "time" 16 "unicode/utf16" 17 "unsafe" 18 19 "golang.org/x/sys/windows" 20 ) 21 22 // Mgr is used to manage Windows service. 23 type Mgr struct { 24 Handle windows.Handle 25 } 26 27 // Connect establishes a connection to the service control manager. 28 func Connect() (*Mgr, error) { 29 return ConnectRemote("") 30 } 31 32 // ConnectRemote establishes a connection to the 33 // service control manager on computer named host. 34 func 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. 47 func (m *Mgr) Disconnect() error { 48 return windows.CloseServiceHandle(m.Handle) 49 } 50 51 type LockStatus struct { 52 IsLocked bool // Whether the SCM has been locked. 53 Age time.Duration // For how long the SCM has been locked. 54 Owner string // The name of the user who has locked the SCM. 55 } 56 57 // LockStatus returns whether the service control manager is locked by 58 // the system, for how long, and by whom. A locked SCM indicates that 59 // most service actions will block until the system unlocks the SCM. 60 func (m *Mgr) LockStatus() (*LockStatus, error) { 61 bytesNeeded := uint32(unsafe.Sizeof(windows.QUERY_SERVICE_LOCK_STATUS{}) + 1024) 62 for { 63 bytes := make([]byte, bytesNeeded) 64 lockStatus := (*windows.QUERY_SERVICE_LOCK_STATUS)(unsafe.Pointer(&bytes[0])) 65 err := windows.QueryServiceLockStatus(m.Handle, lockStatus, uint32(len(bytes)), &bytesNeeded) 66 if err == windows.ERROR_INSUFFICIENT_BUFFER && bytesNeeded >= uint32(unsafe.Sizeof(windows.QUERY_SERVICE_LOCK_STATUS{})) { 67 continue 68 } 69 if err != nil { 70 return nil, err 71 } 72 status := &LockStatus{ 73 IsLocked: lockStatus.IsLocked != 0, 74 Age: time.Duration(lockStatus.LockDuration) * time.Second, 75 Owner: windows.UTF16PtrToString(lockStatus.LockOwner), 76 } 77 return status, nil 78 } 79 } 80 81 func toPtr(s string) *uint16 { 82 if len(s) == 0 { 83 return nil 84 } 85 return syscall.StringToUTF16Ptr(s) 86 } 87 88 // toStringBlock terminates strings in ss with 0, and then 89 // concatenates them together. It also adds extra 0 at the end. 90 func toStringBlock(ss []string) *uint16 { 91 if len(ss) == 0 { 92 return nil 93 } 94 t := "" 95 for _, s := range ss { 96 if s != "" { 97 t += s + "\x00" 98 } 99 } 100 if t == "" { 101 return nil 102 } 103 t += "\x00" 104 return &utf16.Encode([]rune(t))[0] 105 } 106 107 // CreateService installs new service name on the system. 108 // The service will be executed by running exepath binary. 109 // Use config c to specify service parameters. 110 // Any args will be passed as command-line arguments when 111 // the service is started; these arguments are distinct from 112 // the arguments passed to Service.Start or via the "Start 113 // parameters" field in the service's Properties dialog box. 114 func (m *Mgr) CreateService(name, exepath string, c Config, args ...string) (*Service, error) { 115 if c.StartType == 0 { 116 c.StartType = StartManual 117 } 118 if c.ServiceType == 0 { 119 c.ServiceType = windows.SERVICE_WIN32_OWN_PROCESS 120 } 121 s := syscall.EscapeArg(exepath) 122 for _, v := range args { 123 s += " " + syscall.EscapeArg(v) 124 } 125 h, err := windows.CreateService(m.Handle, toPtr(name), toPtr(c.DisplayName), 126 windows.SERVICE_ALL_ACCESS, c.ServiceType, 127 c.StartType, c.ErrorControl, toPtr(s), toPtr(c.LoadOrderGroup), 128 nil, toStringBlock(c.Dependencies), toPtr(c.ServiceStartName), toPtr(c.Password)) 129 if err != nil { 130 return nil, err 131 } 132 if c.SidType != windows.SERVICE_SID_TYPE_NONE { 133 err = updateSidType(h, c.SidType) 134 if err != nil { 135 windows.DeleteService(h) 136 windows.CloseServiceHandle(h) 137 return nil, err 138 } 139 } 140 if c.Description != "" { 141 err = updateDescription(h, c.Description) 142 if err != nil { 143 windows.DeleteService(h) 144 windows.CloseServiceHandle(h) 145 return nil, err 146 } 147 } 148 if c.DelayedAutoStart { 149 err = updateStartUp(h, c.DelayedAutoStart) 150 if err != nil { 151 windows.DeleteService(h) 152 windows.CloseServiceHandle(h) 153 return nil, err 154 } 155 } 156 return &Service{Name: name, Handle: h}, nil 157 } 158 159 // OpenService retrieves access to service name, so it can 160 // be interrogated and controlled. 161 func (m *Mgr) OpenService(name string) (*Service, error) { 162 h, err := windows.OpenService(m.Handle, syscall.StringToUTF16Ptr(name), windows.SERVICE_ALL_ACCESS) 163 if err != nil { 164 return nil, err 165 } 166 return &Service{Name: name, Handle: h}, nil 167 } 168 169 // ListServices enumerates services in the specified 170 // service control manager database m. 171 // If the caller does not have the SERVICE_QUERY_STATUS 172 // access right to a service, the service is silently 173 // omitted from the list of services returned. 174 func (m *Mgr) ListServices() ([]string, error) { 175 var err error 176 var bytesNeeded, servicesReturned uint32 177 var buf []byte 178 for { 179 var p *byte 180 if len(buf) > 0 { 181 p = &buf[0] 182 } 183 err = windows.EnumServicesStatusEx(m.Handle, windows.SC_ENUM_PROCESS_INFO, 184 windows.SERVICE_WIN32, windows.SERVICE_STATE_ALL, 185 p, uint32(len(buf)), &bytesNeeded, &servicesReturned, nil, nil) 186 if err == nil { 187 break 188 } 189 if err != syscall.ERROR_MORE_DATA { 190 return nil, err 191 } 192 if bytesNeeded <= uint32(len(buf)) { 193 return nil, err 194 } 195 buf = make([]byte, bytesNeeded) 196 } 197 if servicesReturned == 0 { 198 return nil, nil 199 } 200 services := unsafe.Slice((*windows.ENUM_SERVICE_STATUS_PROCESS)(unsafe.Pointer(&buf[0])), int(servicesReturned)) 201 202 var names []string 203 for _, s := range services { 204 name := windows.UTF16PtrToString(s.ServiceName) 205 names = append(names, name) 206 } 207 return names, nil 208 }