- Added system info reporting to agent main loop - Updated README with current project status and screenshots - Fixed a few workflow quirks
375 lines
12 KiB
Go
375 lines
12 KiB
Go
//go:build windows
|
|
// +build windows
|
|
|
|
package system
|
|
|
|
import (
|
|
"fmt"
|
|
"os/exec"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// getWindowsInfo gets detailed Windows version information using WMI
|
|
func getWindowsInfo() string {
|
|
// Try using wmic for detailed Windows version info
|
|
if cmd, err := exec.LookPath("wmic"); err == nil {
|
|
if data, err := exec.Command(cmd, "os", "get", "Caption,Version,BuildNumber,SKU").Output(); err == nil {
|
|
lines := strings.Split(string(data), "\n")
|
|
for _, line := range lines {
|
|
if strings.Contains(line, "Microsoft Windows") {
|
|
// Clean up the output
|
|
line = strings.TrimSpace(line)
|
|
// Remove extra spaces
|
|
for strings.Contains(line, " ") {
|
|
line = strings.ReplaceAll(line, " ", " ")
|
|
}
|
|
return line
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fallback to basic version detection
|
|
return "Windows"
|
|
}
|
|
|
|
// getWindowsCPUInfo gets detailed CPU information using WMI
|
|
func getWindowsCPUInfo() (*CPUInfo, error) {
|
|
cpu := &CPUInfo{}
|
|
|
|
// Try using wmic for CPU information
|
|
if cmd, err := exec.LookPath("wmic"); err == nil {
|
|
// Get CPU name with better error handling
|
|
if data, err := exec.Command(cmd, "cpu", "get", "Name").Output(); err == nil {
|
|
output := string(data)
|
|
fmt.Printf("WMIC CPU Name output: '%s'\n", output) // Debug logging
|
|
lines := strings.Split(output, "\n")
|
|
for _, line := range lines {
|
|
line = strings.TrimSpace(line)
|
|
if line != "" && !strings.Contains(line, "Name") {
|
|
cpu.ModelName = line
|
|
fmt.Printf("Found CPU model: '%s'\n", line) // Debug logging
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
fmt.Printf("Failed to get CPU name via wmic: %v\n", err)
|
|
}
|
|
|
|
// Get number of cores
|
|
if data, err := exec.Command(cmd, "cpu", "get", "NumberOfCores").Output(); err == nil {
|
|
output := string(data)
|
|
fmt.Printf("WMIC CPU Cores output: '%s'\n", output) // Debug logging
|
|
lines := strings.Split(output, "\n")
|
|
for _, line := range lines {
|
|
line = strings.TrimSpace(line)
|
|
if line != "" && !strings.Contains(line, "NumberOfCores") {
|
|
if cores, err := strconv.Atoi(line); err == nil {
|
|
cpu.Cores = cores
|
|
fmt.Printf("Found CPU cores: %d\n", cores) // Debug logging
|
|
}
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
fmt.Printf("Failed to get CPU cores via wmic: %v\n", err)
|
|
}
|
|
|
|
// Get number of logical processors (threads)
|
|
if data, err := exec.Command(cmd, "cpu", "get", "NumberOfLogicalProcessors").Output(); err == nil {
|
|
output := string(data)
|
|
fmt.Printf("WMIC CPU Threads output: '%s'\n", output) // Debug logging
|
|
lines := strings.Split(output, "\n")
|
|
for _, line := range lines {
|
|
line = strings.TrimSpace(line)
|
|
if line != "" && !strings.Contains(line, "NumberOfLogicalProcessors") {
|
|
if threads, err := strconv.Atoi(line); err == nil {
|
|
cpu.Threads = threads
|
|
fmt.Printf("Found CPU threads: %d\n", threads) // Debug logging
|
|
}
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
fmt.Printf("Failed to get CPU threads via wmic: %v\n", err)
|
|
}
|
|
|
|
// If we couldn't get threads, assume it's equal to cores
|
|
if cpu.Threads == 0 {
|
|
cpu.Threads = cpu.Cores
|
|
}
|
|
} else {
|
|
fmt.Printf("WMIC command not found, unable to get CPU info\n")
|
|
}
|
|
|
|
// Fallback to PowerShell if wmic failed
|
|
if cpu.ModelName == "" {
|
|
fmt.Printf("Attempting PowerShell fallback for CPU info...\n")
|
|
if psCmd, err := exec.LookPath("powershell"); err == nil {
|
|
// Get CPU info via PowerShell
|
|
if data, err := exec.Command(psCmd, "-Command", "Get-CimInstance -ClassName Win32_Processor | Select-Object -First 1 Name,NumberOfCores,NumberOfLogicalProcessors | ConvertTo-Json").Output(); err == nil {
|
|
fmt.Printf("PowerShell CPU output: '%s'\n", string(data))
|
|
// Try to parse JSON output (simplified)
|
|
output := string(data)
|
|
if strings.Contains(output, "Name") {
|
|
// Simple string extraction as fallback
|
|
lines := strings.Split(output, "\n")
|
|
for _, line := range lines {
|
|
if strings.Contains(line, "Name") && strings.Contains(line, ":") {
|
|
parts := strings.Split(line, ":")
|
|
if len(parts) >= 2 {
|
|
cpu.ModelName = strings.TrimSpace(strings.Trim(parts[1], " ,\""))
|
|
fmt.Printf("Found CPU via PowerShell: '%s'\n", cpu.ModelName)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
fmt.Printf("PowerShell CPU info failed: %v\n", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
return cpu, nil
|
|
}
|
|
|
|
// getWindowsMemoryInfo gets memory information using WMI
|
|
func getWindowsMemoryInfo() (*MemoryInfo, error) {
|
|
mem := &MemoryInfo{}
|
|
|
|
if cmd, err := exec.LookPath("wmic"); err == nil {
|
|
// Get total memory in bytes
|
|
if data, err := exec.Command(cmd, "computersystem", "get", "TotalPhysicalMemory").Output(); err == nil {
|
|
lines := strings.Split(string(data), "\n")
|
|
for _, line := range lines {
|
|
if strings.TrimSpace(line) != "" && !strings.Contains(line, "TotalPhysicalMemory") {
|
|
if total, err := strconv.ParseUint(strings.TrimSpace(line), 10, 64); err == nil {
|
|
mem.Total = total
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get available memory using PowerShell (more accurate than wmic for available memory)
|
|
if cmd, err := exec.LookPath("powershell"); err == nil {
|
|
if data, err := exec.Command(cmd, "-Command",
|
|
"(Get-Counter '\\Memory\\Available MBytes').CounterSamples.CookedValue").Output(); err == nil {
|
|
if available, err := strconv.ParseFloat(strings.TrimSpace(string(data)), 64); err == nil {
|
|
mem.Available = uint64(available * 1024 * 1024) // Convert MB to bytes
|
|
}
|
|
}
|
|
} else {
|
|
// Fallback: estimate available memory (this is not very accurate)
|
|
mem.Available = mem.Total / 4 // Rough estimate: 25% available
|
|
}
|
|
|
|
mem.Used = mem.Total - mem.Available
|
|
if mem.Total > 0 {
|
|
mem.UsedPercent = float64(mem.Used) / float64(mem.Total) * 100
|
|
}
|
|
}
|
|
|
|
return mem, nil
|
|
}
|
|
|
|
// getWindowsDiskInfo gets disk information using WMI
|
|
func getWindowsDiskInfo() ([]DiskInfo, error) {
|
|
var disks []DiskInfo
|
|
|
|
if cmd, err := exec.LookPath("wmic"); err == nil {
|
|
// Get logical disk information
|
|
if data, err := exec.Command(cmd, "logicaldisk", "get", "DeviceID,Size,FreeSpace,FileSystem").Output(); err == nil {
|
|
lines := strings.Split(string(data), "\n")
|
|
for _, line := range lines {
|
|
if strings.TrimSpace(line) != "" && !strings.Contains(line, "DeviceID") {
|
|
fields := strings.Fields(line)
|
|
if len(fields) >= 4 {
|
|
disk := DiskInfo{
|
|
Mountpoint: strings.TrimSpace(fields[0]),
|
|
Filesystem: strings.TrimSpace(fields[3]),
|
|
}
|
|
|
|
// Parse sizes (wmic outputs in bytes)
|
|
if total, err := strconv.ParseUint(strings.TrimSpace(fields[1]), 10, 64); err == nil {
|
|
disk.Total = total
|
|
}
|
|
if available, err := strconv.ParseUint(strings.TrimSpace(fields[2]), 10, 64); err == nil {
|
|
disk.Available = available
|
|
}
|
|
|
|
disk.Used = disk.Total - disk.Available
|
|
if disk.Total > 0 {
|
|
disk.UsedPercent = float64(disk.Used) / float64(disk.Total) * 100
|
|
}
|
|
|
|
disks = append(disks, disk)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return disks, nil
|
|
}
|
|
|
|
// getWindowsProcessCount gets the number of running processes using WMI
|
|
func getWindowsProcessCount() (int, error) {
|
|
if cmd, err := exec.LookPath("wmic"); err == nil {
|
|
if data, err := exec.Command(cmd, "process", "get", "ProcessId").Output(); err == nil {
|
|
lines := strings.Split(string(data), "\n")
|
|
// Count non-empty lines that don't contain the header
|
|
count := 0
|
|
for _, line := range lines {
|
|
if strings.TrimSpace(line) != "" && !strings.Contains(line, "ProcessId") {
|
|
count++
|
|
}
|
|
}
|
|
return count, nil
|
|
}
|
|
}
|
|
|
|
return 0, nil
|
|
}
|
|
|
|
// getWindowsUptime gets system uptime using WMI or PowerShell
|
|
func getWindowsUptime() (string, error) {
|
|
// Try PowerShell first for more accurate uptime
|
|
if cmd, err := exec.LookPath("powershell"); err == nil {
|
|
if data, err := exec.Command(cmd, "-Command",
|
|
"(Get-Date) - (Get-CimInstance Win32_OperatingSystem).LastBootUpTime | Select-Object TotalDays").Output(); err == nil {
|
|
// Parse the output to get days
|
|
lines := strings.Split(string(data), "\n")
|
|
for _, line := range lines {
|
|
if strings.Contains(line, "TotalDays") {
|
|
fields := strings.Fields(line)
|
|
if len(fields) >= 2 {
|
|
if days, err := strconv.ParseFloat(fields[len(fields)-1], 64); err == nil {
|
|
return formatUptimeFromDays(days), nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fallback to wmic
|
|
if cmd, err := exec.LookPath("wmic"); err == nil {
|
|
if data, err := exec.Command(cmd, "os", "get", "LastBootUpTime").Output(); err == nil {
|
|
lines := strings.Split(string(data), "\n")
|
|
for _, line := range lines {
|
|
if strings.TrimSpace(line) != "" && !strings.Contains(line, "LastBootUpTime") {
|
|
// Parse WMI datetime format: 20231201123045.123456-300
|
|
wmiTime := strings.TrimSpace(line)
|
|
if len(wmiTime) >= 14 {
|
|
// Extract just the date part for basic calculation
|
|
// This is a simplified approach - in production you'd want proper datetime parsing
|
|
return fmt.Sprintf("Since %s", wmiTime[:8]), nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return "Unknown", nil
|
|
}
|
|
|
|
// formatUptimeFromDays formats uptime from days into human readable format
|
|
func formatUptimeFromDays(days float64) string {
|
|
if days < 1 {
|
|
hours := int(days * 24)
|
|
return fmt.Sprintf("%d hours", hours)
|
|
} else if days < 7 {
|
|
hours := int((days - float64(int(days))) * 24)
|
|
return fmt.Sprintf("%d days, %d hours", int(days), hours)
|
|
} else {
|
|
weeks := int(days / 7)
|
|
remainingDays := int(days) % 7
|
|
return fmt.Sprintf("%d weeks, %d days", weeks, remainingDays)
|
|
}
|
|
}
|
|
|
|
// getWindowsIPAddress gets the primary IP address using Windows commands
|
|
func getWindowsIPAddress() (string, error) {
|
|
// Try using ipconfig
|
|
if cmd, err := exec.LookPath("ipconfig"); err == nil {
|
|
if data, err := exec.Command(cmd, "/all").Output(); err == nil {
|
|
lines := strings.Split(string(data), "\n")
|
|
for _, line := range lines {
|
|
line = strings.TrimSpace(line)
|
|
if strings.HasPrefix(line, "IPv4 Address") || strings.HasPrefix(line, "IP Address") {
|
|
// Extract the IP address from the line
|
|
parts := strings.Split(line, ":")
|
|
if len(parts) >= 2 {
|
|
ip := strings.TrimSpace(parts[1])
|
|
// Prefer non-169.254.x.x (APIPA) addresses
|
|
if !strings.HasPrefix(ip, "169.254.") {
|
|
return ip, nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fallback to localhost
|
|
return "127.0.0.1", nil
|
|
}
|
|
|
|
// Override the generic functions with Windows-specific implementations
|
|
func init() {
|
|
// This function will be called when the package is imported on Windows
|
|
}
|
|
|
|
// getWindowsHardwareInfo gets additional hardware information
|
|
func getWindowsHardwareInfo() map[string]string {
|
|
hardware := make(map[string]string)
|
|
|
|
if cmd, err := exec.LookPath("wmic"); err == nil {
|
|
// Get motherboard information
|
|
if data, err := exec.Command(cmd, "baseboard", "get", "Manufacturer,Product,SerialNumber").Output(); err == nil {
|
|
lines := strings.Split(string(data), "\n")
|
|
for _, line := range lines {
|
|
if strings.TrimSpace(line) != "" && !strings.Contains(line, "Manufacturer") &&
|
|
!strings.Contains(line, "Product") && !strings.Contains(line, "SerialNumber") {
|
|
// This is a simplified parsing - in production you'd want more robust parsing
|
|
if strings.Contains(line, " ") {
|
|
hardware["motherboard"] = strings.TrimSpace(line)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get BIOS information
|
|
if data, err := exec.Command(cmd, "bios", "get", "Version,SerialNumber").Output(); err == nil {
|
|
lines := strings.Split(string(data), "\n")
|
|
for _, line := range lines {
|
|
if strings.TrimSpace(line) != "" && !strings.Contains(line, "Version") &&
|
|
!strings.Contains(line, "SerialNumber") {
|
|
hardware["bios"] = strings.TrimSpace(line)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get GPU information
|
|
if data, err := exec.Command(cmd, "path", "win32_VideoController", "get", "Name").Output(); err == nil {
|
|
lines := strings.Split(string(data), "\n")
|
|
gpus := []string{}
|
|
for _, line := range lines {
|
|
if strings.TrimSpace(line) != "" && !strings.Contains(line, "Name") {
|
|
gpu := strings.TrimSpace(line)
|
|
if gpu != "" {
|
|
gpus = append(gpus, gpu)
|
|
}
|
|
}
|
|
}
|
|
if len(gpus) > 0 {
|
|
hardware["graphics"] = strings.Join(gpus, ", ")
|
|
}
|
|
}
|
|
}
|
|
|
|
return hardware
|
|
} |