
Register windows service
Article source code address: https://www.github.com/snowlyg/learns
There are many ways to register the previous program as a windows service:
- Use nssm.exe to execute
nssm.exe install myservice - Using https://github.com/kardianos project, how to use reference example
- and srvany.exe etc.
Among them, https://github.com/kardianos is more suitable for realizing the needs of automatic update projects. This project is compatible with multiple system environments such as linux, mac windows, etc. But we only need the windows system environment. It does not need to be so troublesome to update a program in other environments.
Looking at the source code, you can see that the windwos system uses this package golang.org/x/sys/windows. Use this package to simply write a new package.
- Create a new
windowsfolder in the project directory and create 7 new files: - service.go
- beep.go
- install.go -start.go
- stop.go -status.go -uninstall.go
service.go file
package windows
import (
"fmt"
"os"
"os/signal"
"time"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/debug"
"golang.org/x/sys/windows/svc/eventlog"
)
var (
elog debug.Log
interactive = false
)
// init 初始化
// IsAnInteractiveSession 判断程序是否运行在交互模式
// 此方法说是要被废弃了,后期要使用 isWindowsService 代替
// 不过我使用次方法无法正常启动注册好的程序
func init() {
var err error
interactive, err = svc.IsAnInteractiveSession()
if err != nil {
panic(err)
}
}
// Interface
type Interface interface {
Start() error
Stop() error
}
// WindowsService
type WindowsService struct {
Name string
i Interface
}
// NewWindowsService new一个服务对象
// name 服务名称
func NewWindowsService(i Interface, name string) (*WindowsService, error) {
ws := &WindowsService{
i: i,
Name: name,
}
return ws, nil
}
// Execute 服务执行方法,控制服务启动停止
func (ws *WindowsService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (bool, uint32) {
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown
changes <- svc.Status{State: svc.StartPending}
if err := ws.i.Start(); err != nil {
elog.Info(1, fmt.Sprintf("%s service start failed: %v", ws.Name, err))
return true, 1
}
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
loop:
for {
c := <-r
switch c.Cmd {
case svc.Interrogate:
changes <- c.CurrentStatus
case svc.Stop:
changes <- svc.Status{State: svc.StopPending}
if err := ws.i.Stop(); err != nil {
elog.Info(1, fmt.Sprintf("%s service stop failed: %v", ws.Name, err))
return true, 2
}
break loop
case svc.Shutdown:
changes <- svc.Status{State: svc.StopPending}
err := ws.i.Stop()
if err != nil {
elog.Info(1, fmt.Sprintf("%s service shutdown failed: %v", ws.Name, err))
return true, 2
}
break loop
default:
continue loop
}
}
return false, 0
}
func (ws *WindowsService) Run(isDebug bool) error {
var err error
if !interactive {
if isDebug {
elog = debug.New(ws.Name)
} else {
elog, err = eventlog.Open(ws.Name)
if err != nil {
return err
}
}
defer elog.Close()
elog.Info(1, fmt.Sprintf("starting %s service", ws.Name))
run := svc.Run
if isDebug {
run = debug.Run
}
err = run(ws.Name, ws)
if err != nil {
elog.Error(1, fmt.Sprintf("%s service failed: %v", ws.Name, err))
return err
}
elog.Info(1, fmt.Sprintf("%s service stopped", ws.Name))
}
err = ws.i.Start()
if err != nil {
return err
}
sigChan := make(chan os.Signal)
signal.Notify(sigChan, os.Interrupt)
<-sigChan
return ws.i.Stop()
}beep.go file
package windows
import (
"syscall"
)
// BUG(brainman): MessageBeep Windows api is broken on Windows 7,
// so this example does not beep when runs as service on Windows 7.
var (
beepFunc = syscall.MustLoadDLL("user32.dll").MustFindProc("MessageBeep")
)
func beep() {
beepFunc.Call(0xffffffff)
}install.go file
package windows
import (
"fmt"
"golang.org/x/sys/windows/svc/eventlog"
"golang.org/x/sys/windows/svc/mgr"
)
func ServiceInstall(svcName, execPath, dispalyName string, args ...string) error {
m, err := mgr.Connect()
if err != nil {
return err
}
defer m.Disconnect()
s, err := m.OpenService(svcName)
if err == nil {
s.Close()
return fmt.Errorf("service %s already exists", svcName)
}
config := mgr.Config{
DisplayName: dispalyName,
StartType: mgr.StartAutomatic,
}
if len(args) >= 2 {
config.ServiceStartName = args[0]
config.Password = args[1]
}
s, err = m.CreateService(svcName, execPath, config)
if err != nil {
return fmt.Errorf("CreateService() failed: %s", err)
}
defer s.Close()
err = eventlog.InstallAsEventCreate(svcName, eventlog.Error|eventlog.Warning|eventlog.Info)
if err != nil {
s.Delete()
return fmt.Errorf("InstallAsEventCreate() failed: %s", err)
}
return nil
}start.go file
package windows
import (
"golang.org/x/sys/windows/svc/mgr"
)
func ServiceStart(srcName string) error {
m, err := mgr.Connect()
if err != nil {
return err
}
defer m.Disconnect()
s, err := m.OpenService(srcName)
if err != nil {
return err
}
defer s.Close()
return s.Start()
}status.go file
package windows
import (
"errors"
"fmt"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/mgr"
)
type Status byte
const (
StatusUnknown Status = iota // Status is unable to be determined due to an error or it was not installed.
StatusRunning
StatusStopped
StatusUninstall
)
var (
// ErrNameFieldRequired is returned when Config.Name is empty.
ErrNameFieldRequired = errors.New("Config.Name field is required.")
// ErrNoServiceSystemDetected is returned when no system was detected.
ErrNoServiceSystemDetected = errors.New("No service system detected.")
// ErrNotInstalled is returned when the service is not installed
ErrNotInstalled = errors.New("the service is not installed")
)
// status
func ServiceStatus(srcName string) (Status, error) {
m, err := mgr.Connect()
if err != nil {
return StatusUnknown, err
}
defer m.Disconnect()
s, err := m.OpenService(srcName)
if err != nil {
if err.Error() == "The specified service does not exist as an installed service." {
return StatusUninstall, nil
}
return StatusUnknown, err
}
defer s.Close()
status, err := s.Query()
if err != nil {
return StatusUnknown, err
}
switch status.State {
case svc.StartPending:
fallthrough
case svc.Running:
return StatusRunning, nil
case svc.PausePending:
fallthrough
case svc.Paused:
fallthrough
case svc.ContinuePending:
fallthrough
case svc.StopPending:
fallthrough
case svc.Stopped:
return StatusStopped, nil
default:
return StatusUnknown, fmt.Errorf("unknown status %v", status)
}
}stop.go file
package windows
import (
"strconv"
"time"
"golang.org/x/sys/windows/registry"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/mgr"
)
func ServiceStop(srcName string) error {
m, err := mgr.Connect()
if err != nil {
return err
}
defer m.Disconnect()
s, err := m.OpenService(srcName)
if err != nil {
return err
}
defer s.Close()
return stopWait(s)
}
func stopWait(s *mgr.Service) error {
// First stop the service. Then wait for the service to
// actually stop before starting it.
status, err := s.Control(svc.Stop)
if err != nil {
return err
}
timeDuration := time.Millisecond * 50
timeout := time.After(getStopTimeout() + (timeDuration * 2))
tick := time.NewTicker(timeDuration)
defer tick.Stop()
for status.State != svc.Stopped {
select {
case <-tick.C:
status, err = s.Query()
if err != nil {
return err
}
case <-timeout:
break
}
}
return nil
}
// getStopTimeout fetches the time before windows will kill the service.
func getStopTimeout() time.Duration {
// For default and paths see https://support.microsoft.com/en-us/kb/146092
defaultTimeout := time.Millisecond * 20000
key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control`, registry.READ)
if err != nil {
return defaultTimeout
}
sv, _, err := key.GetStringValue("WaitToKillServiceTimeout")
if err != nil {
return defaultTimeout
}
v, err := strconv.Atoi(sv)
if err != nil {
return defaultTimeout
}
return time.Millisecond * time.Duration(v)
}uninstall.go file
package windows
import (
"fmt"
"golang.org/x/sys/windows/svc/eventlog"
"golang.org/x/sys/windows/svc/mgr"
)
func ServiceUninstall(srcName string) error {
m, err := mgr.Connect()
if err != nil {
return err
}
defer m.Disconnect()
s, err := m.OpenService(srcName)
if err != nil {
return fmt.Errorf("service %s is not installed", srcName)
}
defer s.Close()
err = s.Delete()
if err != nil {
return err
}
err = eventlog.Remove(srcName)
if err != nil {
return fmt.Errorf("RemoveEventLogSource() failed: %s", err)
}
return nil
}Modify the previous main.go file
package main
import (
"fmt"
"os"
"strings"
"time"
"github.com/snowlyg/learns/advance/windows"
)
func run() {
// 每隔 5 秒打印当前时间
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
// for 循环阻塞程序主进程
// ticker.C 通道每隔五秒会发送一个值
for {
<-ticker.C
// 格式化时间
now := time.Now().Format(time.RFC3339)
fmt.Printf("当前时间:%s \n", now)
}
}
func (p *program) Start() error {
go run()
return nil
}
func (p *program) Stop() error {
//stop
return nil
}
type program struct{}
// usage 获取终端输入参数
func usage(errmsg string) {
fmt.Fprintf(os.Stderr,
"%s\n\n"+
"usage: %s <command> <servicename>\n"+
" where <command> is one of\n"+
" install, remove, start, stop, status .\n"+
" and use install : .\n"+
" install <service name> <exec path> <display name> <system name> <password> \n",
errmsg, os.Args[0])
os.Exit(2)
}
func main() {
// new 一个服务
s, err := windows.NewWindowsService(&program{}, "myservice")
if err != nil {
fmt.Printf("new service get error %v \n", err)
}
if len(os.Args) >= 2 {
srvName := strings.ToLower(os.Args[2])
cmd := strings.ToLower(os.Args[1])
switch cmd {
case "start": // 启动
err := windows.ServiceStart(srvName)
if err != nil {
fmt.Printf("%v \n", err)
}
println("start success")
return
case "install": //安装
if len(os.Args) == 7 {
err := windows.ServiceInstall(srvName, os.Args[3], os.Args[4], os.Args[5], os.Args[6])
if err != nil {
fmt.Printf("%v \n", err)
}
} else if len(os.Args) == 5 {
err := windows.ServiceInstall(srvName, os.Args[3], os.Args[4])
if err != nil {
fmt.Printf("%v \n", err)
}
} else {
usage("error command specified")
}
println("install success")
return
case "stop": // 停止
err := windows.ServiceStop(srvName)
if err != nil {
fmt.Printf("%v \n", err)
}
println("stop success")
return
case "remove": // 卸载
err := windows.ServiceUninstall(srvName)
if err != nil {
fmt.Printf("%v \n", err)
}
println("remove success")
return
case "status": // 查询服务状态
status, _ := windows.ServiceStatus(srvName)
if status == windows.StatusRunning {
println("运行中")
} else if status == windows.StatusStopped {
println("已停止")
} else if status == windows.StatusUninstall {
println("未安装")
} else if status == windows.StatusUninstall {
println("未安装")
}
return
default:
println("invaild command")
}
switch cmd {
case "start": // 启动
err := windows.ServiceStart(srvName)
if err != nil {
fmt.Printf("%v \n", err)
}
println("start success")
return
case "install": //安装
if len(os.Args) == 7 {
err := windows.ServiceInstall(srvName, os.Args[3], os.Args[4], os.Args[5], os.Args[6])
if err != nil {
fmt.Printf("%v \n", err)
}
} else if len(os.Args) == 5 {
err := windows.ServiceInstall(srvName, os.Args[3], os.Args[4])
if err != nil {
fmt.Printf("%v \n", err)
}
} else {
usage("error command specified")
}
println("install success")
return
case "stop": // 停止
err := windows.ServiceStop(srvName)
if err != nil {
fmt.Printf("%v \n", err)
}
println("stop success")
return
case "remove": // 卸载
err := windows.ServiceUninstall(srvName)
if err != nil {
fmt.Printf("%v \n", err)
}
println("remove success")
return
case "status": // 查询服务状态
status, _ := windows.ServiceStatus(srvName)
if status == windows.StatusRunning {
println("运行中")
} else if status == windows.StatusStopped {
println("已停止")
} else if status == windows.StatusUninstall {
println("未安装")
} else if status == windows.StatusUnknown {
println("未知状态")
}
return
default:
println("invaild command")
}
}
s.Run(false)
}Package file
Package the program again go build -o myservice.exe main.go to generate the myservice.exe file.
The service can be controlled through the following command line:
# 注册服务,有七个参数 服务名称、服务文件路径、服务显示名称、系统账号、系统密码
# 只有 服务名称、服务文件路径 为必选参数,其他为可选参数
# 有账号密码
.\cmd\advance.exe install myservice "C:\Users\Administrator\go\src\github.com\snowlyg\learns\cmd\advance.exe" myservice ".\Administrator" "123456"
# 没账号密码
.\cmd\advance.exe install myservice "C:\Users\Administrator\go\src\github.com\snowlyg\learns\cmd\advance.exe" myservice
# 启动服务
.\cmd\advance.exe start myservice
# 停止服务
.\cmd\advance.exe stop myservice
# 卸载服务
.\cmd\advance.exe remove myservice
# 查询服务状态
.\cmd\advance.exe status myservice Small pit
There is a small pitfall here: when using the account (administrator) and password (123456) parameters to register the service, it will prompt that the account password is incorrect when starting. I have looked at the source code and searched for this problem but have not been able to solve the problem. Finally I accidentally saw this document https://docs.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-createservicea
**It took me several hours to find this document. After all, it had been several days. If you look carefully at this document, you will find this description: **

It is the last line. To register the service and obtain system authorization, you need to use .\Administrator.
Finally, let me complain: Windows documentation is really hard to find.
