package wincmd import ( "fmt" "syscall" "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" "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 } 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) } winmgr, err := mgr.Connect() if err != nil { return nil, err } 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) if err != nil { if isServiceNotExists(err) { return false, nil } 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") } winmgr, err := connectServiceManager() if err != nil { return false, err } defer winmgr.Disconnect() return serviceExistsWithManager(winmgr, name) } func CreateService(mysvc WinSvcInput) (*WinSvc, error) { if strings.TrimSpace(mysvc.Name) == "" { return nil, wrapInputError("empty service name") } if strings.TrimSpace(mysvc.ExecPath) == "" { return nil, wrapInputError("empty executable path") } winmgr, err := connectServiceManager() 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") } 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() } }() err = eventlog.InstallAsEventCreate(mysvc.Name, eventlog.Error|eventlog.Warning|eventlog.Info) if err != nil { _ = gsvc.Delete() 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) } var result WinSvc result.Service = gsvc created = true return &result, nil } func OpenService(name string) (*WinSvc, error) { name = strings.TrimSpace(name) if name == "" { return nil, wrapInputError("empty service name") } winmgr, err := connectServiceManager() 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) } 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() if err != nil { return err } defer winmgr.Disconnect() service, err := winmgr.OpenService(name) if err != nil { if isServiceNotExists(err) { return wrapNotFoundError("service " + name) } return err } if err := service.Delete(); err != nil { service.Close() return err } service.Close() 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 } return !ok, nil }) } func StopService(name string) error { mysvc, err := OpenService(name) if err != nil { return err } defer mysvc.Close() status, err := mysvc.Service.Query() 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 { return err } } return waitServiceStatus(mysvc.Service, svc.Stopped, defaultServiceWaitTimeout) } func StartService(name string) error { mysvc, err := OpenService(name) if err != nil { return err } defer mysvc.Close() status, err := mysvc.Service.Query() if err != nil { return err } if status.State == svc.Running { return nil } if err := mysvc.Service.Start(); err != nil { return err } return waitServiceStatus(mysvc.Service, svc.Running, defaultServiceWaitTimeout) } 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() if err != nil { return err } if status.State == svc.Running { return nil } if err := w.Service.Start(); err != nil { return err } return waitServiceStatus(w.Service, svc.Running, defaultServiceWaitTimeout) } 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 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 }