generated from sendnrw/template_golang
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 59d8fb3e5d | |||
| a2cb8450e2 | |||
| fee5b400a6 | |||
| 8e4325a1f8 | |||
| 25e5a6adc1 |
@@ -16,7 +16,9 @@
|
||||
|
||||
## Features
|
||||
|
||||
- Funktion 1
|
||||
- sc.exe create GoSysInfoService binPath="E:\GoProjects\pcinfo\pcinfo.exe" start=auto
|
||||
- sc.exe description GoSysInfoService "Hilden PC-Info"
|
||||
- sc.exe start GoSysInfoService
|
||||
|
||||
---
|
||||
|
||||
|
||||
478
main.go
478
main.go
@@ -1,14 +1,19 @@
|
||||
//go:build windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"html/template"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -17,6 +22,9 @@ import (
|
||||
"github.com/shirou/gopsutil/v3/disk"
|
||||
"github.com/shirou/gopsutil/v3/host"
|
||||
"github.com/shirou/gopsutil/v3/mem"
|
||||
|
||||
"golang.org/x/sys/windows/registry"
|
||||
"golang.org/x/sys/windows/svc"
|
||||
)
|
||||
|
||||
type NetInterface struct {
|
||||
@@ -24,6 +32,7 @@ type NetInterface struct {
|
||||
MAC string `json:"mac"`
|
||||
Addresses []string `json:"addresses"`
|
||||
IsLoopback bool `json:"is_loopback"`
|
||||
Profile string `json:"profile"`
|
||||
}
|
||||
|
||||
type DiskInfo struct {
|
||||
@@ -64,17 +73,126 @@ type Summary struct {
|
||||
Disks []DiskInfo `json:"disks"`
|
||||
}
|
||||
|
||||
type InstalledApp struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Publisher string `json:"publisher"`
|
||||
InstallDate string `json:"install_date"`
|
||||
Source string `json:"source"` // HKLM-64, HKLM-32, HKCU
|
||||
}
|
||||
|
||||
var (
|
||||
mu sync.RWMutex
|
||||
lastCPUUsage float64
|
||||
)
|
||||
|
||||
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 {
|
||||
return nil
|
||||
}
|
||||
defer k.Close()
|
||||
|
||||
names, err := k.ReadSubKeyNames(-1)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
apps := make([]InstalledApp, 0, len(names))
|
||||
for _, name := range names {
|
||||
sk, err := registry.OpenKey(k, name, registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// versteckte System-Komponenten ausblenden
|
||||
if v, _, err := sk.GetIntegerValue("SystemComponent"); err == nil && v == 1 {
|
||||
sk.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
displayName, _, _ := sk.GetStringValue("DisplayName")
|
||||
if strings.TrimSpace(displayName) == "" {
|
||||
sk.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
displayVersion, _, _ := sk.GetStringValue("DisplayVersion")
|
||||
publisher, _, _ := sk.GetStringValue("Publisher")
|
||||
installDate, _, _ := sk.GetStringValue("InstallDate")
|
||||
|
||||
apps = append(apps, InstalledApp{
|
||||
Name: displayName,
|
||||
Version: displayVersion,
|
||||
Publisher: publisher,
|
||||
InstallDate: installDate,
|
||||
Source: src,
|
||||
})
|
||||
|
||||
sk.Close()
|
||||
}
|
||||
|
||||
return apps
|
||||
}
|
||||
|
||||
func getInstalledApps() []InstalledApp {
|
||||
var all []InstalledApp
|
||||
|
||||
// Maschinenweit 64-bit
|
||||
all = append(all,
|
||||
readUninstallKey(registry.LOCAL_MACHINE,
|
||||
`SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall`, "HKLM-64")...,
|
||||
)
|
||||
|
||||
// Maschinenweit 32-bit auf 64-bit
|
||||
all = append(all,
|
||||
readUninstallKey(registry.LOCAL_MACHINE,
|
||||
`SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall`, "HKLM-32")...,
|
||||
)
|
||||
|
||||
// Aktueller Benutzer
|
||||
all = append(all,
|
||||
readUninstallKey(registry.CURRENT_USER,
|
||||
`SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall`, "HKCU")...,
|
||||
)
|
||||
|
||||
// Deduplizieren (Name+Version+Publisher)
|
||||
uniq := make(map[string]InstalledApp, len(all))
|
||||
for _, a := range all {
|
||||
key := strings.ToLower(strings.TrimSpace(a.Name) + "|" + strings.TrimSpace(a.Version) + "|" + strings.TrimSpace(a.Publisher))
|
||||
if _, ok := uniq[key]; !ok {
|
||||
uniq[key] = a
|
||||
}
|
||||
}
|
||||
|
||||
out := make([]InstalledApp, 0, len(uniq))
|
||||
for _, v := range uniq {
|
||||
out = append(out, v)
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool { return strings.ToLower(out[i].Name) < strings.ToLower(out[j].Name) })
|
||||
return out
|
||||
}
|
||||
|
||||
func appsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
apps := getInstalledApps()
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
enc := json.NewEncoder(w)
|
||||
enc.SetIndent("", " ")
|
||||
_ = enc.Encode(apps)
|
||||
}
|
||||
|
||||
// background sampler for CPU percentage to avoid blocking API calls
|
||||
func startCPUSampler() {
|
||||
func startCPUSampler(ctx context.Context) {
|
||||
go func() {
|
||||
// Prime once
|
||||
_, _ = cpu.Percent(0, false)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
pcts, err := cpu.Percent(time.Second, false)
|
||||
if err == nil && len(pcts) > 0 {
|
||||
mu.Lock()
|
||||
@@ -90,9 +208,12 @@ func getInterfaces() []NetInterface {
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Profile holen (best effort)
|
||||
profilesByIndex, _ := getWinNetProfiles()
|
||||
|
||||
var out []NetInterface
|
||||
for _, ifc := range ifaces {
|
||||
// Skip interfaces that are down
|
||||
if (ifc.Flags & net.FlagUp) == 0 {
|
||||
continue
|
||||
}
|
||||
@@ -107,23 +228,23 @@ func getInterfaces() []NetInterface {
|
||||
case *net.IPAddr:
|
||||
ip = v.IP
|
||||
}
|
||||
if ip == nil {
|
||||
continue
|
||||
}
|
||||
if ip.IsLoopback() {
|
||||
if ip == nil || ip.IsLoopback() {
|
||||
continue
|
||||
}
|
||||
ips = append(ips, ip.String())
|
||||
}
|
||||
// keep even if no IPs, to show MAC etc., but skip pure loopback
|
||||
if isLoop {
|
||||
continue
|
||||
}
|
||||
|
||||
profile := profilesByIndex[ifc.Index] // 👈 jetzt sicher über Index
|
||||
|
||||
out = append(out, NetInterface{
|
||||
Name: ifc.Name,
|
||||
MAC: ifc.HardwareAddr.String(),
|
||||
Addresses: ips,
|
||||
IsLoopback: isLoop,
|
||||
Profile: profile,
|
||||
})
|
||||
}
|
||||
return out
|
||||
@@ -141,7 +262,6 @@ func getDisks() []DiskInfo {
|
||||
continue
|
||||
}
|
||||
|
||||
// read-only erkennen (gopsutil: Opts kann string ODER []string sein)
|
||||
ro := false
|
||||
switch opts := any(p.Opts).(type) {
|
||||
case string:
|
||||
@@ -200,7 +320,7 @@ func collectSummary() Summary {
|
||||
Timestamp: time.Now(),
|
||||
Hostname: safe(hi, func(h *host.InfoStat) string { return h.Hostname }),
|
||||
Username: username,
|
||||
OS: safe(hi, func(h *host.InfoStat) string { return h.OS }), // "windows"
|
||||
OS: safe(hi, func(h *host.InfoStat) string { return h.OS }),
|
||||
Platform: safe(hi, func(h *host.InfoStat) string { return h.Platform }),
|
||||
PlatformVersion: safe(hi, func(h *host.InfoStat) string { return h.PlatformVersion }),
|
||||
KernelVersion: safe(hi, func(h *host.InfoStat) string { return h.KernelVersion }),
|
||||
@@ -239,31 +359,108 @@ var page = template.Must(template.New("index").Parse(`<!doctype html>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Windows Systeminfo</title>
|
||||
<style>
|
||||
:root { --bg:#0b1220; --fg:#e7eefc; --muted:#a3b1cc; --card:#111a2e; --accent:#5aa9ff; }
|
||||
:root {
|
||||
--bg: #f5f6f8;
|
||||
--fg: #121826;
|
||||
--muted: #5d6b82;
|
||||
--card: #ffffff;
|
||||
--accent: #0066ff;
|
||||
--border: rgba(12, 21, 37, 0.08);
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
body{max-width:980px;margin:0 auto;padding:24px;font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif;background:var(--bg);color:var(--fg)}
|
||||
h1{font-size:22px;margin:0 0 16px 0}
|
||||
body{
|
||||
max-width:1200px;
|
||||
margin:0 auto;
|
||||
padding:24px;
|
||||
font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif;
|
||||
background:var(--bg);
|
||||
color:var(--fg);
|
||||
}
|
||||
h1{font-size:22px;margin:0 0 6px 0}
|
||||
h2{margin:0 0 16px 0}
|
||||
.grid{display:flex;flex-direction:column;gap:12px}
|
||||
.card{width:100%}
|
||||
.k{font-size:12px;color:var(--muted);text-transform:uppercase;letter-spacing:.06em}
|
||||
.card{
|
||||
width:100%;
|
||||
background:var(--card);
|
||||
border:1px solid var(--border);
|
||||
border-radius:16px;
|
||||
padding:14px 16px 10px 16px;
|
||||
box-shadow:0 10px 30px rgba(15,23,42,0.03);
|
||||
}
|
||||
.k{font-size:13px;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:4px}
|
||||
.v{font-weight:600}
|
||||
.row{display:flex;justify-content:space-between;gap:8px;align-items:center;margin:6px 0}
|
||||
.mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace}
|
||||
.pill{display:inline-block;background:#0b2547;color:#b8d4ff;border-radius:999px;padding:2px 8px;margin:2px 6px 0 0;font-size:12px}
|
||||
.bar{height:10px;background:#091428;border-radius:999px;overflow:hidden}
|
||||
.fill{height:100%;background:var(--accent);width:0%}
|
||||
.stereo{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;color: #ff0000ff}
|
||||
.pill{
|
||||
display:inline-block;
|
||||
background:#e8f1ff;
|
||||
color:#0047cc;
|
||||
border-radius:999px;
|
||||
padding:2px 8px;
|
||||
margin:2px 6px 0 0;
|
||||
font-size:14px;
|
||||
}
|
||||
.pill2{
|
||||
display:inline-block;
|
||||
background:#ffe480;
|
||||
color:#3e3e3e;
|
||||
border-radius:999px;
|
||||
padding:2px 8px;
|
||||
margin:2px 6px 0 0;
|
||||
font-size:14px;
|
||||
}
|
||||
.bar{
|
||||
height:10px;
|
||||
background:#edf0f5;
|
||||
border-radius:999px;
|
||||
overflow:hidden;
|
||||
}
|
||||
.fill{
|
||||
height:100%;
|
||||
background:var(--accent);
|
||||
width:0%;
|
||||
transition:width .3s ease-out;
|
||||
}
|
||||
footer{margin-top:18px;color:var(--muted);font-size:12px}
|
||||
.disks table{width:100%;border-collapse:collapse}
|
||||
.disks th,.disks td{padding:8px;border-bottom:1px solid #1b2a4a;text-align:left;font-size:14px}
|
||||
.disks th,.disks td{
|
||||
padding:8px;
|
||||
border-bottom:1px solid #e5e9f2;
|
||||
text-align:left;
|
||||
font-size:14px
|
||||
}
|
||||
.disks th{background:#f5f6f8;font-weight:500}
|
||||
button#btnLoadApps{
|
||||
border:none;
|
||||
background:#e8f1ff;
|
||||
color:#0047cc;
|
||||
font-weight:500;
|
||||
}
|
||||
input#appsFilter{
|
||||
background:#ffffff;
|
||||
color:var(--fg);
|
||||
border:1px solid #d0d7e2;
|
||||
border-radius:8px;
|
||||
}
|
||||
table thead th{font-size:13px;color:#3b4560}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
/* optional: falls Host dunkles Theme erzwingt */
|
||||
:root {
|
||||
--bg:#f5f6f8;
|
||||
--fg:#121826;
|
||||
--card:#ffffff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Windows Systeminfo</h1>
|
||||
<h2><div class="v mono" id="hostname2"></div></h2>
|
||||
<h2><div class="v stereo" id="hostname2"></div></h2>
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<div class="k">Host</div>
|
||||
<div class="row"><div>Hostname</div><div class="v mono" id="hostname"></div></div>
|
||||
<div class="row"><div>Hostname</div><div class="v stereo" id="hostname"></div></div>
|
||||
<div class="row"><div>User</div><div class="v mono" id="username"></div></div>
|
||||
<div class="row"><div>Uptime</div><div class="v" id="uptime"></div></div>
|
||||
<div class="row"><div>Boot</div><div class="v" id="boottime"></div></div>
|
||||
@@ -306,6 +503,28 @@ var page = template.Must(template.New("index").Parse(`<!doctype html>
|
||||
<tbody id="disks"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="k">Installierte Programme</div>
|
||||
<div class="row" style="gap:8px;align-items:center;">
|
||||
<button id="btnLoadApps" class="pill" style="cursor:pointer;">Laden</button>
|
||||
<input id="appsFilter" type="text" placeholder="Suchen …"
|
||||
style="flex:1;padding:6px 10px;">
|
||||
<span class="k" id="appsCount"></span>
|
||||
</div>
|
||||
<div class="apps">
|
||||
<table style="width:100%;border-collapse:collapse">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="text-align:left;padding:8px;border-bottom:1px solid #e5e9f2">Name</th>
|
||||
<th style="text-align:left;padding:8px;border-bottom:1px solid #e5e9f2">Version</th>
|
||||
<th style="text-align:left;padding:8px;border-bottom:1px solid #e5e9f2">Publisher</th>
|
||||
<th style="text-align:left;padding:8px;border-bottom:1px solid #e5e9f2">Quelle</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="apps"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>Aktualisiert alle 2s</footer>
|
||||
@@ -347,6 +566,12 @@ async function load(){
|
||||
(j.interfaces||[]).forEach(n=>{
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = '<div class="row"><div>'+n.name+'</div><div class="mono">'+(n.mac||'-')+'</div></div>';
|
||||
if (n.profile) {
|
||||
const p = document.createElement('span');
|
||||
p.className = 'pill2';
|
||||
p.textContent = n.profile;
|
||||
div.appendChild(p);
|
||||
}
|
||||
(n.addresses||[]).forEach(ip=>{ const span=document.createElement('span'); span.className='pill mono'; span.textContent=ip; div.appendChild(span); });
|
||||
wrap.appendChild(div);
|
||||
});
|
||||
@@ -362,9 +587,58 @@ async function load(){
|
||||
|
||||
load();
|
||||
setInterval(load, 2000);
|
||||
|
||||
let appsCache = null;
|
||||
|
||||
function renderApps(list){
|
||||
const tbody = document.getElementById('apps');
|
||||
tbody.innerHTML = '';
|
||||
(list||[]).forEach(a=>{
|
||||
const tr = document.createElement('tr');
|
||||
tr.innerHTML =
|
||||
'<td class="mono" style="padding:6px 8px;border-bottom:1px solid #e5e9f2">'+(a.name||'-')+'</td>'+
|
||||
'<td style="padding:6px 8px;border-bottom:1px solid #e5e9f2">'+(a.version||'')+'</td>'+
|
||||
'<td style="padding:6px 8px;border-bottom:1px solid #e5e9f2">'+(a.publisher||'')+'</td>'+
|
||||
'<td style="padding:6px 8px;border-bottom:1px solid #e5e9f2">'+(a.source||'')+'</td>';
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
const c = document.getElementById('appsCount');
|
||||
if(c) c.textContent = (list||[]).length+' Einträge';
|
||||
}
|
||||
|
||||
async function loadAppsOnce(){
|
||||
if(appsCache) return;
|
||||
try{
|
||||
const r = await fetch('/api/apps');
|
||||
appsCache = await r.json();
|
||||
renderApps(appsCache);
|
||||
}catch(e){
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', ()=>{
|
||||
const btn = document.getElementById('btnLoadApps');
|
||||
const inp = document.getElementById('appsFilter');
|
||||
if(btn){ btn.addEventListener('click', loadAppsOnce); }
|
||||
if(inp){
|
||||
inp.addEventListener('input', ()=>{
|
||||
if(!appsCache) return;
|
||||
const q = inp.value.toLowerCase();
|
||||
const filtered = appsCache.filter(a =>
|
||||
(a.name||'').toLowerCase().includes(q) ||
|
||||
(a.version||'').toLowerCase().includes(q) ||
|
||||
(a.publisher||'').toLowerCase().includes(q) ||
|
||||
(a.source||'').toLowerCase().includes(q)
|
||||
);
|
||||
renderApps(filtered);
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>`))
|
||||
</html>
|
||||
`))
|
||||
|
||||
func indexHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
@@ -383,16 +657,170 @@ func apiHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
startCPUSampler()
|
||||
// --- ab hier: Dienst-Integration ---
|
||||
|
||||
const serviceName = "GoSysInfoService"
|
||||
|
||||
// runHTTP startet deinen bisherigen HTTP-Server und reagiert auf ctx.Done()
|
||||
func runHTTP(ctx context.Context) error {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/", indexHandler)
|
||||
mux.HandleFunc("/api/apps", appsHandler)
|
||||
mux.HandleFunc("/api/summary", apiHandler)
|
||||
|
||||
addr := "127.0.0.1:8080" // bind strictly to loopback (IPv4)
|
||||
addr := "127.0.0.1:24000"
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: addr,
|
||||
Handler: mux,
|
||||
}
|
||||
|
||||
// CPU-Sampler an das gleiche ctx hängen
|
||||
startCPUSampler(ctx)
|
||||
|
||||
log.Printf("Starte lokales Dashboard auf http://%s … (nur von diesem Rechner erreichbar)", addr)
|
||||
if err := http.ListenAndServe(addr, mux); err != nil {
|
||||
|
||||
// Shutdown-Goroutine
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
shCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
_ = srv.Shutdown(shCtx)
|
||||
cancel() // explizit aufrufen, kein defer in der Goroutine nötig
|
||||
}()
|
||||
|
||||
// Blockiert hier bis Stop oder Fehler
|
||||
err := srv.ListenAndServe()
|
||||
// ListenAndServe gibt bei Shutdown typischerweise http.ErrServerClosed zurück
|
||||
if err == http.ErrServerClosed {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// windows-svc Wrapper
|
||||
type winService struct{}
|
||||
|
||||
func (s *winService) Execute(args []string, r <-chan svc.ChangeRequest, status chan<- svc.Status) (bool, uint32) {
|
||||
const accepted = svc.AcceptStop | svc.AcceptShutdown
|
||||
status <- svc.Status{State: svc.StartPending}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel() // <-- neu: für alle Rückgabepfade
|
||||
|
||||
go func() {
|
||||
if err := runHTTP(ctx); err != nil {
|
||||
log.Printf("HTTP server stopped: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
status <- svc.Status{State: svc.Running, Accepts: accepted}
|
||||
|
||||
for c := range r {
|
||||
switch c.Cmd {
|
||||
case svc.Interrogate:
|
||||
status <- c.CurrentStatus
|
||||
case svc.Stop, svc.Shutdown:
|
||||
status <- svc.Status{State: svc.StopPending}
|
||||
return false, 0 // cancel() läuft trotzdem dank defer
|
||||
}
|
||||
}
|
||||
|
||||
// falls der Channel mal zu ist
|
||||
return false, 0
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Prüfen, ob wir als Windows-Dienst laufen
|
||||
isService, err := svc.IsWindowsService()
|
||||
if err != nil {
|
||||
log.Fatalf("svc.IsWindowsService: %v", err)
|
||||
}
|
||||
|
||||
if isService {
|
||||
// Hier spricht das Programm mit dem Service Control Manager
|
||||
if err := svc.Run(serviceName, &winService{}); err != nil {
|
||||
log.Fatalf("svc.Run failed: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// normaler Konsolenmodus
|
||||
ctx := context.Background()
|
||||
if err := runHTTP(ctx); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
type psConnProfile struct {
|
||||
Name string `json:"Name"`
|
||||
InterfaceAlias string `json:"InterfaceAlias"`
|
||||
InterfaceIndex int `json:"InterfaceIndex"`
|
||||
NetworkCategory string `json:"NetworkCategory"` // "Public", "Private", "DomainAuthenticated"
|
||||
}
|
||||
|
||||
func getWinNetProfiles() (map[int]string, error) {
|
||||
cmd := exec.Command(
|
||||
"powershell",
|
||||
"-NoProfile",
|
||||
"-NonInteractive",
|
||||
"-Command",
|
||||
`Get-NetConnectionProfile | Select-Object InterfaceAlias,InterfaceIndex,NetworkCategory | ConvertTo-Json -Depth 3`,
|
||||
)
|
||||
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
log.Printf("getWinNetProfiles: powershell error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
if len(out) == 0 {
|
||||
return map[int]string{}, nil
|
||||
}
|
||||
|
||||
// Wir wissen nicht, ob PS ein Objekt oder ein Array zurückgibt → erst Array versuchen
|
||||
type psConnProfile struct {
|
||||
InterfaceAlias string `json:"InterfaceAlias"`
|
||||
InterfaceIndex int `json:"InterfaceIndex"`
|
||||
NetworkCategory interface{} `json:"NetworkCategory"`
|
||||
}
|
||||
|
||||
normalizeCat := func(v interface{}) string {
|
||||
switch vv := v.(type) {
|
||||
case string:
|
||||
// "Public", "Private", "DomainAuthenticated"
|
||||
return vv
|
||||
case float64:
|
||||
switch int(vv) {
|
||||
case 0:
|
||||
return "Public"
|
||||
case 1:
|
||||
return "Private"
|
||||
case 2:
|
||||
return "DomainAuthenticated"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// 1) Array probieren
|
||||
var arr []psConnProfile
|
||||
if err := json.Unmarshal(out, &arr); err == nil {
|
||||
res := make(map[int]string, len(arr))
|
||||
for _, p := range arr {
|
||||
res[p.InterfaceIndex] = normalizeCat(p.NetworkCategory)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// 2) Einzelnes Objekt probieren
|
||||
var single psConnProfile
|
||||
if err := json.Unmarshal(out, &single); err != nil {
|
||||
log.Printf("getWinNetProfiles: cannot unmarshal: %v -- out: %s", err, string(out))
|
||||
return nil, err
|
||||
}
|
||||
res := make(map[int]string, 1)
|
||||
res[single.InterfaceIndex] = normalizeCat(single.NetworkCategory)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
BIN
pcinfo.exe
Normal file
BIN
pcinfo.exe
Normal file
Binary file not shown.
Reference in New Issue
Block a user