package windows
import (
"fmt"
"strings"
"time"
"unsafe"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/debug"
"golang.org/x/sys/windows/svc/eventlog"
"golang.org/x/sys/windows/svc/mgr"
)
func RegisterService(serviceName, execPath, displayName string, depends, args []string) error {
m, err := mgr.Connect()
if err != nil {
return err
}
defer m.Disconnect()
c := mgr.Config{
ServiceType: windows.SERVICE_WIN32_OWN_PROCESS,
StartType: mgr.StartAutomatic,
ErrorControl: mgr.ErrorNormal,
Dependencies: depends,
DisplayName: displayName,
}
s, err := m.CreateService(serviceName, execPath, c, args...)
if err != nil {
return err
}
defer s.Close()
const (
scActionNone = 0
scActionRestart = 1
scActionReboot = 2
scActionRunCommand = 3
serviceConfigFailureActions = 2
)
type serviceFailureActions struct {
ResetPeriod uint32
RebootMsg *uint16
Command *uint16
ActionsCount uint32
Actions uintptr
}
type scAction struct {
Type uint32
Delay uint32
}
t := []scAction{
{Type: scActionRestart, Delay: uint32(60 * time.Second / time.Millisecond)},
{Type: scActionRestart, Delay: uint32(60 * time.Second / time.Millisecond)},
{Type: scActionNone},
}
lpInfo := serviceFailureActions{ResetPeriod: uint32(24 * time.Hour / time.Second), ActionsCount: uint32(3), Actions: uintptr(unsafe.Pointer(&t[0]))}
err = windows.ChangeServiceConfig2(s.Handle, serviceConfigFailureActions, (*byte)(unsafe.Pointer(&lpInfo)))
if err != nil {
return err
}
return eventlog.Install(serviceName, execPath, false, eventlog.Info|eventlog.Warning|eventlog.Error)
}
func UnRegisterService(serviceName string) error {
m, err := mgr.Connect()
if err != nil {
return err
}
defer m.Disconnect()
s, err := m.OpenService(serviceName)
if err != nil {
return err
}
defer s.Close()
eventlog.Remove(serviceName)
err = s.Delete()
if err != nil {
return err
}
return nil
}
func StartService(serviceName string) error {
m, err := mgr.Connect()
if err != nil {
return err
}
defer m.Disconnect()
s, err := m.OpenService(serviceName)
if err != nil {
return fmt.Errorf("could not open service: %v", err)
}
defer s.Close()
err = s.Start()
if err != nil {
return fmt.Errorf("could not start service: %v", err)
}
return nil
}
func StopService(serviceName string) error {
return controlService(serviceName, svc.Stop, svc.Stopped)
}
func RestartService(serviceName string) error {
if err := controlService(serviceName, svc.Stop, svc.Stopped); err != nil &&
!strings.Contains(err.Error(), "service has not been started") {
return err
}
return StartService(serviceName)
}
func controlService(name string, c svc.Cmd, to svc.State) error {
m, err := mgr.Connect()
if err != nil {
return err
}
defer m.Disconnect()
s, err := m.OpenService(name)
if err != nil {
return fmt.Errorf("could not access service: %v", err)
}
defer s.Close()
status, err := s.Control(c)
if err != nil {
return fmt.Errorf("could not send control=%d: %v", c, err)
}
timeout := time.Now().Add(10 * time.Second)
for status.State != to {
if timeout.Before(time.Now()) {
return fmt.Errorf("timeout waiting for service to go to state=%d", to)
}
time.Sleep(300 * time.Millisecond)
status, err = s.Query()
if err != nil {
return fmt.Errorf("could not retrieve service status: %v", err)
}
}
return nil
}
var elog debug.Log
func RunAsService(name string, start, stop func() error, isDebug bool) (err error) {
if isDebug {
elog = debug.New(name)
} else {
elog, err = eventlog.Open(name)
if err != nil {
return err
}
}
defer elog.Close()
run := svc.Run
if isDebug {
run = debug.Run
}
elog.Info(1, fmt.Sprintf("winsvc.RunAsService: starting %s service", name))
if err = run(name, &winService{Start: start, Stop: stop}); err != nil {
elog.Error(1, fmt.Sprintf("%s service failed: %v", name, err))
return err
}
elog.Info(1, fmt.Sprintf("winsvc.RunAsService: %s service stopped", name))
return nil
}
type winService struct {
Start func() error
Stop func() error
}
func (p *winService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) {
elog.Info(1, "winsvc.Execute:"+"begin")
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPauseAndContinue
changes <- svc.Status{State: svc.StartPending}
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
go func() {
defer func() {
changes <- svc.Status{State: svc.StopPending}
p.Stop()
}()
for {
select {
case c := <-r:
switch c.Cmd {
case svc.Interrogate:
changes <- c.CurrentStatus
time.Sleep(100 * time.Millisecond)
changes <- c.CurrentStatus
case svc.Stop, svc.Shutdown:
return
case svc.Pause:
changes <- svc.Status{State: svc.Paused, Accepts: cmdsAccepted}
case svc.Continue:
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
default:
elog.Error(1, fmt.Sprintf("winsvc.Execute:: unexpected control request #%d", c))
}
}
}
}()
p.Start()
elog.Info(1, "winsvc.Execute:"+"end")
return
}