feat: WAU-Integration + Windows Software-Inventar (v1.1.0)
Windows Agent (v1.1.0):
- Software-Inventar aus Windows-Registry (64-bit + 32-bit)
Sammelt Name, Version, Hersteller, InstallDate aller installierten Apps
Filtert SystemComponent-Einträge heraus, sortiert alphabetisch
Funktioniert unter SYSTEM-Account (kein winget nötig)
- Version auf 1.1.0 gehoben
Backend:
- customer_wau_rules Tabelle (Migration automatisch)
- WAU-Regeln pro Kunde (allow/block, winget-ID + Anzeigename)
- GET/POST/DELETE /api/v1/customers/{id}/wau/rules
- GET /api/v1/customers/{id}/wau/inventory (SQL-Aggregation aus Agenten-Daten)
- GET /api/v1/customers/{id}/wau/included (Plaintext für WAU)
- GET /api/v1/customers/{id}/wau/excluded (Plaintext für WAU)
Frontend:
- Kunden-Seite: Tab-Navigation (Agents | API-Keys | WAU)
- WAU-Tab pro Kunde: Allowlist + Blocklist mit Inline-Assign aus Inventar
Allow/Block-Buttons direkt in Inventar-Zeile, winget-ID vorgeschlagen
Installationsbefehl mit externer Backend-URL aus Systemeinstellungen
- Windows-Panel Software-Tab: Installiert-Liste aus Registry-Daten
- Windows-Panel WAU-Tab: Status (installiert/Task/letzter Lauf)
WAU installieren via MSI-Download + msiexec (synchron)
Jetzt ausführen, Log anzeigen
Gewünschten Zustand herstellen (Allowlist auf Client installieren)
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -21,16 +22,17 @@ type SystemData struct {
|
||||
}
|
||||
|
||||
type WindowsData struct {
|
||||
Hostname string `json:"hostname"`
|
||||
OSVersion string `json:"os_version"`
|
||||
OSBuild string `json:"os_build"`
|
||||
UptimeSecs int64 `json:"uptime_seconds"`
|
||||
CPU CPUInfo `json:"cpu"`
|
||||
Memory MemoryInfo `json:"memory"`
|
||||
Disks []DiskInfo `json:"disks"`
|
||||
Services []SvcInfo `json:"services,omitempty"`
|
||||
Domain string `json:"domain,omitempty"`
|
||||
LastUpdate time.Time `json:"last_update"`
|
||||
Hostname string `json:"hostname"`
|
||||
OSVersion string `json:"os_version"`
|
||||
OSBuild string `json:"os_build"`
|
||||
UptimeSecs int64 `json:"uptime_seconds"`
|
||||
CPU CPUInfo `json:"cpu"`
|
||||
Memory MemoryInfo `json:"memory"`
|
||||
Disks []DiskInfo `json:"disks"`
|
||||
Services []SvcInfo `json:"services,omitempty"`
|
||||
InstalledSoftware []SoftwareInfo `json:"installed_software,omitempty"`
|
||||
Domain string `json:"domain,omitempty"`
|
||||
LastUpdate time.Time `json:"last_update"`
|
||||
}
|
||||
|
||||
type CPUInfo struct {
|
||||
@@ -62,6 +64,13 @@ type SvcInfo struct {
|
||||
StartType string `json:"start_type"`
|
||||
}
|
||||
|
||||
type SoftwareInfo struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Publisher string `json:"publisher,omitempty"`
|
||||
InstallDate string `json:"install_date,omitempty"`
|
||||
}
|
||||
|
||||
// MEMORYSTATUSEX fuer GlobalMemoryStatusEx
|
||||
type memoryStatusEx struct {
|
||||
dwLength uint32
|
||||
@@ -111,6 +120,9 @@ func Collect() (*SystemData, error) {
|
||||
// Domain
|
||||
wd.Domain = getDomain()
|
||||
|
||||
// Installierte Software (Registry)
|
||||
wd.InstalledSoftware = getInstalledSoftware()
|
||||
|
||||
return &SystemData{Windows: wd}, nil
|
||||
}
|
||||
|
||||
@@ -359,3 +371,73 @@ func min(a, b int) int {
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// getInstalledSoftware liest installierte Software aus der Windows-Registry.
|
||||
// Funktioniert auch unter SYSTEM-Account (kein winget noetig).
|
||||
func getInstalledSoftware() []SoftwareInfo {
|
||||
var result []SoftwareInfo
|
||||
seen := make(map[string]bool)
|
||||
|
||||
regPaths := []struct {
|
||||
root registry.Key
|
||||
path string
|
||||
}{
|
||||
{registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall`},
|
||||
{registry.LOCAL_MACHINE, `SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall`},
|
||||
}
|
||||
|
||||
for _, rp := range regPaths {
|
||||
k, err := registry.OpenKey(rp.root, rp.path, registry.READ|registry.ENUMERATE_SUB_KEYS)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
subkeys, err := k.ReadSubKeyNames(-1)
|
||||
k.Close()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, subkey := range subkeys {
|
||||
sk, err := registry.OpenKey(rp.root, rp.path+`\`+subkey, registry.READ)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
name, _, _ := sk.GetStringValue("DisplayName")
|
||||
sk.Close()
|
||||
|
||||
if name == "" || seen[name] {
|
||||
continue
|
||||
}
|
||||
seen[name] = true
|
||||
|
||||
// Nochmal öffnen für weitere Felder (getrennt um Close sauber zu halten)
|
||||
sk2, err := registry.OpenKey(rp.root, rp.path+`\`+subkey, registry.READ)
|
||||
if err != nil {
|
||||
result = append(result, SoftwareInfo{Name: name})
|
||||
continue
|
||||
}
|
||||
version, _, _ := sk2.GetStringValue("DisplayVersion")
|
||||
publisher, _, _ := sk2.GetStringValue("Publisher")
|
||||
installDate, _, _ := sk2.GetStringValue("InstallDate")
|
||||
|
||||
// SystemComponent überspringen (Windows-interne Komponenten)
|
||||
sysComp, _, _ := sk2.GetIntegerValue("SystemComponent")
|
||||
sk2.Close()
|
||||
if sysComp == 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
result = append(result, SoftwareInfo{
|
||||
Name: name,
|
||||
Version: version,
|
||||
Publisher: publisher,
|
||||
InstallDate: installDate,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(result, func(i, j int) bool {
|
||||
return strings.ToLower(result[i].Name) < strings.ToLower(result[j].Name)
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
"github.com/cynfo/rmm-agent-windows/ws"
|
||||
)
|
||||
|
||||
var Version = "1.0.0"
|
||||
var Version = "1.1.0"
|
||||
|
||||
const (
|
||||
ServiceName = "RMMAgent"
|
||||
|
||||
Reference in New Issue
Block a user