315 lines
6.8 KiB
Go
315 lines
6.8 KiB
Go
package services
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/amidaware/rmmagent/agent/utils"
|
|
"github.com/gonutz/w32/v2"
|
|
"golang.org/x/sys/windows/svc"
|
|
"golang.org/x/sys/windows/svc/mgr"
|
|
)
|
|
|
|
const (
|
|
WinSvcName = "tacticalrmm"
|
|
meshSvcName = "mesh agent"
|
|
)
|
|
|
|
// ShowStatus prints windows service status
|
|
// If called from an interactive desktop, pops up a message box
|
|
// Otherwise prints to the console
|
|
func ShowStatus(version string) {
|
|
statusMap := make(map[string]string)
|
|
svcs := []string{WinSvcName, meshSvcName}
|
|
|
|
for _, service := range svcs {
|
|
status, err := GetServiceStatus(service)
|
|
if err != nil {
|
|
statusMap[service] = "Not Installed"
|
|
continue
|
|
}
|
|
statusMap[service] = status
|
|
}
|
|
|
|
window := w32.GetForegroundWindow()
|
|
if window != 0 {
|
|
_, consoleProcID := w32.GetWindowThreadProcessId(window)
|
|
if w32.GetCurrentProcessId() == consoleProcID {
|
|
w32.ShowWindow(window, w32.SW_HIDE)
|
|
}
|
|
var handle w32.HWND
|
|
msg := fmt.Sprintf("Agent: %s\n\nMesh Agent: %s", statusMap[WinSvcName], statusMap[meshSvcName])
|
|
w32.MessageBox(handle, msg, fmt.Sprintf("Tactical RMM v%s", version), w32.MB_OK|w32.MB_ICONINFORMATION)
|
|
} else {
|
|
fmt.Println("Tactical RMM Version", version)
|
|
fmt.Println("Tactical Agent:", statusMap[WinSvcName])
|
|
fmt.Println("Mesh Agent:", statusMap[meshSvcName])
|
|
}
|
|
}
|
|
|
|
func GetServiceStatus(name string) (string, error) {
|
|
conn, err := mgr.Connect()
|
|
if err != nil {
|
|
return "n/a", err
|
|
}
|
|
|
|
defer conn.Disconnect()
|
|
srv, err := conn.OpenService(name)
|
|
if err != nil {
|
|
return "n/a", err
|
|
}
|
|
|
|
defer srv.Close()
|
|
q, err := srv.Query()
|
|
if err != nil {
|
|
return "n/a", err
|
|
}
|
|
|
|
return serviceStatusText(uint32(q.State)), nil
|
|
}
|
|
|
|
// https://docs.microsoft.com/en-us/dotnet/api/system.serviceprocess.servicecontrollerstatus?view=dotnet-plat-ext-3.1
|
|
func serviceStatusText(num uint32) string {
|
|
switch num {
|
|
case 1:
|
|
return "stopped"
|
|
case 2:
|
|
return "start_pending"
|
|
case 3:
|
|
return "stop_pending"
|
|
case 4:
|
|
return "running"
|
|
case 5:
|
|
return "continue_pending"
|
|
case 6:
|
|
return "pause_pending"
|
|
case 7:
|
|
return "paused"
|
|
default:
|
|
return "unknown"
|
|
}
|
|
}
|
|
|
|
// GetServices returns a list of windows services
|
|
func GetServices() ([]Service, []error, error) {
|
|
ret := make([]Service, 0)
|
|
|
|
conn, err := mgr.Connect()
|
|
if err != nil {
|
|
return ret, nil, err
|
|
}
|
|
|
|
defer conn.Disconnect()
|
|
svcs, err := conn.ListServices()
|
|
if err != nil {
|
|
return ret, nil, err
|
|
}
|
|
|
|
errors := []error{}
|
|
|
|
for _, s := range svcs {
|
|
srv, err := conn.OpenService(s)
|
|
if err != nil {
|
|
if err.Error() != "Access is denied." {
|
|
//a.Logger.Debugln("Open Service", s, err)
|
|
errors = append(errors, err)
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
defer srv.Close()
|
|
q, err := srv.Query()
|
|
if err != nil {
|
|
errors = append(errors, err)
|
|
continue
|
|
}
|
|
|
|
conf, err := srv.Config()
|
|
if err != nil {
|
|
errors = append(errors, err)
|
|
continue
|
|
}
|
|
|
|
ret = append(ret, Service{
|
|
Name: s,
|
|
Status: serviceStatusText(uint32(q.State)),
|
|
DisplayName: utils.CleanString(conf.DisplayName),
|
|
BinPath: utils.CleanString(conf.BinaryPathName),
|
|
Description: utils.CleanString(conf.Description),
|
|
Username: utils.CleanString(conf.ServiceStartName),
|
|
PID: q.ProcessId,
|
|
StartType: serviceStartType(uint32(conf.StartType)),
|
|
DelayedAutoStart: conf.DelayedAutoStart,
|
|
})
|
|
}
|
|
|
|
return ret, errors, nil
|
|
}
|
|
|
|
// https://docs.microsoft.com/en-us/dotnet/api/system.serviceprocess.servicestartmode?view=dotnet-plat-ext-3.1
|
|
func serviceStartType(num uint32) string {
|
|
switch num {
|
|
case 0:
|
|
return "Boot"
|
|
case 1:
|
|
return "System"
|
|
case 2:
|
|
return "Automatic"
|
|
case 3:
|
|
return "Manual"
|
|
case 4:
|
|
return "Disabled"
|
|
default:
|
|
return "Unknown"
|
|
}
|
|
}
|
|
|
|
func ControlService(name, action string) WinSvcResp {
|
|
conn, err := mgr.Connect()
|
|
if err != nil {
|
|
return WinSvcResp{Success: false, ErrorMsg: err.Error()}
|
|
}
|
|
|
|
defer conn.Disconnect()
|
|
srv, err := conn.OpenService(name)
|
|
if err != nil {
|
|
return WinSvcResp{Success: false, ErrorMsg: err.Error()}
|
|
}
|
|
|
|
defer srv.Close()
|
|
var status svc.Status
|
|
switch action {
|
|
|
|
case "stop":
|
|
status, err = srv.Control(svc.Stop)
|
|
if err != nil {
|
|
return WinSvcResp{Success: false, ErrorMsg: err.Error()}
|
|
}
|
|
timeout := time.Now().Add(30 * time.Second)
|
|
for status.State != svc.Stopped {
|
|
if timeout.Before(time.Now()) {
|
|
return WinSvcResp{Success: false, ErrorMsg: "Timed out waiting for service to stop"}
|
|
}
|
|
|
|
time.Sleep(500 * time.Millisecond)
|
|
status, err = srv.Query()
|
|
if err != nil {
|
|
return WinSvcResp{Success: false, ErrorMsg: err.Error()}
|
|
}
|
|
}
|
|
|
|
return WinSvcResp{Success: true, ErrorMsg: ""}
|
|
|
|
case "start":
|
|
err := srv.Start()
|
|
if err != nil {
|
|
return WinSvcResp{Success: false, ErrorMsg: err.Error()}
|
|
}
|
|
|
|
return WinSvcResp{Success: true, ErrorMsg: ""}
|
|
}
|
|
|
|
return WinSvcResp{Success: false, ErrorMsg: "Something went wrong"}
|
|
}
|
|
|
|
func GetServiceDetail(name string) Service {
|
|
ret := Service{}
|
|
|
|
conn, err := mgr.Connect()
|
|
if err != nil {
|
|
return ret
|
|
}
|
|
|
|
defer conn.Disconnect()
|
|
srv, err := conn.OpenService(name)
|
|
if err != nil {
|
|
return ret
|
|
}
|
|
|
|
defer srv.Close()
|
|
q, err := srv.Query()
|
|
if err != nil {
|
|
return ret
|
|
}
|
|
|
|
conf, err := srv.Config()
|
|
if err != nil {
|
|
return ret
|
|
}
|
|
|
|
ret.BinPath = utils.CleanString(conf.BinaryPathName)
|
|
ret.Description = utils.CleanString(conf.Description)
|
|
ret.DisplayName = utils.CleanString(conf.DisplayName)
|
|
ret.Name = name
|
|
ret.PID = q.ProcessId
|
|
ret.StartType = serviceStartType(uint32(conf.StartType))
|
|
ret.Status = serviceStatusText(uint32(q.State))
|
|
ret.Username = utils.CleanString(conf.ServiceStartName)
|
|
ret.DelayedAutoStart = conf.DelayedAutoStart
|
|
return ret
|
|
}
|
|
|
|
func EditService(name, startupType string) WinSvcResp {
|
|
conn, err := mgr.Connect()
|
|
if err != nil {
|
|
return WinSvcResp{Success: false, ErrorMsg: err.Error()}
|
|
}
|
|
defer conn.Disconnect()
|
|
|
|
srv, err := conn.OpenService(name)
|
|
if err != nil {
|
|
return WinSvcResp{Success: false, ErrorMsg: err.Error()}
|
|
}
|
|
defer srv.Close()
|
|
|
|
conf, err := srv.Config()
|
|
if err != nil {
|
|
return WinSvcResp{Success: false, ErrorMsg: err.Error()}
|
|
}
|
|
|
|
var startType uint32
|
|
switch startupType {
|
|
case "auto":
|
|
startType = 2
|
|
case "autodelay":
|
|
startType = 2
|
|
case "manual":
|
|
startType = 3
|
|
case "disabled":
|
|
startType = 4
|
|
default:
|
|
return WinSvcResp{Success: false, ErrorMsg: "Unknown startup type provided"}
|
|
}
|
|
|
|
conf.StartType = startType
|
|
switch startupType {
|
|
case "autodelay":
|
|
conf.DelayedAutoStart = true
|
|
case "auto":
|
|
conf.DelayedAutoStart = false
|
|
}
|
|
|
|
err = srv.UpdateConfig(conf)
|
|
if err != nil {
|
|
return WinSvcResp{Success: false, ErrorMsg: err.Error()}
|
|
}
|
|
|
|
return WinSvcResp{Success: true, ErrorMsg: ""}
|
|
}
|
|
|
|
func ServiceExists(name string) (bool, error) {
|
|
conn, err := mgr.Connect()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
defer conn.Disconnect()
|
|
srv, err := conn.OpenService(name)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
defer srv.Close()
|
|
return true, nil
|
|
}
|