diff --git a/main.go b/main.go index 667e995..76f6a17 100644 --- a/main.go +++ b/main.go @@ -81,11 +81,123 @@ type InstalledApp struct { Source string `json:"source"` // HKLM-64, HKLM-32, HKCU } +type DeviceInfo struct { + InstanceID string `json:"instance_id"` + Class string `json:"class"` + ClassGUID string `json:"class_guid"` + FriendlyName string `json:"friendly_name"` + Manufacturer string `json:"manufacturer"` + Status string `json:"status"` + Present bool `json:"present"` +} + var ( mu sync.RWMutex lastCPUUsage float64 ) +func getPnpDevices() ([]DeviceInfo, error) { + // PowerShell: aktuelle PnP-Geräte, inkl. ClassGuid + cmd := exec.Command( + "powershell", + "-NoProfile", + "-NonInteractive", + "-Command", + `Get-PnpDevice -PresentOnly | Select-Object InstanceId,Class,ClassGuid,FriendlyName,Manufacturer,Status,Present | ConvertTo-Json -Depth 3`, + ) + + out, err := cmd.Output() + if err != nil { + log.Printf("getPnpDevices: powershell error: %v", err) + return nil, err + } + if len(out) == 0 { + return []DeviceInfo{}, nil + } + + // PS gibt entweder ein Objekt oder ein Array zurück → wie bei getWinNetProfiles behandeln + type psDevice struct { + InstanceId string `json:"InstanceId"` + Class string `json:"Class"` + ClassGuid string `json:"ClassGuid"` + FriendlyName string `json:"FriendlyName"` + Manufacturer string `json:"Manufacturer"` + Status string `json:"Status"` + Present interface{} `json:"Present"` + } + + parsePresent := func(v interface{}) bool { + switch t := v.(type) { + case bool: + return t + case string: + // "True"/"False" etc. + return strings.EqualFold(t, "true") + default: + return false + } + } + + var arr []psDevice + if err := json.Unmarshal(out, &arr); err == nil { + res := make([]DeviceInfo, 0, len(arr)) + for _, d := range arr { + res = append(res, DeviceInfo{ + InstanceID: d.InstanceId, + Class: d.Class, + ClassGUID: d.ClassGuid, + FriendlyName: d.FriendlyName, + Manufacturer: d.Manufacturer, + Status: d.Status, + Present: parsePresent(d.Present), + }) + } + return res, nil + } + + // Ein einzelnes Objekt + var single psDevice + if err := json.Unmarshal(out, &single); err != nil { + log.Printf("getPnpDevices: cannot unmarshal: %v -- out: %s", err, string(out)) + return nil, err + } + return []DeviceInfo{ + { + InstanceID: single.InstanceId, + Class: single.Class, + ClassGUID: single.ClassGuid, + FriendlyName: single.FriendlyName, + Manufacturer: single.Manufacturer, + Status: single.Status, + Present: parsePresent(single.Present), + }, + }, nil +} + +func devicesHandler(w http.ResponseWriter, r *http.Request) { + devs, err := getPnpDevices() + if err != nil { + http.Error(w, "failed to enumerate devices: "+err.Error(), http.StatusInternalServerError) + return + } + + // Optional: Filter nur USB-Geräte (InstanceID beginnt mit "USB\") + if r.URL.Query().Get("only_usb") == "1" { + filtered := make([]DeviceInfo, 0, len(devs)) + for _, d := range devs { + if strings.HasPrefix(strings.ToUpper(d.InstanceID), "USB\\") { + filtered = append(filtered, d) + } + } + devs = filtered + } + + w.Header().Set("Content-Type", "application/json") + enc := json.NewEncoder(w) + enc.SetIndent("", " ") + _ = enc.Encode(devs) +} + func readUninstallKey(base registry.Key, path, src string) []InstalledApp { k, err := registry.OpenKey(base, path, registry.ENUMERATE_SUB_KEYS|registry.QUERY_VALUE) if err != nil { @@ -667,6 +779,7 @@ func runHTTP(ctx context.Context) error { mux.HandleFunc("/", indexHandler) mux.HandleFunc("/api/apps", appsHandler) mux.HandleFunc("/api/summary", apiHandler) + mux.HandleFunc("/api/devices", devicesHandler) addr := ":24000"