Release 2.1.0
This commit is contained in:
commit
25dfa5db55
49
.github/workflows/ci.yml
vendored
Normal file
49
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
name: Run tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "*"
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- "*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Run tests
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: '1.18.3'
|
||||||
|
|
||||||
|
- name: Ensure linux agent compiles
|
||||||
|
run: |
|
||||||
|
ARCHS='amd64 386 arm64 arm'
|
||||||
|
for i in ${ARCHS}; do
|
||||||
|
env CGO_ENABLED=0 GOOS=linux GOARCH=${i} go build -ldflags "-s -w"
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Ensure windows agent compiles
|
||||||
|
run: |
|
||||||
|
ARCHS='amd64 386'
|
||||||
|
for i in ${ARCHS}; do
|
||||||
|
env CGO_ENABLED=0 GOOS=windows GOARCH=${i} go build -ldflags "-s -w"
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Ensure mac agent compiles
|
||||||
|
run: |
|
||||||
|
ARCHS='amd64 arm64'
|
||||||
|
for i in ${ARCHS}; do
|
||||||
|
env CGO_ENABLED=0 GOOS=darwin GOARCH=${i} go build -ldflags "-s -w"
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Ensure freebsd agent compiles
|
||||||
|
run: |
|
||||||
|
ARCHS='amd64 386 arm64 arm'
|
||||||
|
for i in ${ARCHS}; do
|
||||||
|
env CGO_ENABLED=0 GOOS=freebsd GOARCH=${i} go build -ldflags "-s -w"
|
||||||
|
done
|
@ -66,6 +66,9 @@ type Agent struct {
|
|||||||
Platform string
|
Platform string
|
||||||
GoArch string
|
GoArch string
|
||||||
ServiceConfig *service.Config
|
ServiceConfig *service.Config
|
||||||
|
NatsServer string
|
||||||
|
NatsProxyPath string
|
||||||
|
NatsProxyPort string
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -139,9 +142,25 @@ func New(logger *logrus.Logger, version string) *Agent {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var natsProxyPath, natsProxyPort string
|
||||||
|
if ac.NatsProxyPath == "" {
|
||||||
|
natsProxyPath = "natsws"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ac.NatsProxyPort == "" {
|
||||||
|
natsProxyPort = "443"
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if using nats standard tcp, otherwise use nats websockets by default
|
||||||
|
var natsServer string
|
||||||
|
if ac.NatsStandardPort != "" {
|
||||||
|
natsServer = fmt.Sprintf("tls://%s:%s", ac.APIURL, ac.NatsStandardPort)
|
||||||
|
} else {
|
||||||
|
natsServer = fmt.Sprintf("wss://%s:%s", ac.APIURL, natsProxyPort)
|
||||||
|
}
|
||||||
|
|
||||||
return &Agent{
|
return &Agent{
|
||||||
Hostname: info.Hostname,
|
Hostname: info.Hostname,
|
||||||
Arch: info.Architecture,
|
|
||||||
BaseURL: ac.BaseURL,
|
BaseURL: ac.BaseURL,
|
||||||
AgentID: ac.AgentID,
|
AgentID: ac.AgentID,
|
||||||
ApiURL: ac.APIURL,
|
ApiURL: ac.APIURL,
|
||||||
@ -164,6 +183,9 @@ func New(logger *logrus.Logger, version string) *Agent {
|
|||||||
Platform: runtime.GOOS,
|
Platform: runtime.GOOS,
|
||||||
GoArch: runtime.GOARCH,
|
GoArch: runtime.GOARCH,
|
||||||
ServiceConfig: svcConf,
|
ServiceConfig: svcConf,
|
||||||
|
NatsServer: natsServer,
|
||||||
|
NatsProxyPath: natsProxyPath,
|
||||||
|
NatsProxyPort: natsProxyPort,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -360,6 +382,7 @@ func (a *Agent) setupNatsOptions() []nats.Option {
|
|||||||
opts = append(opts, nats.RetryOnFailedConnect(true))
|
opts = append(opts, nats.RetryOnFailedConnect(true))
|
||||||
opts = append(opts, nats.MaxReconnects(-1))
|
opts = append(opts, nats.MaxReconnects(-1))
|
||||||
opts = append(opts, nats.ReconnectBufSize(-1))
|
opts = append(opts, nats.ReconnectBufSize(-1))
|
||||||
|
opts = append(opts, nats.ProxyPath(a.NatsProxyPath))
|
||||||
return opts
|
return opts
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -385,6 +408,7 @@ func (a *Agent) CleanupAgentUpdates() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// winagent-v* is deprecated
|
||||||
files, err := filepath.Glob("winagent-v*.exe")
|
files, err := filepath.Glob("winagent-v*.exe")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
@ -392,6 +416,13 @@ func (a *Agent) CleanupAgentUpdates() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
agents, err := filepath.Glob("tacticalagent-v*.exe")
|
||||||
|
if err == nil {
|
||||||
|
for _, f := range agents {
|
||||||
|
os.Remove(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cderr = os.Chdir(os.Getenv("TMP"))
|
cderr = os.Chdir(os.Getenv("TMP"))
|
||||||
if cderr != nil {
|
if cderr != nil {
|
||||||
a.Logger.Errorln(cderr)
|
a.Logger.Errorln(cderr)
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
//go:build !windows
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright 2022 AmidaWare LLC.
|
Copyright 2022 AmidaWare LLC.
|
||||||
|
|
||||||
@ -15,6 +18,7 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -151,6 +155,9 @@ func NewAgentConfig() *rmm.AgentConfig {
|
|||||||
Cert: viper.GetString("cert"),
|
Cert: viper.GetString("cert"),
|
||||||
Proxy: viper.GetString("proxy"),
|
Proxy: viper.GetString("proxy"),
|
||||||
CustomMeshDir: viper.GetString("meshdir"),
|
CustomMeshDir: viper.GetString("meshdir"),
|
||||||
|
NatsProxyPath: viper.GetString("natsproxypath"),
|
||||||
|
NatsProxyPort: viper.GetString("natsproxyport"),
|
||||||
|
NatsStandardPort: viper.GetString("natsstandardport"),
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
@ -244,9 +251,30 @@ func (a *Agent) AgentUpdate(url, inno, version string) {
|
|||||||
os.Chmod(f.Name(), 0755)
|
os.Chmod(f.Name(), 0755)
|
||||||
err = os.Rename(f.Name(), self)
|
err = os.Rename(f.Name(), self)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.Logger.Errorln("AgentUpdate() os.Rename():", err)
|
a.Logger.Debugln("Detected /tmp on different filesystem")
|
||||||
|
// rename does not work when src and dest are on different filesystems
|
||||||
|
// so we need to manually copy it to the same fs then rename it
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
a.Logger.Errorln("AgentUpdate() os.Getwd():", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// create a tmpfile in same fs as agent
|
||||||
|
tmpfile := filepath.Join(cwd, GenerateAgentID())
|
||||||
|
defer os.Remove(tmpfile)
|
||||||
|
a.Logger.Debugln("Copying tmpfile from", f.Name(), "to", tmpfile)
|
||||||
|
cperr := copyFile(f.Name(), tmpfile)
|
||||||
|
if cperr != nil {
|
||||||
|
a.Logger.Errorln("AgentUpdate() copyFile:", cperr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
os.Chmod(tmpfile, 0755)
|
||||||
|
rerr := os.Rename(tmpfile, self)
|
||||||
|
if rerr != nil {
|
||||||
|
a.Logger.Errorln("AgentUpdate() os.Rename():", rerr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
opts := a.NewCMDOpts()
|
opts := a.NewCMDOpts()
|
||||||
opts.Detached = true
|
opts.Detached = true
|
@ -61,6 +61,9 @@ func NewAgentConfig() *rmm.AgentConfig {
|
|||||||
cert, _, _ := k.GetStringValue("Cert")
|
cert, _, _ := k.GetStringValue("Cert")
|
||||||
proxy, _, _ := k.GetStringValue("Proxy")
|
proxy, _, _ := k.GetStringValue("Proxy")
|
||||||
customMeshDir, _, _ := k.GetStringValue("MeshDir")
|
customMeshDir, _, _ := k.GetStringValue("MeshDir")
|
||||||
|
natsProxyPath, _, _ := k.GetStringValue("NatsProxyPath")
|
||||||
|
natsProxyPort, _, _ := k.GetStringValue("NatsProxyPort")
|
||||||
|
natsStandardPort, _, _ := k.GetStringValue("NatsStandardPort")
|
||||||
|
|
||||||
return &rmm.AgentConfig{
|
return &rmm.AgentConfig{
|
||||||
BaseURL: baseurl,
|
BaseURL: baseurl,
|
||||||
@ -72,6 +75,9 @@ func NewAgentConfig() *rmm.AgentConfig {
|
|||||||
Cert: cert,
|
Cert: cert,
|
||||||
Proxy: proxy,
|
Proxy: proxy,
|
||||||
CustomMeshDir: customMeshDir,
|
CustomMeshDir: customMeshDir,
|
||||||
|
NatsProxyPath: natsProxyPath,
|
||||||
|
NatsProxyPort: natsProxyPort,
|
||||||
|
NatsStandardPort: natsStandardPort,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -635,7 +641,7 @@ func (a *Agent) AgentUninstall(code string) {
|
|||||||
func (a *Agent) addDefenderExlusions() {
|
func (a *Agent) addDefenderExlusions() {
|
||||||
code := `
|
code := `
|
||||||
Add-MpPreference -ExclusionPath 'C:\Program Files\TacticalAgent\*'
|
Add-MpPreference -ExclusionPath 'C:\Program Files\TacticalAgent\*'
|
||||||
Add-MpPreference -ExclusionPath 'C:\Windows\Temp\winagent-v*.exe'
|
Add-MpPreference -ExclusionPath 'C:\Windows\Temp\tacticalagent-v*.exe'
|
||||||
Add-MpPreference -ExclusionPath 'C:\Windows\Temp\trmm\*'
|
Add-MpPreference -ExclusionPath 'C:\Windows\Temp\trmm\*'
|
||||||
Add-MpPreference -ExclusionPath 'C:\Program Files\Mesh Agent\*'
|
Add-MpPreference -ExclusionPath 'C:\Program Files\Mesh Agent\*'
|
||||||
`
|
`
|
||||||
|
@ -12,7 +12,6 @@ https://license.tacticalrmm.com
|
|||||||
package agent
|
package agent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -78,8 +77,7 @@ func (a *Agent) NatsMessage(nc *nats.Conn, mode string) {
|
|||||||
|
|
||||||
func (a *Agent) DoNatsCheckIn() {
|
func (a *Agent) DoNatsCheckIn() {
|
||||||
opts := a.setupNatsOptions()
|
opts := a.setupNatsOptions()
|
||||||
server := fmt.Sprintf("tls://%s:4222", a.ApiURL)
|
nc, err := nats.Connect(a.NatsServer, opts...)
|
||||||
nc, err := nats.Connect(server, opts...)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.Logger.Errorln(err)
|
a.Logger.Errorln(err)
|
||||||
return
|
return
|
||||||
|
@ -82,11 +82,6 @@ func (a *Agent) Install(i *Installer) {
|
|||||||
|
|
||||||
a.Logger.Debugln("API:", i.SaltMaster)
|
a.Logger.Debugln("API:", i.SaltMaster)
|
||||||
|
|
||||||
terr := TestTCP(fmt.Sprintf("%s:4222", i.SaltMaster))
|
|
||||||
if terr != nil {
|
|
||||||
a.installerMsg(fmt.Sprintf("ERROR: Either port 4222 TCP is not open on your RMM, or nats.service is not running.\n\n%s", terr.Error()), "error", i.Silent)
|
|
||||||
}
|
|
||||||
|
|
||||||
baseURL := u.Scheme + "://" + u.Host
|
baseURL := u.Scheme + "://" + u.Host
|
||||||
a.Logger.Debugln("Base URL:", baseURL)
|
a.Logger.Debugln("Base URL:", baseURL)
|
||||||
|
|
||||||
@ -138,14 +133,6 @@ func (a *Agent) Install(i *Installer) {
|
|||||||
rClient.SetProxy(i.Proxy)
|
rClient.SetProxy(i.Proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
var arch string
|
|
||||||
switch a.Arch {
|
|
||||||
case "x86_64":
|
|
||||||
arch = "64"
|
|
||||||
case "x86":
|
|
||||||
arch = "32"
|
|
||||||
}
|
|
||||||
|
|
||||||
var installerMeshSystemEXE string
|
var installerMeshSystemEXE string
|
||||||
if len(i.MeshDir) > 0 {
|
if len(i.MeshDir) > 0 {
|
||||||
installerMeshSystemEXE = filepath.Join(i.MeshDir, "MeshAgent.exe")
|
installerMeshSystemEXE = filepath.Join(i.MeshDir, "MeshAgent.exe")
|
||||||
@ -159,7 +146,7 @@ func (a *Agent) Install(i *Installer) {
|
|||||||
mesh := filepath.Join(a.ProgramDir, a.MeshInstaller)
|
mesh := filepath.Join(a.ProgramDir, a.MeshInstaller)
|
||||||
if i.LocalMesh == "" {
|
if i.LocalMesh == "" {
|
||||||
a.Logger.Infoln("Downloading mesh agent...")
|
a.Logger.Infoln("Downloading mesh agent...")
|
||||||
payload := map[string]string{"arch": arch, "plat": a.Platform}
|
payload := map[string]string{"goarch": a.GoArch, "plat": a.Platform}
|
||||||
r, err := rClient.R().SetBody(payload).SetOutput(mesh).Post(fmt.Sprintf("%s/api/v3/meshexe/", baseURL))
|
r, err := rClient.R().SetBody(payload).SetOutput(mesh).Post(fmt.Sprintf("%s/api/v3/meshexe/", baseURL))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.installerMsg(fmt.Sprintf("Failed to download mesh agent: %s", err.Error()), "error", i.Silent)
|
a.installerMsg(fmt.Sprintf("Failed to download mesh agent: %s", err.Error()), "error", i.Silent)
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
//go:build !windows
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright 2022 AmidaWare LLC.
|
Copyright 2022 AmidaWare LLC.
|
||||||
|
|
@ -64,9 +64,16 @@ func (a *Agent) KillHungUpdates() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// winagent-v* is deprecated
|
||||||
if strings.Contains(p.Exe, "winagent-v") {
|
if strings.Contains(p.Exe, "winagent-v") {
|
||||||
a.Logger.Debugln("killing process", p.Exe)
|
a.Logger.Debugln("killing process", p.Exe)
|
||||||
KillProc(int32(p.PID))
|
KillProc(int32(p.PID))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.Contains(p.Exe, "tacticalagent-v") {
|
||||||
|
a.Logger.Debugln("killing process", p.Exe)
|
||||||
|
KillProc(int32(p.PID))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,8 +54,7 @@ func (a *Agent) RunRPC() {
|
|||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
opts := a.setupNatsOptions()
|
opts := a.setupNatsOptions()
|
||||||
server := fmt.Sprintf("tls://%s:4222", a.ApiURL)
|
nc, err := nats.Connect(a.NatsServer, opts...)
|
||||||
nc, err := nats.Connect(server, opts...)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.Logger.Fatalln("RunRPC() nats.Connect()", err)
|
a.Logger.Fatalln("RunRPC() nats.Connect()", err)
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@ https://license.tacticalrmm.com
|
|||||||
package agent
|
package agent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -38,8 +37,7 @@ func (a *Agent) AgentSvc() {
|
|||||||
time.Sleep(time.Duration(sleepDelay) * time.Second)
|
time.Sleep(time.Duration(sleepDelay) * time.Second)
|
||||||
|
|
||||||
opts := a.setupNatsOptions()
|
opts := a.setupNatsOptions()
|
||||||
server := fmt.Sprintf("tls://%s:4222", a.ApiURL)
|
nc, err := nats.Connect(a.NatsServer, opts...)
|
||||||
nc, err := nats.Connect(server, opts...)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.Logger.Fatalln("AgentSvc() nats.Connect()", err)
|
a.Logger.Fatalln("AgentSvc() nats.Connect()", err)
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<assemblyIdentity
|
<assemblyIdentity
|
||||||
type="win32"
|
type="win32"
|
||||||
name="TacticalRMM"
|
name="TacticalRMM"
|
||||||
version="2.0.4.0"
|
version="2.1.0.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.4"
|
#define MyAppVersion "2.1.0"
|
||||||
#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"
|
||||||
|
4
main.go
4
main.go
@ -25,7 +25,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
version = "2.0.4"
|
version = "2.1.0"
|
||||||
log = logrus.New()
|
log = logrus.New()
|
||||||
logFile *os.File
|
logFile *os.File
|
||||||
)
|
)
|
||||||
@ -197,6 +197,6 @@ func installUsage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateUsage() {
|
func updateUsage() {
|
||||||
u := `Usage: tacticalrmm.exe -m update -updateurl https://example.com/winagent-vX.X.X.exe -inno winagent-vX.X.X.exe -updatever 1.1.1`
|
u := `Usage: tacticalrmm.exe -m update -updateurl https://example.com/tacticalagent-vX.X.X.exe -inno tacticalagent-vX.X.X.exe -updatever 1.1.1`
|
||||||
fmt.Println(u)
|
fmt.Println(u)
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,9 @@ type AgentConfig struct {
|
|||||||
Cert string
|
Cert string
|
||||||
Proxy string
|
Proxy string
|
||||||
CustomMeshDir string
|
CustomMeshDir string
|
||||||
|
NatsProxyPath string
|
||||||
|
NatsProxyPort string
|
||||||
|
NatsStandardPort string
|
||||||
}
|
}
|
||||||
|
|
||||||
type RunScriptResp struct {
|
type RunScriptResp struct {
|
||||||
|
@ -2,14 +2,14 @@
|
|||||||
"FixedFileInfo": {
|
"FixedFileInfo": {
|
||||||
"FileVersion": {
|
"FileVersion": {
|
||||||
"Major": 2,
|
"Major": 2,
|
||||||
"Minor": 0,
|
"Minor": 1,
|
||||||
"Patch": 4,
|
"Patch": 0,
|
||||||
"Build": 0
|
"Build": 0
|
||||||
},
|
},
|
||||||
"ProductVersion": {
|
"ProductVersion": {
|
||||||
"Major": 2,
|
"Major": 2,
|
||||||
"Minor": 0,
|
"Minor": 1,
|
||||||
"Patch": 4,
|
"Patch": 0,
|
||||||
"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.4.0",
|
"FileVersion": "v2.1.0.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.4.0",
|
"ProductVersion": "v2.1.0.0",
|
||||||
"SpecialBuild": ""
|
"SpecialBuild": ""
|
||||||
},
|
},
|
||||||
"VarFileInfo": {
|
"VarFileInfo": {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user