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  }