Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
072bd8096c | ||
![]() |
de5d4871a1 | ||
![]() |
a2f9b047a8 | ||
![]() |
fb6402f756 | ||
![]() |
6409f214a7 | ||
![]() |
7509389cfc | ||
![]() |
e8f11a852e | ||
![]() |
5dbe513fe3 | ||
![]() |
92af9b5c67 | ||
![]() |
c57c5a0cbf | ||
![]() |
118608999c |
@@ -133,11 +133,12 @@ func NewAgentConfig() *rmm.AgentConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *Agent) RunScript(code string, shell string, args []string, timeout int) (stdout, stderr string, exitcode int, e error) {
|
func (a *Agent) RunScript(code string, shell string, args []string, timeout int) (stdout, stderr string, exitcode int, e error) {
|
||||||
|
code = removeWinNewLines(code)
|
||||||
content := []byte(code)
|
content := []byte(code)
|
||||||
|
|
||||||
f, err := os.CreateTemp("", "trmm")
|
f, err := createTmpFile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.Logger.Errorln(err)
|
a.Logger.Errorln("RunScript createTmpFile()", err)
|
||||||
return "", err.Error(), 85, err
|
return "", err.Error(), 85, err
|
||||||
}
|
}
|
||||||
defer os.Remove(f.Name())
|
defer os.Remove(f.Name())
|
||||||
@@ -186,9 +187,9 @@ func (a *Agent) AgentUpdate(url, inno, version string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := os.CreateTemp("", "")
|
f, err := createTmpFile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.Logger.Errorln("AgentUpdate()", err)
|
a.Logger.Errorln("AgentUpdate createTmpFile()", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer os.Remove(f.Name())
|
defer os.Remove(f.Name())
|
||||||
@@ -231,9 +232,9 @@ func (a *Agent) AgentUpdate(url, inno, version string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *Agent) AgentUninstall(code string) {
|
func (a *Agent) AgentUninstall(code string) {
|
||||||
f, err := os.CreateTemp("", "trmm")
|
f, err := createTmpFile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.Logger.Errorln("AgentUninstall CreateTemp:", err)
|
a.Logger.Errorln("AgentUninstall createTmpFile():", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,6 +254,12 @@ func (a *Agent) NixMeshNodeID() string {
|
|||||||
var meshNodeID string
|
var meshNodeID string
|
||||||
meshSuccess := false
|
meshSuccess := false
|
||||||
a.Logger.Debugln("Getting mesh node id")
|
a.Logger.Debugln("Getting mesh node id")
|
||||||
|
|
||||||
|
if !trmm.FileExists(a.MeshSystemEXE) {
|
||||||
|
a.Logger.Debugln(a.MeshSystemEXE, "does not exist. Skipping.")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
opts := a.NewCMDOpts()
|
opts := a.NewCMDOpts()
|
||||||
opts.IsExecutable = true
|
opts.IsExecutable = true
|
||||||
opts.Shell = a.MeshSystemEXE
|
opts.Shell = a.MeshSystemEXE
|
||||||
@@ -261,11 +268,12 @@ func (a *Agent) NixMeshNodeID() string {
|
|||||||
for !meshSuccess {
|
for !meshSuccess {
|
||||||
out := a.CmdV2(opts)
|
out := a.CmdV2(opts)
|
||||||
meshNodeID = out.Stdout
|
meshNodeID = out.Stdout
|
||||||
|
a.Logger.Debugln("Stdout:", out.Stdout)
|
||||||
|
a.Logger.Debugln("Stderr:", out.Stderr)
|
||||||
if meshNodeID == "" {
|
if meshNodeID == "" {
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
continue
|
continue
|
||||||
} else if strings.Contains(strings.ToLower(meshNodeID), "graphical version") || strings.Contains(strings.ToLower(meshNodeID), "zenity") {
|
} else if strings.Contains(strings.ToLower(meshNodeID), "graphical version") || strings.Contains(strings.ToLower(meshNodeID), "zenity") {
|
||||||
a.Logger.Debugln(out.Stdout)
|
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -385,6 +393,9 @@ func (a *Agent) GetWMIInfo() map[string]interface{} {
|
|||||||
if makeModel != "" && (wmiInfo["make_model"] == "" || wmiInfo["make_model"] == "unknown unknown") {
|
if makeModel != "" && (wmiInfo["make_model"] == "" || wmiInfo["make_model"] == "unknown unknown") {
|
||||||
wmiInfo["make_model"] = makeModel
|
wmiInfo["make_model"] = makeModel
|
||||||
}
|
}
|
||||||
|
if len(gpus) == 1 && gpus[0] == "unknown unknown" {
|
||||||
|
wmiInfo["gpus"] = ""
|
||||||
|
}
|
||||||
|
|
||||||
return wmiInfo
|
return wmiInfo
|
||||||
}
|
}
|
||||||
|
@@ -159,6 +159,7 @@ func (a *Agent) RunChecks(force bool) error {
|
|||||||
|
|
||||||
type ScriptCheckResult struct {
|
type ScriptCheckResult struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
|
AgentID string `json:"agent_id"`
|
||||||
Stdout string `json:"stdout"`
|
Stdout string `json:"stdout"`
|
||||||
Stderr string `json:"stderr"`
|
Stderr string `json:"stderr"`
|
||||||
Retcode int `json:"retcode"`
|
Retcode int `json:"retcode"`
|
||||||
@@ -172,6 +173,7 @@ func (a *Agent) ScriptCheck(data rmm.Check, r *resty.Client) {
|
|||||||
|
|
||||||
payload := ScriptCheckResult{
|
payload := ScriptCheckResult{
|
||||||
ID: data.CheckPK,
|
ID: data.CheckPK,
|
||||||
|
AgentID: a.AgentID,
|
||||||
Stdout: stdout,
|
Stdout: stdout,
|
||||||
Stderr: stderr,
|
Stderr: stderr,
|
||||||
Retcode: retcode,
|
Retcode: retcode,
|
||||||
@@ -193,6 +195,7 @@ func (a *Agent) SendDiskCheckResult(payload DiskCheckResult, r *resty.Client) {
|
|||||||
|
|
||||||
type DiskCheckResult struct {
|
type DiskCheckResult struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
|
AgentID string `json:"agent_id"`
|
||||||
MoreInfo string `json:"more_info"`
|
MoreInfo string `json:"more_info"`
|
||||||
PercentUsed float64 `json:"percent_used"`
|
PercentUsed float64 `json:"percent_used"`
|
||||||
Exists bool `json:"exists"`
|
Exists bool `json:"exists"`
|
||||||
@@ -201,6 +204,7 @@ type DiskCheckResult struct {
|
|||||||
// DiskCheck checks disk usage
|
// DiskCheck checks disk usage
|
||||||
func (a *Agent) DiskCheck(data rmm.Check) (payload DiskCheckResult) {
|
func (a *Agent) DiskCheck(data rmm.Check) (payload DiskCheckResult) {
|
||||||
payload.ID = data.CheckPK
|
payload.ID = data.CheckPK
|
||||||
|
payload.AgentID = a.AgentID
|
||||||
|
|
||||||
usage, err := disk.Usage(data.Disk)
|
usage, err := disk.Usage(data.Disk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -217,13 +221,14 @@ func (a *Agent) DiskCheck(data rmm.Check) (payload DiskCheckResult) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CPUMemResult struct {
|
type CPUMemResult struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
Percent int `json:"percent"`
|
AgentID string `json:"agent_id"`
|
||||||
|
Percent int `json:"percent"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CPULoadCheck checks avg cpu load
|
// CPULoadCheck checks avg cpu load
|
||||||
func (a *Agent) CPULoadCheck(data rmm.Check, r *resty.Client) {
|
func (a *Agent) CPULoadCheck(data rmm.Check, r *resty.Client) {
|
||||||
payload := CPUMemResult{ID: data.CheckPK, Percent: a.GetCPULoadAvg()}
|
payload := CPUMemResult{ID: data.CheckPK, AgentID: a.AgentID, Percent: a.GetCPULoadAvg()}
|
||||||
_, err := r.R().SetBody(payload).Patch("/api/v3/checkrunner/")
|
_, err := r.R().SetBody(payload).Patch("/api/v3/checkrunner/")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.Logger.Debugln(err)
|
a.Logger.Debugln(err)
|
||||||
@@ -236,7 +241,7 @@ func (a *Agent) MemCheck(data rmm.Check, r *resty.Client) {
|
|||||||
mem, _ := host.Memory()
|
mem, _ := host.Memory()
|
||||||
percent := (float64(mem.Used) / float64(mem.Total)) * 100
|
percent := (float64(mem.Used) / float64(mem.Total)) * 100
|
||||||
|
|
||||||
payload := CPUMemResult{ID: data.CheckPK, Percent: int(math.Round(percent))}
|
payload := CPUMemResult{ID: data.CheckPK, AgentID: a.AgentID, Percent: int(math.Round(percent))}
|
||||||
_, err := r.R().SetBody(payload).Patch("/api/v3/checkrunner/")
|
_, err := r.R().SetBody(payload).Patch("/api/v3/checkrunner/")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.Logger.Debugln(err)
|
a.Logger.Debugln(err)
|
||||||
@@ -244,8 +249,9 @@ func (a *Agent) MemCheck(data rmm.Check, r *resty.Client) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type EventLogCheckResult struct {
|
type EventLogCheckResult struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
Log []rmm.EventLogMsg `json:"log"`
|
AgentID string `json:"agent_id"`
|
||||||
|
Log []rmm.EventLogMsg `json:"log"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Agent) EventLogCheck(data rmm.Check, r *resty.Client) {
|
func (a *Agent) EventLogCheck(data rmm.Check, r *resty.Client) {
|
||||||
@@ -299,7 +305,7 @@ func (a *Agent) EventLogCheck(data rmm.Check, r *resty.Client) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
payload := EventLogCheckResult{ID: data.CheckPK, Log: log}
|
payload := EventLogCheckResult{ID: data.CheckPK, AgentID: a.AgentID, Log: log}
|
||||||
_, err := r.R().SetBody(payload).Patch("/api/v3/checkrunner/")
|
_, err := r.R().SetBody(payload).Patch("/api/v3/checkrunner/")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.Logger.Debugln(err)
|
a.Logger.Debugln(err)
|
||||||
@@ -315,6 +321,7 @@ func (a *Agent) SendPingCheckResult(payload rmm.PingCheckResponse, r *resty.Clie
|
|||||||
|
|
||||||
func (a *Agent) PingCheck(data rmm.Check) (payload rmm.PingCheckResponse) {
|
func (a *Agent) PingCheck(data rmm.Check) (payload rmm.PingCheckResponse) {
|
||||||
payload.ID = data.CheckPK
|
payload.ID = data.CheckPK
|
||||||
|
payload.AgentID = a.AgentID
|
||||||
|
|
||||||
out, err := DoPing(data.IP)
|
out, err := DoPing(data.IP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -331,6 +338,7 @@ func (a *Agent) PingCheck(data rmm.Check) (payload rmm.PingCheckResponse) {
|
|||||||
|
|
||||||
type WinSvcCheckResult struct {
|
type WinSvcCheckResult struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
|
AgentID string `json:"agent_id"`
|
||||||
MoreInfo string `json:"more_info"`
|
MoreInfo string `json:"more_info"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
}
|
}
|
||||||
@@ -344,6 +352,7 @@ func (a *Agent) SendWinSvcCheckResult(payload WinSvcCheckResult, r *resty.Client
|
|||||||
|
|
||||||
func (a *Agent) WinSvcCheck(data rmm.Check) (payload WinSvcCheckResult) {
|
func (a *Agent) WinSvcCheck(data rmm.Check) (payload WinSvcCheckResult) {
|
||||||
payload.ID = data.CheckPK
|
payload.ID = data.CheckPK
|
||||||
|
payload.AgentID = a.AgentID
|
||||||
|
|
||||||
status, err := GetServiceStatus(data.ServiceName)
|
status, err := GetServiceStatus(data.ServiceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -59,7 +59,7 @@ func (a *Agent) InstallChoco() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *Agent) InstallWithChoco(name string) (string, error) {
|
func (a *Agent) InstallWithChoco(name string) (string, error) {
|
||||||
out, err := CMD("choco.exe", []string{"install", name, "--yes", "--force", "--force-dependencies"}, 1200, false)
|
out, err := CMD("choco.exe", []string{"install", name, "--yes", "--force", "--force-dependencies", "--no-progress"}, 1200, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.Logger.Errorln(err)
|
a.Logger.Errorln(err)
|
||||||
return err.Error(), err
|
return err.Error(), err
|
||||||
|
@@ -49,7 +49,7 @@ func (a *Agent) AgentSvc() {
|
|||||||
time.Sleep(time.Duration(randRange(100, 400)) * time.Millisecond)
|
time.Sleep(time.Duration(randRange(100, 400)) * time.Millisecond)
|
||||||
}
|
}
|
||||||
|
|
||||||
a.SyncMeshNodeID()
|
go a.SyncMeshNodeID()
|
||||||
|
|
||||||
time.Sleep(time.Duration(randRange(1, 3)) * time.Second)
|
time.Sleep(time.Duration(randRange(1, 3)) * time.Second)
|
||||||
a.AgentStartup()
|
a.AgentStartup()
|
||||||
|
@@ -298,3 +298,23 @@ func randRange(min, max int) int {
|
|||||||
func randomCheckDelay() {
|
func randomCheckDelay() {
|
||||||
time.Sleep(time.Duration(randRange(300, 950)) * time.Millisecond)
|
time.Sleep(time.Duration(randRange(300, 950)) * time.Millisecond)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func removeWinNewLines(s string) string {
|
||||||
|
return strings.ReplaceAll(s, "\r\n", "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTmpFile() (*os.File, error) {
|
||||||
|
var f *os.File
|
||||||
|
f, err := os.CreateTemp("", "trmm")
|
||||||
|
if err != nil {
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return f, err
|
||||||
|
}
|
||||||
|
f, err = os.CreateTemp(cwd, "trmm")
|
||||||
|
if err != nil {
|
||||||
|
return f, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
<assemblyIdentity
|
<assemblyIdentity
|
||||||
type="win32"
|
type="win32"
|
||||||
name="TacticalRMM"
|
name="TacticalRMM"
|
||||||
version="2.0.0.0"
|
version="2.0.3.0"
|
||||||
processorArchitecture="*"/>
|
processorArchitecture="*"/>
|
||||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
<security>
|
<security>
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
#define MyAppName "Tactical RMM Agent"
|
#define MyAppName "Tactical RMM Agent"
|
||||||
#define MyAppVersion "2.0.0"
|
#define MyAppVersion "2.0.3"
|
||||||
#define MyAppPublisher "AmidaWare LLC"
|
#define MyAppPublisher "AmidaWare LLC"
|
||||||
#define MyAppURL "https://github.com/amidaware"
|
#define MyAppURL "https://github.com/amidaware"
|
||||||
#define MyAppExeName "tacticalrmm.exe"
|
#define MyAppExeName "tacticalrmm.exe"
|
||||||
|
2
go.mod
2
go.mod
@@ -11,7 +11,7 @@ require (
|
|||||||
github.com/gonutz/w32/v2 v2.4.0
|
github.com/gonutz/w32/v2 v2.4.0
|
||||||
github.com/iamacarpet/go-win64api v0.0.0-20211130162011-82e31fe23f80
|
github.com/iamacarpet/go-win64api v0.0.0-20211130162011-82e31fe23f80
|
||||||
github.com/nats-io/nats-server/v2 v2.4.0 // indirect
|
github.com/nats-io/nats-server/v2 v2.4.0 // indirect
|
||||||
github.com/nats-io/nats.go v1.13.0
|
github.com/nats-io/nats.go v1.14.0
|
||||||
github.com/rickb777/date v1.15.3
|
github.com/rickb777/date v1.15.3
|
||||||
github.com/shirou/gopsutil/v3 v3.22.2
|
github.com/shirou/gopsutil/v3 v3.22.2
|
||||||
github.com/sirupsen/logrus v1.8.1
|
github.com/sirupsen/logrus v1.8.1
|
||||||
|
2
go.sum
2
go.sum
@@ -348,6 +348,8 @@ github.com/nats-io/nats-server/v2 v2.4.0/go.mod h1:TUAhMFYh1VISyY/D4WKJUMuGHg8yH
|
|||||||
github.com/nats-io/nats.go v1.12.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w=
|
github.com/nats-io/nats.go v1.12.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w=
|
||||||
github.com/nats-io/nats.go v1.13.0 h1:LvYqRB5epIzZWQp6lmeltOOZNLqCvm4b+qfvzZO03HE=
|
github.com/nats-io/nats.go v1.13.0 h1:LvYqRB5epIzZWQp6lmeltOOZNLqCvm4b+qfvzZO03HE=
|
||||||
github.com/nats-io/nats.go v1.13.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w=
|
github.com/nats-io/nats.go v1.13.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w=
|
||||||
|
github.com/nats-io/nats.go v1.14.0 h1:/QLCss4vQ6wvDpbqXucsVRDi13tFIR6kTdau+nXzKJw=
|
||||||
|
github.com/nats-io/nats.go v1.14.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w=
|
||||||
github.com/nats-io/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s=
|
github.com/nats-io/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s=
|
||||||
github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8=
|
github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8=
|
||||||
github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4=
|
github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4=
|
||||||
|
2
main.go
2
main.go
@@ -25,7 +25,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
version = "2.0.0"
|
version = "2.0.3"
|
||||||
log = logrus.New()
|
log = logrus.New()
|
||||||
logFile *os.File
|
logFile *os.File
|
||||||
)
|
)
|
||||||
|
@@ -69,9 +69,10 @@ type AgentInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type PingCheckResponse struct {
|
type PingCheckResponse struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
Status string `json:"status"`
|
AgentID string `json:"agent_id"`
|
||||||
Output string `json:"output"`
|
Status string `json:"status"`
|
||||||
|
Output string `json:"output"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type WinUpdateResult struct {
|
type WinUpdateResult struct {
|
||||||
|
@@ -3,13 +3,13 @@
|
|||||||
"FileVersion": {
|
"FileVersion": {
|
||||||
"Major": 2,
|
"Major": 2,
|
||||||
"Minor": 0,
|
"Minor": 0,
|
||||||
"Patch": 0,
|
"Patch": 3,
|
||||||
"Build": 0
|
"Build": 0
|
||||||
},
|
},
|
||||||
"ProductVersion": {
|
"ProductVersion": {
|
||||||
"Major": 2,
|
"Major": 2,
|
||||||
"Minor": 0,
|
"Minor": 0,
|
||||||
"Patch": 0,
|
"Patch": 3,
|
||||||
"Build": 0
|
"Build": 0
|
||||||
},
|
},
|
||||||
"FileFlagsMask": "3f",
|
"FileFlagsMask": "3f",
|
||||||
@@ -22,14 +22,14 @@
|
|||||||
"Comments": "",
|
"Comments": "",
|
||||||
"CompanyName": "AmidaWare LLC",
|
"CompanyName": "AmidaWare LLC",
|
||||||
"FileDescription": "Tactical RMM Agent",
|
"FileDescription": "Tactical RMM Agent",
|
||||||
"FileVersion": "v2.0.0.0",
|
"FileVersion": "v2.0.3.0",
|
||||||
"InternalName": "tacticalrmm.exe",
|
"InternalName": "tacticalrmm.exe",
|
||||||
"LegalCopyright": "Copyright (c) 2022 AmidaWare LLC",
|
"LegalCopyright": "Copyright (c) 2022 AmidaWare LLC",
|
||||||
"LegalTrademarks": "",
|
"LegalTrademarks": "",
|
||||||
"OriginalFilename": "tacticalrmm.exe",
|
"OriginalFilename": "tacticalrmm.exe",
|
||||||
"PrivateBuild": "",
|
"PrivateBuild": "",
|
||||||
"ProductName": "Tactical RMM Agent",
|
"ProductName": "Tactical RMM Agent",
|
||||||
"ProductVersion": "v2.0.0.0",
|
"ProductVersion": "v2.0.3.0",
|
||||||
"SpecialBuild": ""
|
"SpecialBuild": ""
|
||||||
},
|
},
|
||||||
"VarFileInfo": {
|
"VarFileInfo": {
|
||||||
|
Reference in New Issue
Block a user