wincmd/svc.go

380 lines
8.7 KiB
Go
Raw Normal View History

2021-07-15 11:16:07 +08:00
package wincmd
import (
"fmt"
"syscall"
2021-07-15 11:16:07 +08:00
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/eventlog"
"golang.org/x/sys/windows/svc/mgr"
"strings"
2021-07-15 11:16:07 +08:00
"time"
)
type SvcStatus svc.State
const (
Stopped = SvcStatus(svc.Stopped)
StartPending = SvcStatus(svc.StartPending)
StopPending = SvcStatus(svc.StopPending)
Running = SvcStatus(svc.Running)
ContinuePending = SvcStatus(svc.ContinuePending)
PausePending = SvcStatus(svc.PausePending)
Paused = SvcStatus(svc.Paused)
StartManual = windows.SERVICE_DEMAND_START // the service must be started manually
StartAutomatic = windows.SERVICE_AUTO_START // the service will start by itself whenever the computer reboots
StartDisabled = windows.SERVICE_DISABLED // the service cannot be started
// The severity of the error, and action taken,
// if this service fails to start.
ErrorCritical = windows.SERVICE_ERROR_CRITICAL
ErrorIgnore = windows.SERVICE_ERROR_IGNORE
ErrorNormal = windows.SERVICE_ERROR_NORMAL
ErrorSevere = windows.SERVICE_ERROR_SEVERE
)
type WinSvcExecute struct {
Run func()
Stop func()
Interrupt func()
Pause func()
Continue func()
OtherMethod func(svc.Cmd)
Name string
Accepted []svc.Accepted
}
type WinSvcInput struct {
Name string
DisplayName string
ExecPath string
DelayedAutoStart bool
Description string
StartType uint32
Args []string
RecoveryActions []mgr.RecoveryAction
RecoveryResetSec uint32
RecoveryCommand string
RecoveryCommandSet bool
RecoveryOnFail *bool
2021-07-15 11:16:07 +08:00
}
type WinSvc struct {
*mgr.Service
}
func connectServiceManager() (*mgr.Mgr, error) {
elevated, err := IsElevated()
if err != nil {
return nil, wrapPermissionError("query elevation", err)
}
if !elevated {
return nil, wrapPermissionError("admin required for service operations", nil)
2021-07-15 11:16:07 +08:00
}
winmgr, err := mgr.Connect()
if err != nil {
return nil, err
2021-07-15 11:16:07 +08:00
}
return winmgr, nil
}
func serviceExistsWithManager(winmgr *mgr.Mgr, name string) (bool, error) {
if winmgr == nil {
return false, wrapInputError("nil service manager")
}
service, err := winmgr.OpenService(name)
2021-07-15 11:16:07 +08:00
if err != nil {
if isServiceNotExists(err) {
return false, nil
}
2021-07-15 11:16:07 +08:00
return false, err
}
service.Close()
return true, nil
}
func IsServiceExists(name string) (bool, error) {
name = strings.TrimSpace(name)
if name == "" {
return false, wrapInputError("empty service name")
2021-07-15 11:16:07 +08:00
}
winmgr, err := connectServiceManager()
if err != nil {
return false, err
}
defer winmgr.Disconnect()
return serviceExistsWithManager(winmgr, name)
2021-07-15 11:16:07 +08:00
}
func CreateService(mysvc WinSvcInput) (*WinSvc, error) {
if strings.TrimSpace(mysvc.Name) == "" {
return nil, wrapInputError("empty service name")
2021-07-15 11:16:07 +08:00
}
if strings.TrimSpace(mysvc.ExecPath) == "" {
return nil, wrapInputError("empty executable path")
2021-07-15 11:16:07 +08:00
}
winmgr, err := connectServiceManager()
2021-07-15 11:16:07 +08:00
if err != nil {
return nil, err
}
defer winmgr.Disconnect()
if exists, err := serviceExistsWithManager(winmgr, mysvc.Name); err != nil {
return nil, err
} else if exists {
return nil, wrapInputError("service already exists")
}
2021-07-15 11:16:07 +08:00
mycfg := mgr.Config{
DisplayName: mysvc.DisplayName,
StartType: mysvc.StartType,
DelayedAutoStart: mysvc.DelayedAutoStart,
Description: mysvc.Description,
}
gsvc, err := winmgr.CreateService(mysvc.Name, mysvc.ExecPath, mycfg, mysvc.Args...)
if err != nil {
return nil, err
}
created := false
defer func() {
if !created {
_ = gsvc.Close()
}
}()
2021-07-15 11:16:07 +08:00
err = eventlog.InstallAsEventCreate(mysvc.Name, eventlog.Error|eventlog.Warning|eventlog.Info)
if err != nil {
_ = gsvc.Delete()
2021-07-15 11:16:07 +08:00
return nil, fmt.Errorf("winsvc.InstallService: InstallAsEventCreate failed, err = %v", err)
}
if _, err := applyServiceRecoverySettings(gsvc, mysvc); err != nil {
_ = eventlog.Remove(mysvc.Name)
_ = gsvc.Delete()
return nil, fmt.Errorf("winsvc.InstallService: apply recovery config failed, err = %v", err)
}
2021-07-15 11:16:07 +08:00
var result WinSvc
result.Service = gsvc
created = true
2021-07-15 11:16:07 +08:00
return &result, nil
}
func OpenService(name string) (*WinSvc, error) {
name = strings.TrimSpace(name)
if name == "" {
return nil, wrapInputError("empty service name")
2021-07-15 11:16:07 +08:00
}
winmgr, err := connectServiceManager()
2021-07-15 11:16:07 +08:00
if err != nil {
return nil, err
}
defer winmgr.Disconnect()
gsvc, err := winmgr.OpenService(name)
if err != nil {
if isServiceNotExists(err) {
return nil, wrapNotFoundError("service " + name)
}
2021-07-15 11:16:07 +08:00
return nil, err
}
var result WinSvc
result.Service = gsvc
return &result, nil
}
func DeleteService(name string) error {
name = strings.TrimSpace(name)
if name == "" {
return wrapInputError("empty service name")
}
winmgr, err := connectServiceManager()
2021-07-15 11:16:07 +08:00
if err != nil {
return err
}
defer winmgr.Disconnect()
service, err := winmgr.OpenService(name)
2021-07-15 11:16:07 +08:00
if err != nil {
if isServiceNotExists(err) {
return wrapNotFoundError("service " + name)
}
return err
}
if err := service.Delete(); err != nil {
service.Close()
2021-07-15 11:16:07 +08:00
return err
}
service.Close()
2021-07-15 11:16:07 +08:00
err = eventlog.Remove(name)
if err != nil {
return err
}
return waitUntil(defaultServiceWaitTimeout, servicePollInterval, "wait service deletion", func() (bool, error) {
ok, err := serviceExistsWithManager(winmgr, name)
if err != nil {
return false, err
2021-07-15 11:16:07 +08:00
}
return !ok, nil
})
2021-07-15 11:16:07 +08:00
}
func StopService(name string) error {
mysvc, err := OpenService(name)
if err != nil {
return err
}
defer mysvc.Close()
status, err := mysvc.Service.Query()
2021-07-15 11:16:07 +08:00
if err != nil {
return err
}
if status.State == svc.Stopped {
return nil
}
_, err = mysvc.Service.Control(svc.Stop)
if err != nil {
if errno, ok := err.(syscall.Errno); !ok || errno != windows.ERROR_SERVICE_NOT_ACTIVE {
2021-07-15 11:16:07 +08:00
return err
}
}
return waitServiceStatus(mysvc.Service, svc.Stopped, defaultServiceWaitTimeout)
2021-07-15 11:16:07 +08:00
}
func StartService(name string) error {
mysvc, err := OpenService(name)
if err != nil {
return err
}
defer mysvc.Close()
status, err := mysvc.Service.Query()
2021-07-15 11:16:07 +08:00
if err != nil {
return err
}
if status.State == svc.Running {
return nil
}
if err := mysvc.Service.Start(); err != nil {
return err
2021-07-15 11:16:07 +08:00
}
return waitServiceStatus(mysvc.Service, svc.Running, defaultServiceWaitTimeout)
2021-07-15 11:16:07 +08:00
}
func ServiceStatus(name string) (SvcStatus, error) {
mysvc, err := OpenService(name)
if err != nil {
return Stopped, err
}
defer mysvc.Close()
status, err := mysvc.Service.Query()
return SvcStatus(status.State), err
}
func InService() (bool, error) {
return svc.IsWindowsService()
}
func (w *WinSvc) Stop() error {
return StopService(w.Name)
}
func (w *WinSvc) Delete() error {
if err := w.Close(); err != nil {
return err
}
return DeleteService(w.Name)
}
func (w *WinSvc) StartService() error {
status, err := w.Query()
2021-07-15 11:16:07 +08:00
if err != nil {
return err
}
if status.State == svc.Running {
return nil
}
if err := w.Service.Start(); err != nil {
return err
2021-07-15 11:16:07 +08:00
}
return waitServiceStatus(w.Service, svc.Running, defaultServiceWaitTimeout)
2021-07-15 11:16:07 +08:00
}
func InServiceBool() bool {
ok, _ := svc.IsWindowsService()
return ok
}
func (w *WinSvcExecute) Execute(args []string, r <-chan svc.ChangeRequest, s chan<- svc.Status) (svcSpecificEC bool, exitCode uint32) {
var sva svc.Accepted
alreadyStoped := make(chan int)
for _, v := range w.Accepted {
sva = sva | v
}
s <- svc.Status{State: svc.StartPending}
go func() {
s <- svc.Status{State: svc.Running, Accepts: sva}
w.Run()
alreadyStoped <- 1
}()
for {
select {
case <-alreadyStoped:
return
case c := <-r:
switch c.Cmd {
case svc.Interrogate:
s <- c.CurrentStatus
w.Interrupt()
s <- c.CurrentStatus
case svc.Stop, svc.Shutdown:
s <- svc.Status{State: svc.StopPending}
w.Stop()
s <- svc.Status{State: svc.Stopped}
return
case svc.Pause:
s <- svc.Status{State: svc.PausePending, Accepts: sva}
if w.Pause != nil {
w.Pause()
}
s <- svc.Status{State: svc.Paused, Accepts: sva}
case svc.Continue:
s <- svc.Status{State: svc.ContinuePending, Accepts: sva}
if w.Continue != nil {
w.Continue()
}
s <- svc.Status{State: svc.Running, Accepts: sva}
default:
if w.OtherMethod != nil {
w.OtherMethod(c.Cmd)
}
s <- svc.Status{State: svc.Running, Accepts: sva}
}
}
}
}
func NewWinSvcExecute(name string, run, stop func()) *WinSvcExecute {
var res WinSvcExecute
res.Name = name
2021-07-15 11:16:07 +08:00
res.Run = run
res.Stop = stop
res.Interrupt = func() {
time.Sleep(time.Millisecond)
}
res.Accepted = []svc.Accepted{svc.AcceptStop, svc.AcceptShutdown, svc.AcceptPauseAndContinue}
return &res
}
func (w *WinSvcExecute) StartService() error {
return svc.Run(w.Name, w)
}
func (w *WinSvcExecute) InService() (bool, error) {
return svc.IsWindowsService()
}
func (w *WinSvcExecute) InServiceBool() bool {
ok, _ := svc.IsWindowsService()
return ok
}