diff --git a/agent/agent_windows.go b/agent/agent_windows.go index ec87c53..f88c312 100644 --- a/agent/agent_windows.go +++ b/agent/agent_windows.go @@ -34,12 +34,14 @@ type WindowsAgent struct { DB Host ProgramDir string + EXE string SystemDrive string SaltCall string Nssm string SaltMinion string SaltInstaller string MeshInstaller string + MeshSVC string PyBin string Headers map[string]string Logger *logrus.Logger @@ -52,6 +54,7 @@ func New(logger *logrus.Logger, version string) *WindowsAgent { host, _ := ps.Host() info := host.Info() pd := filepath.Join(os.Getenv("ProgramFiles"), "TacticalAgent") + exe := filepath.Join(pd, "tacticalrmm.exe") dbFile := filepath.Join(pd, "agentdb.db") sd := os.Getenv("SystemDrive") pybin := filepath.Join(sd, "\\salt", "bin", "python.exe") @@ -82,12 +85,14 @@ func New(logger *logrus.Logger, version string) *WindowsAgent { Timezone: info.Timezone, }, ProgramDir: pd, + EXE: exe, SystemDrive: sd, SaltCall: sc, Nssm: nssm, SaltMinion: saltexe, SaltInstaller: saltinstaller, MeshInstaller: mesh, + MeshSVC: "mesh agent", PyBin: pybin, Headers: headers, Logger: logger, @@ -356,18 +361,24 @@ func (a *WindowsAgent) GetDisks() []Disk { } // CMDShell mimics python's `subprocess.run(shell=True)` -// -// Context timeout won't work here since cmd.exe spawns a child process -// and golang's defer will only kill the parent process which will already -// -// for example, passing `ping 8.8.8.8 -t` here will run forever even with a timeout set -// -// Passing `detached` will also ensure the function returns immediately and will never hang, -// rather continue to run in the background -func CMDShell(command string, detached bool) (output [2]string, e error) { - var outb, errb bytes.Buffer +func CMDShell(cmdArgs []string, command string, timeout int, detached bool) (output [2]string, e error) { + var ( + outb bytes.Buffer + errb bytes.Buffer + cmd *exec.Cmd + timedOut bool = false + ) + + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) + defer cancel() + + if len(cmdArgs) > 0 && command == "" { + cmdArgs = append([]string{"/C"}, cmdArgs...) + cmd = exec.Command("cmd.exe", cmdArgs...) + } else { + cmd = exec.Command("cmd.exe", "/C", command) + } - cmd := exec.Command("cmd.exe", "/C", command) // https://docs.microsoft.com/en-us/windows/win32/procthread/process-creation-flags if detached { cmd.SysProcAttr = &windows.SysProcAttr{ @@ -376,10 +387,26 @@ func CMDShell(command string, detached bool) (output [2]string, e error) { } cmd.Stdout = &outb cmd.Stderr = &errb - err := cmd.Run() + err := cmd.Start() + + pid := int32(cmd.Process.Pid) + + go func(p int32) { + + <-ctx.Done() + + _ = KillProc(p) + timedOut = true + }(pid) + + err = cmd.Wait() + + if timedOut { + return [2]string{outb.String(), errb.String()}, ctx.Err() + } if err != nil { - return [2]string{"", ""}, fmt.Errorf("%s: %s", err, errb.String()) + return [2]string{outb.String(), errb.String()}, err } return [2]string{outb.String(), errb.String()}, nil @@ -413,9 +440,9 @@ func CMD(exe string, args []string, timeout int, detached bool) (output [2]strin // EnablePing enables ping func EnablePing() { - fmt.Println("Enabling ping...") + args := make([]string, 0) cmd := `netsh advfirewall firewall add rule name="ICMP Allow incoming V4 echo request" protocol=icmpv4:8,any dir=in action=allow` - _, err := CMDShell(cmd, false) + _, err := CMDShell(args, cmd, 10, false) if err != nil { fmt.Println(err) } @@ -423,7 +450,6 @@ func EnablePing() { // EnableRDP enables Remote Desktop func EnableRDP() { - fmt.Println("Enabling RDP...") k, _, err := registry.CreateKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control\Terminal Server`, registry.ALL_ACCESS) if err != nil { fmt.Println(err) @@ -435,8 +461,9 @@ func EnableRDP() { fmt.Println(err) } + args := make([]string, 0) cmd := `netsh advfirewall firewall set rule group="remote desktop" new enable=Yes` - _, cerr := CMDShell(cmd, false) + _, cerr := CMDShell(args, cmd, 10, false) if cerr != nil { fmt.Println(cerr) } @@ -444,7 +471,6 @@ func EnableRDP() { // DisableSleepHibernate disables sleep and hibernate func DisableSleepHibernate() { - fmt.Println("Disabling sleep/hibernate...") k, _, err := registry.CreateKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control\Session Manager\Power`, registry.ALL_ACCESS) if err != nil { fmt.Println(err) @@ -456,21 +482,23 @@ func DisableSleepHibernate() { fmt.Println(err) } + args := make([]string, 0) + var wg sync.WaitGroup currents := []string{"ac", "dc"} for _, i := range currents { wg.Add(1) go func(c string) { defer wg.Done() - CMDShell(fmt.Sprintf("powercfg /set%svalueindex scheme_current sub_buttons lidaction 0", c), false) - CMDShell(fmt.Sprintf("powercfg /x -standby-timeout-%s 0", c), false) - CMDShell(fmt.Sprintf("powercfg /x -hibernate-timeout-%s 0", c), false) - CMDShell(fmt.Sprintf("powercfg /x -disk-timeout-%s 0", c), false) - CMDShell(fmt.Sprintf("powercfg /x -monitor-timeout-%s 0", c), false) + _, _ = CMDShell(args, fmt.Sprintf("powercfg /set%svalueindex scheme_current sub_buttons lidaction 0", c), 5, false) + _, _ = CMDShell(args, fmt.Sprintf("powercfg /x -standby-timeout-%s 0", c), 5, false) + _, _ = CMDShell(args, fmt.Sprintf("powercfg /x -hibernate-timeout-%s 0", c), 5, false) + _, _ = CMDShell(args, fmt.Sprintf("powercfg /x -disk-timeout-%s 0", c), 5, false) + _, _ = CMDShell(args, fmt.Sprintf("powercfg /x -monitor-timeout-%s 0", c), 5, false) }(i) } wg.Wait() - CMDShell("powercfg -S SCHEME_CURRENT", false) + _, _ = CMDShell(args, "powercfg -S SCHEME_CURRENT", 5, false) } // LoggedOnUser returns active logged on console user @@ -564,18 +592,7 @@ func (a *WindowsAgent) RecoverSalt() { a.Logger.Debugln("Salt recovery completed on", a.Hostname) } -//RecoverMesh recovers mesh agent -func (a *WindowsAgent) RecoverMesh() { - meshSVC := "mesh agent" - a.Logger.Debugln("Attempting mesh recovery on", a.Hostname) - defer CMD("sc.exe", []string{"start", meshSVC}, 20, false) - - args := []string{"stop", meshSVC} - CMD("sc.exe", args, 45, false) - WaitForService(meshSVC, "stopped", 5) - a.ForceKillMesh() - - var meshexe string +func (a *WindowsAgent) getMeshEXE() (meshexe string) { mesh1 := filepath.Join(os.Getenv("ProgramFiles"), "Mesh Agent", "MeshAgent.exe") mesh2 := filepath.Join(a.ProgramDir, a.MeshInstaller) if FileExists(mesh1) { @@ -583,6 +600,20 @@ func (a *WindowsAgent) RecoverMesh() { } else { meshexe = mesh2 } + return meshexe +} + +//RecoverMesh recovers mesh agent +func (a *WindowsAgent) RecoverMesh() { + a.Logger.Debugln("Attempting mesh recovery on", a.Hostname) + defer CMD("sc.exe", []string{"start", a.MeshSVC}, 20, false) + + args := []string{"stop", a.MeshSVC} + CMD("sc.exe", args, 45, false) + WaitForService(a.MeshSVC, "stopped", 5) + a.ForceKillMesh() + + meshexe := a.getMeshEXE() out, err := CMD(meshexe, []string{"-nodeidhex"}, 10, false) if err != nil { @@ -640,10 +671,7 @@ func (a *WindowsAgent) RecoverMesh() { func (a *WindowsAgent) RecoverCMD(command string) { a.Logger.Debugln("Attempting shell recovery on", a.Hostname) a.Logger.Debugln(command) - cmd := exec.Command("cmd.exe", "/C", command) - if err := cmd.Run(); err != nil { - a.Logger.Debugln(err) - } + _, _ = CMDShell([]string{}, command, 18000, true) } // ShowStatus prints windows service status diff --git a/agent/install_windows.go b/agent/install_windows.go index 2e7605b..fb2331d 100644 --- a/agent/install_windows.go +++ b/agent/install_windows.go @@ -11,6 +11,7 @@ import ( "time" "github.com/go-resty/resty/v2" + "github.com/gonutz/w32" ) type Installer struct { @@ -32,6 +33,8 @@ type Installer struct { } func (a *WindowsAgent) Install(i *Installer) { + a.checkExistingAndRemove() + i.Headers = map[string]string{ "content-type": "application/json", "Authorization": fmt.Sprintf("Token %s", i.Token), @@ -45,14 +48,14 @@ func (a *WindowsAgent) Install(i *Installer) { } if u.Scheme != "https" && u.Scheme != "http" { - a.installerMsg("Invalid URL (must contain https or http)\nInstallation Failed", "error") + a.installerMsg("Invalid URL (must contain https or http)", "error") } i.SaltMaster = u.Host a.Logger.Debugln("Salt Master:", i.SaltMaster) baseURL := u.Scheme + "://" + u.Host - a.Logger.Debugln(baseURL) + a.Logger.Debugln("Base URL:", baseURL) minion := filepath.Join(a.ProgramDir, a.SaltInstaller) a.Logger.Debugln("Salt Minion:", minion) @@ -60,7 +63,7 @@ func (a *WindowsAgent) Install(i *Installer) { rClient := resty.New() rClient.SetCloseConnection(true) rClient.SetTimeout(i.Timeout * time.Second) - rClient.SetDebug(a.Debug) + //rClient.SetDebug(a.Debug) // download or copy the salt-minion-setup.exe saltMin := filepath.Join(a.ProgramDir, a.SaltInstaller) @@ -135,33 +138,41 @@ func (a *WindowsAgent) Install(i *Installer) { agentToken := r.Result().(*TokenResp).Token - // install mesh agent - out, err := CMD(mesh, []string{"-fullinstall"}, int(60), false) - if err != nil { - a.installerMsg(fmt.Sprintf("Failed to install mesh agent: %s", err.Error()), "error") + a.Logger.Infoln("Installing mesh agent...") + a.Logger.Debugln("Mesh agent:", mesh) + meshOut, meshErr := CMD(mesh, []string{"-fullinstall"}, int(60), false) + if meshErr != nil { + a.installerMsg(fmt.Sprintf("Failed to install mesh agent: %s", meshErr.Error()), "error") } - if out[1] != "" { - a.installerMsg(fmt.Sprintf("Failed to install mesh agent: %s", out[1]), "error") + if meshOut[1] != "" { + a.installerMsg(fmt.Sprintf("Failed to install mesh agent: %s", meshOut[1]), "error") } - WaitForService("mesh agent", "running", 10) + fmt.Println(meshOut) + + a.Logger.Debugln("Waiting for mesh service to be running") + WaitForService(a.MeshSVC, "running", 15) + a.Logger.Debugln("Mesh service is running") + a.Logger.Debugln("Sleeping for 10") time.Sleep(10 * time.Second) meshSuccess := false var meshNodeID string for !meshSuccess { - pMesh, err := CMD(mesh, []string{"-nodeidhex"}, int(30), false) - if err != nil { - a.Logger.Errorln(err) + a.Logger.Debugln("Getting mesh node id hex") + pMesh, pErr := CMD(mesh, []string{"-nodeidhex"}, int(30), false) + if pErr != nil { + a.Logger.Errorln(pErr) time.Sleep(5 * time.Second) continue } - if out[1] != "" { - a.Logger.Errorln(out[1]) + if pMesh[1] != "" { + a.Logger.Errorln(pMesh[1]) time.Sleep(5 * time.Second) continue } meshNodeID = StripAll(pMesh[0]) + a.Logger.Debugln("Node id hex:", meshNodeID) if strings.Contains(strings.ToLower(meshNodeID), "not defined") { a.Logger.Errorln(meshNodeID) time.Sleep(5 * time.Second) @@ -197,7 +208,9 @@ func (a *WindowsAgent) Install(i *Installer) { agentPK := r.Result().(*NewAgentResp).AgentPK saltID := r.Result().(*NewAgentResp).SaltID - fmt.Println(agentToken, agentPK, saltID) + a.Logger.Debugln("Agent token:", agentToken) + a.Logger.Debugln("Agent PK:", agentPK) + a.Logger.Debugln("Salt ID:", saltID) // create the database db, err := sql.Open("sqlite3", filepath.Join(a.ProgramDir, "agentdb.db")) @@ -240,6 +253,156 @@ func (a *WindowsAgent) Install(i *Installer) { // refresh our agent with new values a = New(a.Logger, a.Version) + + // install salt + a.Logger.Debugln("changing dir to", a.ProgramDir) + cdErr := os.Chdir(a.ProgramDir) + if cdErr != nil { + a.installerMsg(cdErr.Error(), "error") + } + + a.Logger.Infoln("Installing the salt-minion, this might take a while...") + saltInstallArgs := []string{ + a.SaltInstaller, + "/S", + "/custom-config=saltcustom", + fmt.Sprintf("/master=%s", i.SaltMaster), + fmt.Sprintf("/minion-name=%s", saltID), + "/start-minion=1", + } + + a.Logger.Debugln("Installing salt with:", saltInstallArgs) + _, saltErr := CMDShell(saltInstallArgs, "", int(i.Timeout), false) + if saltErr != nil { + a.installerMsg(fmt.Sprintf("Unable to install salt: %s", saltErr.Error()), "error") + } + + a.Logger.Debugln("Waiting for salt-minion service enter the running state") + WaitForService("salt-minion", "running", 30) + a.Logger.Debugln("Salt-minion is running") + _, serr := WinServiceGet("salt-minion") + if serr != nil { + a.installerMsg("Salt installation failed\nCheck the log file in c:\\salt\\var\\log\\salt\\minion", "error") + } + + time.Sleep(5 * time.Second) + + // set new headers, no longer knox auth...use agent auth + rClient.SetHeaders(a.Headers) + + // accept the salt key on the rmm + a.Logger.Debugln("Registering salt with the RMM") + acceptPayload := map[string]string{"saltid": saltID, "agent_id": a.AgentID} + acceptAttempts := 0 + acceptRetries := 20 + for { + r, err := rClient.R().SetBody(acceptPayload).Post(fmt.Sprintf("%s/api/v2/saltminion/", baseURL)) + if err != nil { + a.Logger.Debugln(err) + acceptAttempts++ + time.Sleep(5 * time.Second) + } + + if r.StatusCode() != 200 { + a.Logger.Debugln(r.String()) + acceptAttempts++ + time.Sleep(5 * time.Second) + } else { + acceptAttempts = 0 + } + + if acceptAttempts == 0 { + a.Logger.Debugln(r.String()) + break + } else if acceptAttempts >= acceptRetries { + a.installerMsg("Unable to register salt with the RMM\nInstallation failed.", "error") + } + } + + time.Sleep(10 * time.Second) + + // sync salt modules + a.Logger.Debugln("Syncing salt modules") + syncPayload := map[string]string{"agent_id": a.AgentID} + syncAttempts := 0 + syncRetries := 20 + for { + r, err := rClient.R().SetBody(syncPayload).Patch(fmt.Sprintf("%s/api/v2/saltminion/", baseURL)) + if err != nil { + a.Logger.Debugln(err) + syncAttempts++ + time.Sleep(5 * time.Second) + } + + if r.StatusCode() != 200 { + a.Logger.Debugln(r.String()) + syncAttempts++ + time.Sleep(5 * time.Second) + } else { + syncAttempts = 0 + } + + if syncAttempts == 0 { + a.Logger.Debugln(r.String()) + break + } else if syncAttempts >= syncRetries { + a.installerMsg("Unable to sync salt modules\nInstallation failed.", "error") + } + } + + // send wmi sysinfo + a.Logger.Debugln("Getting sysinfo with WMI") + a.GetWMI() + + // remove existing services if exist + services := []string{"tacticalagent", "checkrunner"} + for _, svc := range services { + _, err := WinServiceGet(svc) + if err == nil { + a.Logger.Debugln(fmt.Sprintf("Found existing %s service. Removing", svc)) + _, _ = CMD(a.Nssm, []string{"stop", svc}, 30, false) + _, _ = CMD(a.Nssm, []string{"remove", svc, "confirm"}, 30, false) + } + } + + a.Logger.Infoln("Installing services...") + svcCommands := [8][]string{ + // winagentsvc + {"install", "tacticalagent", a.EXE, "-m", "winagentsvc"}, + {"set", "tacticalagent", "DisplayName", "Tactical RMM Agent"}, + {"set", "tacticalagent", "Description", "Tactical RMM Agent"}, + {"start", "tacticalagent"}, + //checkrunner + {"install", "checkrunner", a.EXE, "-m", "checkrunner"}, + {"set", "checkrunner", "DisplayName", "Tactical RMM Check Runner"}, + {"set", "checkrunner", "Description", "Tactical RMM Check Runner"}, + {"start", "checkrunner"}, + } + + for _, s := range svcCommands { + a.Logger.Debugln(s) + _, nssmErr := CMD(a.Nssm, s, 15, false) + if nssmErr != nil { + a.installerMsg(nssmErr.Error(), "error") + } + } + + if i.Power { + a.Logger.Infoln("Disabling sleep/hibernate...") + DisableSleepHibernate() + } + + if i.Ping { + a.Logger.Infoln("Enabling ping...") + EnablePing() + } + + if i.RDP { + a.Logger.Infoln("Enabling RDP...") + EnableRDP() + } + + a.installerMsg("Installation was successfull!\nAllow a few minutes for the agent to properly display in the RMM", "info") } func copyFile(src, dst string) error { @@ -261,3 +424,29 @@ func copyFile(src, dst string) error { } return nil } + +func (a *WindowsAgent) checkExistingAndRemove() { + installedMesh := filepath.Join(a.ProgramDir, "Mesh Agent", "MeshAgent.exe") + installedSalt := filepath.Join(a.SystemDrive, "\\salt", "uninst.exe") + agentDB := filepath.Join(a.ProgramDir, "agentdb.db") + if FileExists(installedMesh) || FileExists(installedSalt) || FileExists(agentDB) { + tacUninst := filepath.Join(a.ProgramDir, "unins000.exe") + tacUninstArgs := []string{tacUninst, "/VERYSILENT", "/SUPPRESSMSGBOXES"} + + window := w32.GetForegroundWindow() + if window != 0 { + var handle w32.HWND + msg := "Existing installation found\nClick OK to remove, then re-run the installer.\nClick Cancel to abort." + action := w32.MessageBox(handle, msg, "Tactical RMM", w32.MB_OKCANCEL|w32.MB_ICONWARNING) + if action == w32.IDOK { + _, _ = CMDShell(tacUninstArgs, "", 60, true) + w32.MessageBox(handle, "Uninstall finished", "Tactical RMM", w32.MB_OK|w32.MB_ICONINFORMATION) + } + } else { + fmt.Println("Existing installation found and must be removed before attempting to reinstall.") + fmt.Println("Run the following command to uninstall, and then re-run this installer.") + fmt.Printf("\"%s\" %s %s", tacUninstArgs[0], tacUninstArgs[1], tacUninstArgs[2]) + } + os.Exit(0) + } +} diff --git a/agent/service_windows.go b/agent/service_windows.go index 4af9a0d..5667ac7 100644 --- a/agent/service_windows.go +++ b/agent/service_windows.go @@ -29,8 +29,8 @@ type HelloPatch struct { BootTime int64 `json:"boot_time"` } -// RunAsService tacticalagent windows nssm service -func (a *WindowsAgent) RunAsService() { +// WinAgentSvc tacticalagent windows nssm service +func (a *WindowsAgent) WinAgentSvc() { a.Logger.Infoln("Agent service started") var data map[string]interface{} var sleep int diff --git a/build/onit.bmp b/build/onit.bmp new file mode 100644 index 0000000..4fc5401 Binary files /dev/null and b/build/onit.bmp differ diff --git a/build/saltcustom b/build/saltcustom new file mode 100644 index 0000000..b4e71f7 --- /dev/null +++ b/build/saltcustom @@ -0,0 +1,9 @@ +master_tries: -1 +keysize: 2048 +multiprocessing: True +output: json +grains_cache: True +grains_refresh_every: 180 +grains_cache_expiration: 86400 +enable_fqdn_grains: False +log_level: error diff --git a/build/setup-x86.iss b/build/setup-x86.iss new file mode 100644 index 0000000..35f71b0 --- /dev/null +++ b/build/setup-x86.iss @@ -0,0 +1,87 @@ +#define MyAppName "Tactical RMM Agent" +#define MyAppVersion "1.0.0" +#define MyAppPublisher "Tactical Techs" +#define MyAppURL "https://github.com/wh1te909" +#define MyAppExeName "tacticalrmm.exe" +#define NSSM "nssm-x86.exe" +#define MESHEXE "meshagent-x86.exe" +#define SALTUNINSTALL "{sd}\salt\uninst.exe" +#define SALTDIR "{sd}\salt" +#define MESHDIR "{sd}\Program Files\Mesh Agent" + +[Setup] +AppId={{0D34D278-5FAF-4159-A4A0-4E2D2C08139D} +AppName={#MyAppName} +AppVersion={#MyAppVersion} +AppVerName={#MyAppName} +AppPublisher={#MyAppPublisher} +AppPublisherURL={#MyAppURL} +AppSupportURL={#MyAppURL} +AppUpdatesURL={#MyAppURL} +DefaultDirName="{sd}\Program Files\TacticalAgent" +DisableDirPage=yes +DisableProgramGroupPage=yes +OutputBaseFilename=winagent-v{#MyAppVersion}-x86 +SetupIconFile=C:\Users\Public\Documents\rmmagent\build\onit.ico +WizardSmallImageFile=C:\Users\Public\Documents\rmmagent\build\onit.bmp +UninstallDisplayIcon={app}\{#MyAppExeName} +Compression=lzma +SolidCompression=yes +WizardStyle=modern + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" + +[Tasks] +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked + +[Files] +Source: "C:\Users\Public\Documents\rmmagent\tacticalrmm.exe"; DestDir: "{app}"; Flags: ignoreversion; BeforeInstall: StopServices; +Source: "C:\Users\Public\Documents\rmmagent\build\nssm-x86.exe"; DestDir: "{app}"; Flags: ignoreversion; +Source: "C:\Users\Public\Documents\rmmagent\build\saltcustom"; DestDir: "{app}"; Flags: ignoreversion; AfterInstall: StartServices; + +[Icons] +Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" + +[Run] +Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent runascurrentuser + +[UninstallRun] +Filename: "{app}\{#NSSM}"; Parameters: "stop tacticalagent"; RunOnceId: "stoptacagent"; +Filename: "{app}\{#NSSM}"; Parameters: "remove tacticalagent confirm"; RunOnceId: "removetacagent"; +Filename: "{app}\{#NSSM}"; Parameters: "stop checkrunner"; RunOnceId: "stopcheckrun"; +Filename: "{app}\{#NSSM}"; Parameters: "remove checkrunner confirm"; RunOnceId: "removecheckrun"; +;Filename: "{app}\{#MyAppExeName}"; Parameters: "-m cleanup"; RunOnceId: "cleanuprm"; +Filename: "{#SALTUNINSTALL}"; Parameters: "/S"; RunOnceId: "saltrm"; +Filename: "{app}\{#MESHEXE}"; Parameters: "-fulluninstall"; RunOnceId: "meshrm"; + +[UninstallDelete] +Type: filesandordirs; Name: "{app}"; +Type: filesandordirs; Name: "{#SALTDIR}"; +Type: filesandordirs; Name: "{#MESHDIR}"; + +[Code] +procedure StopServices(); +var + ResultCode: Integer; + StopTactical: string; + StopCheckrunner: string; +begin + StopTactical := ExpandConstant(' /c "{app}\{#NSSM}"' + ' stop tacticalagent && ping 127.0.0.1 -n 5'); + StopCheckrunner := ExpandConstant(' /c "{app}\{#NSSM}"' + ' stop checkrunner && ping 127.0.0.1 -n 5'); + Exec('cmd.exe', StopTactical, '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + Exec('cmd.exe', StopCheckrunner, '', SW_HIDE, ewWaitUntilTerminated, ResultCode); +end; + +procedure StartServices(); +var + ResultCode: Integer; + StartTactical: string; + StartCheckrunner: string; +begin + StartTactical := ExpandConstant(' /c "{app}\{#NSSM}"' + ' start tacticalagent && ping 127.0.0.1 -n 7'); + StartCheckrunner := ExpandConstant(' /c "{app}\{#NSSM}"' + ' start checkrunner && ping 127.0.0.1 -n 3'); + Exec('cmd.exe', StartTactical, '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + Exec('cmd.exe', StartCheckrunner, '', SW_HIDE, ewWaitUntilTerminated, ResultCode); +end; + diff --git a/build/setup.iss b/build/setup.iss new file mode 100644 index 0000000..59603df --- /dev/null +++ b/build/setup.iss @@ -0,0 +1,87 @@ +#define MyAppName "Tactical RMM Agent" +#define MyAppVersion "1.0.0" +#define MyAppPublisher "Tactical Techs" +#define MyAppURL "https://github.com/wh1te909" +#define MyAppExeName "tacticalrmm.exe" +#define NSSM "nssm.exe" +#define MESHEXE "meshagent.exe" +#define SALTUNINSTALL "{sd}\salt\uninst.exe" +#define SALTDIR "{sd}\salt" +#define MESHDIR "{sd}\Program Files\Mesh Agent" + +[Setup] +AppId={{0D34D278-5FAF-4159-A4A0-4E2D2C08139D} +AppName={#MyAppName} +AppVersion={#MyAppVersion} +AppVerName={#MyAppName} +AppPublisher={#MyAppPublisher} +AppPublisherURL={#MyAppURL} +AppSupportURL={#MyAppURL} +AppUpdatesURL={#MyAppURL} +DefaultDirName="{sd}\Program Files\TacticalAgent" +DisableDirPage=yes +DisableProgramGroupPage=yes +OutputBaseFilename=winagent-v{#MyAppVersion} +SetupIconFile=C:\Users\Public\Documents\rmmagent\build\onit.ico +WizardSmallImageFile=C:\Users\Public\Documents\rmmagent\build\onit.bmp +UninstallDisplayIcon={app}\{#MyAppExeName} +Compression=lzma +SolidCompression=yes +WizardStyle=modern + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" + +[Tasks] +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked + +[Files] +Source: "C:\Users\Public\Documents\rmmagent\tacticalrmm.exe"; DestDir: "{app}"; Flags: ignoreversion; BeforeInstall: StopServices; +Source: "C:\Users\Public\Documents\rmmagent\build\nssm.exe"; DestDir: "{app}"; Flags: ignoreversion; +Source: "C:\Users\Public\Documents\rmmagent\build\saltcustom"; DestDir: "{app}"; Flags: ignoreversion; AfterInstall: StartServices; + +[Icons] +Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" + +[Run] +Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent runascurrentuser + +[UninstallRun] +Filename: "{app}\{#NSSM}"; Parameters: "stop tacticalagent"; RunOnceId: "stoptacagent"; +Filename: "{app}\{#NSSM}"; Parameters: "remove tacticalagent confirm"; RunOnceId: "removetacagent"; +Filename: "{app}\{#NSSM}"; Parameters: "stop checkrunner"; RunOnceId: "stopcheckrun"; +Filename: "{app}\{#NSSM}"; Parameters: "remove checkrunner confirm"; RunOnceId: "removecheckrun"; +;Filename: "{app}\{#MyAppExeName}"; Parameters: "-m cleanup"; RunOnceId: "cleanuprm"; +Filename: "{#SALTUNINSTALL}"; Parameters: "/S"; RunOnceId: "saltrm"; +Filename: "{app}\{#MESHEXE}"; Parameters: "-fulluninstall"; RunOnceId: "meshrm"; + +[UninstallDelete] +Type: filesandordirs; Name: "{app}"; +Type: filesandordirs; Name: "{#SALTDIR}"; +Type: filesandordirs; Name: "{#MESHDIR}"; + +[Code] +procedure StopServices(); +var + ResultCode: Integer; + StopTactical: string; + StopCheckrunner: string; +begin + StopTactical := ExpandConstant(' /c "{app}\{#NSSM}"' + ' stop tacticalagent && ping 127.0.0.1 -n 5'); + StopCheckrunner := ExpandConstant(' /c "{app}\{#NSSM}"' + ' stop checkrunner && ping 127.0.0.1 -n 5'); + Exec('cmd.exe', StopTactical, '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + Exec('cmd.exe', StopCheckrunner, '', SW_HIDE, ewWaitUntilTerminated, ResultCode); +end; + +procedure StartServices(); +var + ResultCode: Integer; + StartTactical: string; + StartCheckrunner: string; +begin + StartTactical := ExpandConstant(' /c "{app}\{#NSSM}"' + ' start tacticalagent && ping 127.0.0.1 -n 7'); + StartCheckrunner := ExpandConstant(' /c "{app}\{#NSSM}"' + ' start checkrunner && ping 127.0.0.1 -n 3'); + Exec('cmd.exe', StartTactical, '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + Exec('cmd.exe', StartCheckrunner, '', SW_HIDE, ewWaitUntilTerminated, ResultCode); +end; + diff --git a/main.go b/main.go index 13d50ca..950c386 100644 --- a/main.go +++ b/main.go @@ -1,18 +1,11 @@ //go:generate goversioninfo -64 package main -// cross compile from linux for windows -// apt install build-essential gcc-multilib gcc-mingw-w64-x86-64 gcc-mingw-w64-i686 -// 64 bit: CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ GOOS=windows GOARCH=amd64 go build -o tacticalrmm.exe -// 32 bit: CGO_ENABLED=1 CC=i686-w64-mingw32-gcc CXX=i686-w64-mingw32-g++ GOOS=windows GOARCH=386 go build -o tacticalrmm-x86.exe - -// building 32 bit from windows from git bash -// env CGO_ENABLED=1 CC=i686-w64-mingw32-gcc CXX=i686-w64-mingw32-g++ GOARCH=386 go build -o tacticalrmm-x86.exe - import ( "flag" "fmt" "os" + "runtime" "github.com/sirupsen/logrus" "github.com/wh1te909/rmmagent/agent" @@ -35,7 +28,7 @@ func main() { siteID := flag.Int("site-id", 0, "Site ID") timeout := flag.Duration("timeout", 900, "Installer timeout (seconds)") desc := flag.String("desc", hostname, "Agent's Description") - atype := flag.String("agent-type", "server", "Server or Workstation") + atype := flag.String("agent-type", "server", "server or workstation") token := flag.String("auth", "", "Token") power := flag.Bool("power", false, "Disable sleep/hibernate") rdp := flag.Bool("rdp", false, "Enable RDP") @@ -64,7 +57,15 @@ func main() { case "checkrunner": a.CheckRunner() case "winagentsvc": - a.RunAsService() + a.WinAgentSvc() + case "runchecks": + a.RunChecks() + case "sysinfo": + a.GetWMI() + case "recoversalt": + a.RecoverSalt() + case "recovermesh": + a.RecoverMesh() case "install": log.SetOutput(os.Stdout) if *api == "" || *clientID == 0 || *siteID == 0 || *token == "" { @@ -108,6 +109,11 @@ func setupLogging(level *string, to *string) { } func installUsage() { - u := `Usage: tacticalrmm.exe -m install -api -client-id X -site-id X -auth ` - fmt.Println(u) + switch runtime.GOOS { + case "windows": + u := `Usage: tacticalrmm.exe -m install -api -client-id X -site-id X -auth ` + fmt.Println(u) + case "linux": + // todo + } }