diff --git a/README.md b/README.md index ae90c48..0ddd43a 100644 --- a/README.md +++ b/README.md @@ -198,6 +198,7 @@ See `examples/cron/cron_job.go` - [0X8C - Demired](https://github.com/Demired) - [Maximus](https://github.com/maximus12793) - [AlgorathDev](https://github.com/AlgorathDev) +- [Alexis Camilleri](https://github.com/krysennn) All the contributors are welcome. If you would like to be the contributor please accept some rules. diff --git a/daemon.go b/daemon.go index ba0d996..cb62b0d 100644 --- a/daemon.go +++ b/daemon.go @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. /* -Package daemon 0.10.2 for use with Go (golang) services. +Package daemon 0.10.3 for use with Go (golang) services. Package daemon provides primitives for daemonization of golang services. This package is not provide implementation of user daemon, diff --git a/daemon_windows.go b/daemon_windows.go index 8d92cb5..6c1ffc9 100644 --- a/daemon_windows.go +++ b/daemon_windows.go @@ -9,13 +9,12 @@ import ( "errors" "fmt" "os/exec" + "regexp" "syscall" "unicode/utf16" "unsafe" ) -var ErrWindowsUnsupported = errors.New("Adding as a service failed. Download and place nssm.exe in the path to install this service as an service. NSSM url: https://nssm.cc/") - // windowsRecord - standard record (struct) for windows version of daemon package type windowsRecord struct { name string @@ -31,7 +30,6 @@ func newDaemon(name, description string, dependencies []string) (Daemon, error) // Install the service func (windows *windowsRecord) Install(args ...string) (string, error) { installAction := "Install " + windows.description + ":" - adminAccessNecessary := "Administrator access is needed to install a service." execp, err := execPath() @@ -39,21 +37,13 @@ func (windows *windowsRecord) Install(args ...string) (string, error) { return installAction + failed, err } - cmdArgs := []string{"install", windows.name, execp} + cmdArgs := []string{"create", windows.name, "start=auto", "binPath=" + execp} cmdArgs = append(cmdArgs, args...) - cmd := exec.Command("nssm.exe", cmdArgs...) - out, err := cmd.Output() + cmd := exec.Command("sc", cmdArgs...) + _, err = cmd.Output() if err != nil { - if len(out) > 0 { - fmt.Println(string(out)) - } else { - fmt.Println("No output. Probably service already exists. Try uninstall first.") - } - return installAction + failed, err - } - if len(out) == 0 { - return adminAccessNecessary, errors.New(adminAccessNecessary) + return installAction + failed, getWindowsError(err) } return installAction + " completed.", nil } @@ -61,10 +51,10 @@ func (windows *windowsRecord) Install(args ...string) (string, error) { // Remove the service func (windows *windowsRecord) Remove() (string, error) { removeAction := "Removing " + windows.description + ":" - cmd := exec.Command("nssm.exe", "remove", windows.name, "confirm") + cmd := exec.Command("sc", "delete", windows.name, "confirm") err := cmd.Run() if err != nil { - return removeAction + failed, err + return removeAction + failed, getWindowsError(err) } return removeAction + " completed.", nil } @@ -72,10 +62,10 @@ func (windows *windowsRecord) Remove() (string, error) { // Start the service func (windows *windowsRecord) Start() (string, error) { startAction := "Starting " + windows.description + ":" - cmd := exec.Command("nssm.exe", "start", windows.name) + cmd := exec.Command("sc", "start", windows.name) err := cmd.Run() if err != nil { - return startAction + failed, err + return startAction + failed, getWindowsError(err) } return startAction + " completed.", nil } @@ -83,7 +73,7 @@ func (windows *windowsRecord) Start() (string, error) { // Stop the service func (windows *windowsRecord) Stop() (string, error) { stopAction := "Stopping " + windows.description + ":" - cmd := exec.Command("nssm.exe", "stop", windows.name) + cmd := exec.Command("sc", "stop", windows.name) err := cmd.Run() if err != nil { return stopAction + failed, err @@ -93,12 +83,12 @@ func (windows *windowsRecord) Stop() (string, error) { // Status - Get service status func (windows *windowsRecord) Status() (string, error) { - cmd := exec.Command("nssm.exe", "status", windows.name) + cmd := exec.Command("sc", "query", windows.name) out, err := cmd.Output() if err != nil { - return "Getting status:" + failed, err + return "Getting status:" + failed, getWindowsError(err) } - return "Status: " + string(out), nil + return "Status: " + "SERVICE_" + getWindowsServiceState(out), nil } // Get executable path @@ -118,3 +108,24 @@ func execPath() (string, error) { } return string(utf16.Decode(b[0:n])), nil } + +// Get windows error +func getWindowsError(inputError error) error { + if exiterr, ok := inputError.(*exec.ExitError); ok { + if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { + if sysErr, ok := WinErrCode[status.ExitStatus()]; ok { + return errors.New(fmt.Sprintf("\n %s: %s \n %s", sysErr.Title, sysErr.Description, sysErr.Action)) + } + } + } + + return inputError +} + +// Get windows service state +func getWindowsServiceState(out []byte) string { + regex := regexp.MustCompile("STATE.*: (?P[0-9]) (?P.*) ") + service := regex.FindAllStringSubmatch(string(out), -1)[0] + + return service[2] +} diff --git a/helper_windows.go b/helper_windows.go new file mode 100644 index 0000000..12816bc --- /dev/null +++ b/helper_windows.go @@ -0,0 +1,128 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by +// license that can be found in the LICENSE file. + +package daemon + +// SystemError contains error description and corresponded action helper to fix it +type SystemError struct { + Title string + Description string + Action string +} + +var ( + // WinErrCode - List of system errors from Microsoft source: + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms681385(v=vs.85).aspx + WinErrCode = map[int]SystemError{ + 5: SystemError{ + Title: "ERROR_ACCESS_DENIED", + Description: "Access denied.", + Action: "Administrator access is needed to install a service.", + }, + 1051: SystemError{ + Title: "ERROR_DEPENDENT_SERVICES_RUNNING", + Description: "A stop control has been sent to a service that other running services are dependent on.", + }, + 1052: SystemError{ + Title: "ERROR_INVALID_SERVICE_CONTROL", + Description: "The requested control is not valid for this service.", + }, + 1053: SystemError{ + Title: "ERROR_SERVICE_REQUEST_TIMEOUT", + Description: "The service did not respond to the start or control request in a timely fashion.", + }, + 1054: SystemError{ + Title: "ERROR_SERVICE_NO_THREAD", + Description: "A thread could not be created for the service.", + }, + 1055: SystemError{ + Title: "ERROR_SERVICE_DATABASE_LOCKED", + Description: "The service database is locked.", + }, + 1056: SystemError{ + Title: "ERROR_SERVICE_ALREADY_RUNNING", + Description: "An instance of the service is already running.", + }, + 1057: SystemError{ + Title: "ERROR_INVALID_SERVICE_ACCOUNT", + Description: "The account name is invalid or does not exist, or the password is invalid for the account name specified.", + }, + 1058: SystemError{ + Title: "ERROR_SERVICE_DISABLED", + Description: "The service cannot be started, either because it is disabled or because it has no enabled devices associated with it.", + }, + 1060: SystemError{ + Title: "ERROR_SERVICE_DOES_NOT_EXIST", + Description: "The specified service does not exist as an installed service.", + }, + 1061: SystemError{ + Title: "ERROR_SERVICE_CANNOT_ACCEPT_CTRL", + Description: "The service cannot accept control messages at this time.", + }, + 1062: SystemError{ + Title: "ERROR_SERVICE_NOT_ACTIVE", + Description: "The service has not been started.", + }, + 1063: SystemError{ + Title: "ERROR_FAILED_SERVICE_CONTROLLER_CONNECT", + Description: "The service process could not connect to the service controller.", + }, + 1064: SystemError{ + Title: "ERROR_EXCEPTION_IN_SERVICE", + Description: "An exception occurred in the service when handling the control request.", + }, + 1066: SystemError{ + Title: "ERROR_SERVICE_SPECIFIC_ERROR", + Description: "The service has returned a service-specific error code.", + }, + 1068: SystemError{ + Title: "ERROR_SERVICE_DEPENDENCY_FAIL", + Description: "The dependency service or group failed to start.", + }, + 1069: SystemError{ + Title: "ERROR_SERVICE_LOGON_FAILED", + Description: "The service did not start due to a logon failure.", + }, + 1070: SystemError{ + Title: "ERROR_SERVICE_START_HANG", + Description: "After starting, the service hung in a start-pending state.", + }, + 1071: SystemError{ + Title: "ERROR_INVALID_SERVICE_LOCK", + Description: "The specified service database lock is invalid.", + }, + 1072: SystemError{ + Title: "ERROR_SERVICE_MARKED_FOR_DELETE", + Description: "The specified service has been marked for deletion.", + }, + 1073: SystemError{ + Title: "ERROR_SERVICE_EXISTS", + Description: "The specified service already exists.", + }, + 1075: SystemError{ + Title: "ERROR_SERVICE_DEPENDENCY_DELETED", + Description: "The dependency service does not exist or has been marked for deletion.", + }, + 1077: SystemError{ + Title: "ERROR_SERVICE_NEVER_STARTED", + Description: "No attempts to start the service have been made since the last boot.", + }, + 1078: SystemError{ + Title: "ERROR_DUPLICATE_SERVICE_NAME", + Description: "The name is already in use as either a service name or a service display name.", + }, + 1079: SystemError{ + Title: "ERROR_DIFFERENT_SERVICE_ACCOUNT", + Description: "The account specified for this service is different from the account specified for other services running in the same process.", + }, + 1083: SystemError{ + Title: "ERROR_SERVICE_NOT_IN_EXE", + Description: "The executable program that this service is configured to run in does not implement the service.", + }, + 1084: SystemError{ + Title: "ERROR_NOT_SAFEBOOT_SERVICE", + Description: "This service cannot be started in Safe Mode.", + }, + } +)