@daimazun/hardware-info 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +907 -0
  2. package/bin/OpenHardwareMonitorLib.dll +0 -0
  3. package/index.js +192 -0
  4. package/lib/allhardware.js +35 -0
  5. package/lib/backend-usage.js +103 -0
  6. package/lib/battery.js +76 -0
  7. package/lib/disk-watcher.js +156 -0
  8. package/lib/disk.js +77 -0
  9. package/lib/gpu.js +70 -0
  10. package/lib/hardware-service.js +284 -0
  11. package/lib/memory.js +59 -0
  12. package/lib/monitor.js +176 -0
  13. package/lib/network.js +75 -0
  14. package/lib/ohm-daemon.js +208 -0
  15. package/lib/ohm.js +41 -0
  16. package/lib/perfctr.js +43 -0
  17. package/lib/process-icon.js +76 -0
  18. package/lib/process-ops.js +218 -0
  19. package/lib/processes.js +232 -0
  20. package/lib/ps.js +93 -0
  21. package/lib/public-ip.js +124 -0
  22. package/lib/services.js +43 -0
  23. package/lib/sysinfo-daemon.js +188 -0
  24. package/lib/system.js +77 -0
  25. package/lib/usb.js +49 -0
  26. package/lib/win32-procs.js +241 -0
  27. package/package.json +50 -0
  28. package/scripts/get-all-hardware.ps1 +98 -0
  29. package/scripts/get-lhm-temp.ps1 +119 -0
  30. package/scripts/ohm-daemon.ps1 +141 -0
  31. package/scripts/sysinfo-daemon.ps1 +386 -0
  32. package/test/check-bugs.js +182 -0
  33. package/test/public/gj.pay.ali.jpg +0 -0
  34. package/test/public/gj.pay.wx.jpg +0 -0
  35. package/test/public/index.html +996 -0
  36. package/test/public/zdl.pay.ali.jpg +0 -0
  37. package/test/public/zdl.pay.wx.jpg +0 -0
  38. package/test/scan-encoding.js +77 -0
  39. package/test/server.js +255 -0
  40. package/test/test-all.js +180 -0
  41. package/test/test-daemon.js +51 -0
  42. package/test/test-kill-name.js +30 -0
  43. package/test/test-monitor.js +78 -0
  44. package/test/test-new-features.js +93 -0
  45. package/test/test-service.js +91 -0
  46. package/test/test-sysinfo-daemon.js +95 -0
  47. package/test/test.js +63 -0
@@ -0,0 +1,141 @@
1
+ param(
2
+ [string]$DllPath = ""
3
+ )
4
+
5
+ # UTF-8 输出,解决中文乱码
6
+ [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
7
+ $OutputEncoding = [System.Text.Encoding]::UTF8
8
+ chcp 65001 > $null
9
+
10
+ $ErrorActionPreference = "Stop"
11
+
12
+ if (-not $DllPath) {
13
+ $DllPath = Join-Path $PSScriptRoot "..\bin\OpenHardwareMonitorLib.dll"
14
+ }
15
+ $DllPath = (Resolve-Path $DllPath).Path
16
+
17
+ if (-not (Test-Path $DllPath)) {
18
+ Write-Error "DLL not found: $DllPath"
19
+ exit 1
20
+ }
21
+
22
+ try {
23
+ Unblock-File -LiteralPath $DllPath -ErrorAction SilentlyContinue
24
+ Add-Type -LiteralPath $DllPath
25
+ } catch {
26
+ Write-Error "Failed to load DLL: $_"
27
+ exit 1
28
+ }
29
+
30
+ $monitor = New-Object OpenHardwareMonitor.Hardware.Computer
31
+ $monitor.CPUEnabled = $true
32
+ $monitor.MainboardEnabled = $false
33
+ $monitor.GPUEnabled = $false
34
+ $monitor.HDDEnabled = $false
35
+ $monitor.RAMEnabled = $false
36
+ $monitor.FanControllerEnabled = $false
37
+
38
+ try {
39
+ $monitor.Open()
40
+ } catch {
41
+ Write-Error "Failed to open monitor (needs admin?): $_"
42
+ exit 1
43
+ }
44
+
45
+ function Get-CpuData {
46
+ $script:cpuName = ""
47
+ $script:allSensors = @()
48
+
49
+ function Walk-Hardware {
50
+ param($hw)
51
+ $hw.Update()
52
+ foreach ($sensor in $hw.Sensors) {
53
+ $val = $sensor.Value
54
+ if ($null -ne $val) {
55
+ $script:allSensors += [PSCustomObject]@{
56
+ name = $sensor.Name
57
+ type = $sensor.SensorType.ToString()
58
+ value = [math]::Round([double]$val, 2)
59
+ }
60
+ }
61
+ }
62
+ foreach ($sub in $hw.SubHardware) {
63
+ Walk-Hardware $sub
64
+ }
65
+ }
66
+
67
+ foreach ($hw in $monitor.Hardware) {
68
+ if ($hw.HardwareType -eq [OpenHardwareMonitor.Hardware.HardwareType]::CPU) {
69
+ $script:cpuName = $hw.Name
70
+ Walk-Hardware $hw
71
+ }
72
+ }
73
+
74
+ if ($script:allSensors.Count -eq 0) {
75
+ return $null
76
+ }
77
+
78
+ function Get-ByType {
79
+ param($type)
80
+ return @($script:allSensors | Where-Object { $_.type -eq $type } | ForEach-Object {
81
+ [PSCustomObject]@{ name = $_.name; value = $_.value }
82
+ })
83
+ }
84
+
85
+ $tempSensors = Get-ByType "Temperature"
86
+ $loadSensors = Get-ByType "Load"
87
+ $clockSensors = Get-ByType "Clock"
88
+ $powerSensors = Get-ByType "Power"
89
+
90
+ # 没有温度传感器说明驱动没加载成功(大概率非管理员)
91
+ if ($tempSensors.Count -eq 0) {
92
+ return [PSCustomObject]@{ error = "no temperature sensors (likely no admin privileges, WinRing0 driver not loaded)" }
93
+ }
94
+
95
+ $coreTemps = @($tempSensors | Where-Object { $_.name -match "Core" })
96
+ $packageTemp = ($tempSensors | Where-Object { $_.name -match "Package" } | Select-Object -First 1).value
97
+ $maxCoreTemp = if ($coreTemps.Count -gt 0) { ($coreTemps | Measure-Object -Property value -Maximum).Maximum } else { $null }
98
+
99
+ return [PSCustomObject]@{
100
+ source = "openhardwaremonitor_daemon"
101
+ isCoreTemp = $true
102
+ cpuName = $script:cpuName
103
+ temperature = [PSCustomObject]@{
104
+ cores = $coreTemps
105
+ package = $packageTemp
106
+ maxCore = $maxCoreTemp
107
+ }
108
+ load = $loadSensors
109
+ clock = $clockSensors
110
+ power = $powerSensors
111
+ timestamp = (Get-Date).ToString("o")
112
+ }
113
+ }
114
+
115
+ # 发送就绪信号,让 Node.js 知道进程已启动并完成驱动加载
116
+ Write-Output "READY"
117
+
118
+ # 主循环:等待 stdin 指令
119
+ while ($true) {
120
+ $line = [Console]::In.ReadLine()
121
+ if ($null -eq $line) { break }
122
+ $line = $line.Trim()
123
+ if ($line -eq "exit" -or $line -eq "quit") {
124
+ break
125
+ }
126
+ if ($line -eq "get" -or $line -eq "") {
127
+ try {
128
+ $data = Get-CpuData
129
+ if ($null -eq $data) {
130
+ Write-Output '{"error":"no sensors"}'
131
+ } else {
132
+ Write-Output ($data | ConvertTo-Json -Depth 6 -Compress)
133
+ }
134
+ } catch {
135
+ $errMsg = $_.Exception.Message -replace '"', '\"'
136
+ Write-Output "{`"error`":`"$errMsg`"}"
137
+ }
138
+ }
139
+ }
140
+
141
+ $monitor.Close()
@@ -0,0 +1,386 @@
1
+ # sysinfo-daemon.ps1 — 统一硬件信息常驻进程
2
+ # 通过 stdin 接收指令,stdout 输出 JSON,支持高频低延迟查询
3
+
4
+ param(
5
+ [int]$SystemCacheTtl = 30,
6
+ [int]$NetworkCacheTtl = 30
7
+ )
8
+
9
+ # UTF-8 输出
10
+ [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
11
+ $OutputEncoding = [System.Text.Encoding]::UTF8
12
+ chcp 65001 > $null
13
+
14
+ $ErrorActionPreference = "Stop"
15
+
16
+ # ========== 缓存(变化不频繁的数据,启动时预加载)==========
17
+ $script:systemCache = $null
18
+ $script:networkAdapterCache = $null
19
+ $script:gpuCache = $null
20
+ $script:usbCache = $null
21
+ $script:cacheTime = @{}
22
+
23
+ function Get-CachedOrUpdate {
24
+ param($key, $ttlSeconds, $fetchBlock)
25
+ $now = Get-Date
26
+ if (-not $script:cacheTime.ContainsKey($key) -or ($now - $script:cacheTime[$key]).TotalSeconds -gt $ttlSeconds) {
27
+ $script:cacheTime[$key] = $now
28
+ return & $fetchBlock
29
+ }
30
+ return $null
31
+ }
32
+
33
+ # ========== 各模块查询函数 ==========
34
+
35
+ function Get-SystemInfo {
36
+ # 静态信息缓存 30 秒(电源计划可能变化),只更新 uptime 和 currentTime
37
+ $needRefresh = ($null -eq $script:systemCache) -or (-not $script:cacheTime.ContainsKey('system')) -or ((Get-Date) - $script:cacheTime['system']).TotalSeconds -gt $SystemCacheTtl
38
+ if ($needRefresh) {
39
+ $script:cacheTime['system'] = Get-Date
40
+ $os = Get-CimInstance Win32_OperatingSystem
41
+ $cs = Get-CimInstance Win32_ComputerSystem
42
+ $bios = Get-CimInstance Win32_BIOS
43
+ $board = Get-CimInstance Win32_BaseBoard
44
+ $cpu = Get-CimInstance Win32_Processor | Select-Object -First 1
45
+ try {
46
+ $powerPlan = (Get-CimInstance -Namespace "root\cimv2\power" -ClassName Win32_PowerPlan | Where-Object { $_.IsActive -eq $true } | Select-Object -First 1).ElementName
47
+ } catch { $powerPlan = $null }
48
+ $script:systemCache = [PSCustomObject]@{
49
+ os = [PSCustomObject]@{
50
+ caption = $os.Caption; version = $os.Version; buildNumber = $os.BuildNumber
51
+ architecture = $os.OSArchitecture; installDate = $os.InstallDate.ToString('o')
52
+ lastBootUpTime = $os.LastBootUpTime.ToString('o'); systemDrive = $os.SystemDrive
53
+ }
54
+ computer = [PSCustomObject]@{
55
+ manufacturer = $cs.Manufacturer; model = $cs.Model; name = $cs.Name
56
+ domain = $cs.Domain; totalPhysicalMemoryGB = [math]::Round($cs.TotalPhysicalMemory / 1GB, 2)
57
+ numberOfLogicalProcessors = $cs.NumberOfLogicalProcessors; systemType = $cs.SystemType
58
+ }
59
+ bios = [PSCustomObject]@{
60
+ manufacturer = $bios.Manufacturer; version = $bios.SMBIOSBIOSVersion
61
+ releaseDate = if ($bios.ReleaseDate) { $bios.ReleaseDate.ToString('o') } else { $null }
62
+ serialNumber = $bios.SerialNumber
63
+ }
64
+ motherboard = [PSCustomObject]@{
65
+ manufacturer = $board.Manufacturer; product = $board.Product; version = $board.Version; serialNumber = $board.SerialNumber
66
+ }
67
+ cpu = [PSCustomObject]@{
68
+ name = $cpu.Name; manufacturer = $cpu.Manufacturer
69
+ numberOfCores = $cpu.NumberOfCores; numberOfLogicalProcessors = $cpu.NumberOfLogicalProcessors
70
+ maxClockSpeedMHz = $cpu.MaxClockSpeed; l2CacheKB = $cpu.L2CacheSize; l3CacheKB = $cpu.L3CacheSize
71
+ socketDesignation = $cpu.SocketDesignation; virtualizationEnabled = $cpu.VirtualizationFirmwareEnabled
72
+ }
73
+ powerPlan = $powerPlan; timezone = (Get-TimeZone).Id
74
+ bootTime = $os.LastBootUpTime
75
+ }
76
+ }
77
+ $c = $script:systemCache
78
+ $uptime = (Get-Date) - $c.bootTime
79
+ return [PSCustomObject]@{
80
+ os = $c.os; computer = $c.computer; bios = $c.bios; motherboard = $c.motherboard
81
+ cpu = $c.cpu; powerPlan = $c.powerPlan; timezone = $c.timezone
82
+ currentTime = (Get-Date).ToString('o')
83
+ uptimeSeconds = [math]::Round($uptime.TotalSeconds); uptimeDays = [math]::Round($uptime.TotalDays, 2)
84
+ }
85
+ }
86
+
87
+ function Get-MemoryInfo {
88
+ $sticks = Get-CimInstance Win32_PhysicalMemory
89
+ $array = Get-CimInstance Win32_PhysicalMemoryArray | Select-Object -First 1
90
+ $os = Get-CimInstance Win32_OperatingSystem
91
+ $stickList = @()
92
+ foreach ($s in $sticks) {
93
+ $stickList += [PSCustomObject]@{
94
+ deviceLocator = $s.DeviceLocator; bankLabel = $s.BankLabel
95
+ capacityGB = [math]::Round($s.Capacity / 1GB, 2); speedMHz = $s.Speed
96
+ configuredSpeedMHz = $s.ConfiguredClockSpeed; manufacturer = $s.Manufacturer
97
+ partNumber = $s.PartNumber.Trim(); serialNumber = $s.SerialNumber; dataWidth = $s.DataWidth
98
+ }
99
+ }
100
+ $totalPhysicalGB = [math]::Round($os.TotalVisibleMemorySize / 1MB, 2)
101
+ $freePhysicalGB = [math]::Round($os.FreePhysicalMemory / 1MB, 2)
102
+ [PSCustomObject]@{
103
+ totalPhysicalGB = $totalPhysicalGB; usedPhysicalGB = [math]::Round($totalPhysicalGB - $freePhysicalGB, 2)
104
+ freePhysicalGB = $freePhysicalGB; usedPercent = [math]::Round((($totalPhysicalGB - $freePhysicalGB) / $totalPhysicalGB) * 100, 2)
105
+ totalVirtualGB = [math]::Round($os.TotalVirtualMemorySize / 1MB, 2); freeVirtualGB = [math]::Round($os.FreeVirtualMemory / 1MB, 2)
106
+ sticks = $stickList; stickCount = $stickList.Count
107
+ array = if ($array) { [PSCustomObject]@{ maxCapacityGB = [math]::Round($array.MaxCapacity / 1MB, 2); memoryDevices = $array.MemoryDevices } } else { $null }
108
+ }
109
+ }
110
+
111
+ function Get-DiskInfo {
112
+ $disks = Get-CimInstance Win32_DiskDrive
113
+ $logical = Get-CimInstance Win32_LogicalDisk -Filter "DriveType=3"
114
+ $diskPerf = Get-CimInstance Win32_PerfFormattedData_PerfDisk_PhysicalDisk | Where-Object { $_.Name -ne '_Total' }
115
+ $diskList = @()
116
+ foreach ($d in $disks) {
117
+ $perf = $diskPerf | Where-Object { $_.Name -like "$($d.Index) *" } | Select-Object -First 1
118
+ # 关联物理磁盘 → 分区 → 逻辑盘符
119
+ $driveLetters = @()
120
+ try {
121
+ $parts = Get-CimAssociatedInstance -InputObject $d -ResultClassName Win32_DiskPartition -ErrorAction SilentlyContinue
122
+ foreach ($p in $parts) {
123
+ $logs = Get-CimAssociatedInstance -InputObject $p -ResultClassName Win32_LogicalDisk -ErrorAction SilentlyContinue
124
+ foreach ($l in $logs) { if ($l.DeviceID) { $driveLetters += $l.DeviceID } }
125
+ }
126
+ } catch { }
127
+ $diskList += [PSCustomObject]@{
128
+ index = $d.Index; model = $d.Model; interfaceType = $d.InterfaceType; mediaType = $d.MediaType
129
+ serialNumber = if ($d.SerialNumber) { $d.SerialNumber.Trim() } else { $null }
130
+ firmwareRevision = $d.FirmwareRevision; sizeGB = [math]::Round($d.Size / 1GB, 2)
131
+ drives = $driveLetters
132
+ readBytesPerSec = if ($perf) { $perf.DiskReadBytesPerSec } else { $null }
133
+ writeBytesPerSec = if ($perf) { $perf.DiskWriteBytesPerSec } else { $null }
134
+ percentActive = if ($perf) { $perf.PercentDiskTime } else { $null }
135
+ }
136
+ }
137
+ $partitionList = @()
138
+ foreach ($ld in $logical) {
139
+ $sizeGB = [math]::Round($ld.Size / 1GB, 2); $freeGB = [math]::Round($ld.FreeSpace / 1GB, 2)
140
+ $partitionList += [PSCustomObject]@{
141
+ drive = $ld.DeviceID; volumeName = $ld.VolumeName; fileSystem = $ld.FileSystem
142
+ sizeGB = $sizeGB; usedGB = [math]::Round($sizeGB - $freeGB, 2); freeGB = $freeGB
143
+ usedPercent = if ($sizeGB -gt 0) { [math]::Round((($sizeGB - $freeGB) / $sizeGB) * 100, 2) } else { 0 }
144
+ }
145
+ }
146
+ [PSCustomObject]@{ physicalDisks = $diskList; logicalDrives = $partitionList; diskCount = $diskList.Count; totalSizeGB = [math]::Round(($diskList | Measure-Object -Property sizeGB -Sum).Sum, 2) }
147
+ }
148
+
149
+ function Get-NetworkInfo {
150
+ param($includeWifi = $false)
151
+ # 网卡配置缓存,每次只更新性能计数器(网速)
152
+ $needRefresh = (-not $script:networkAdapterCache) -or (-not $script:cacheTime.ContainsKey('network')) -or ((Get-Date) - $script:cacheTime['network']).TotalSeconds -gt $NetworkCacheTtl
153
+ if ($needRefresh) {
154
+ $script:cacheTime['network'] = Get-Date
155
+ $adapters = Get-CimInstance Win32_NetworkAdapter | Where-Object { $_.PhysicalAdapter -eq $true -or $_.NetConnectionID }
156
+ $configs = Get-CimInstance Win32_NetworkAdapterConfiguration | Where-Object { $_.IPEnabled }
157
+ $cached = @()
158
+ foreach ($a in $adapters) {
159
+ $cfg = $configs | Where-Object { $_.Index -eq $a.Index } | Select-Object -First 1
160
+ $counterName = $a.Name -replace '\(', '[' -replace '\)', ']'
161
+ $cached += [PSCustomObject]@{
162
+ index = $a.Index; name = $a.Name; connectionName = $a.NetConnectionID; macAddress = $a.MACAddress
163
+ speedMbps = if ($a.Speed) { [math]::Round($a.Speed / 1MB, 0) } else { $null }
164
+ status = $a.NetConnectionStatus; isPhysical = $a.PhysicalAdapter; counterName = $counterName
165
+ ipAddresses = if ($cfg) { $cfg.IPAddress } else { @() }
166
+ defaultIpGateway = if ($cfg) { $cfg.DefaultIPGateway } else { @() }
167
+ dnsServers = if ($cfg) { $cfg.DNSServerSearchOrder } else { @() }
168
+ }
169
+ }
170
+ $script:networkAdapterCache = $cached
171
+ }
172
+ # 每次只查性能计数器(网速)
173
+ $ifStats = Get-CimInstance Win32_PerfFormattedData_Tcpip_NetworkInterface
174
+ $wifi = $null
175
+ if ($includeWifi) {
176
+ try {
177
+ $wlan = netsh wlan show interfaces 2>$null
178
+ if ($wlan -match 'SSID') {
179
+ $ssid = ($wlan | Select-String 'SSID' | Select-Object -First 1).ToString() -replace '.*:\s*', ''
180
+ $signal = ($wlan | Select-String 'Signal' | Select-Object -First 1).ToString() -replace '.*:\s*', '' -replace '%', ''
181
+ $wifi = [PSCustomObject]@{ ssId = $ssid.Trim(); signalPercent = if ($signal) { [int]$signal.Trim() } else { $null } }
182
+ }
183
+ } catch {}
184
+ }
185
+ $adapterList = @()
186
+ foreach ($c in $script:networkAdapterCache) {
187
+ $stat = $ifStats | Where-Object { $_.Name -eq $c.connectionName -or $_.Name -eq $c.counterName } | Select-Object -First 1
188
+ $adapterList += [PSCustomObject]@{
189
+ name = $c.name; connectionName = $c.connectionName; macAddress = $c.macAddress
190
+ speedMbps = $c.speedMbps; status = $c.status; isPhysical = $c.isPhysical
191
+ ipAddresses = $c.ipAddresses; defaultIpGateway = $c.defaultIpGateway; dnsServers = $c.dnsServers
192
+ bytesReceivedPerSec = if ($stat) { $stat.BytesReceivedPerSec } else { $null }
193
+ bytesSentPerSec = if ($stat) { $stat.BytesSentPerSec } else { $null }
194
+ packetsReceivedPerSec = if ($stat) { $stat.PacketsReceivedPerSec } else { $null }
195
+ packetsSentPerSec = if ($stat) { $stat.PacketsSentPerSec } else { $null }
196
+ }
197
+ }
198
+ $active = $adapterList | Where-Object { $_.status -eq 2 -and $_.ipAddresses.Count -gt 0 }
199
+ [PSCustomObject]@{
200
+ adapters = $adapterList; activeAdapters = $active; adapterCount = $adapterList.Count; activeAdapterCount = @($active).Count
201
+ wifi = $wifi
202
+ totalDownloadBytesPerSec = ($active | Measure-Object -Property bytesReceivedPerSec -Sum).Sum
203
+ totalUploadBytesPerSec = ($active | Measure-Object -Property bytesSentPerSec -Sum).Sum
204
+ }
205
+ }
206
+
207
+ function Get-BatteryInfo {
208
+ $batteries = Get-CimInstance Win32_Battery
209
+ $portable = Get-CimInstance Win32_PortableBattery
210
+ if (-not $batteries -or $batteries.Count -eq 0) {
211
+ return [PSCustomObject]@{ hasBattery = $false; batteries = @() }
212
+ }
213
+ $batteryList = @()
214
+ foreach ($b in $batteries) {
215
+ $p = $portable | Where-Object { $_.DeviceID -eq $b.DeviceID } | Select-Object -First 1
216
+ $designCap = if ($p -and $p.DesignCapacity) { $p.DesignCapacity } else { $null }
217
+ $fullCap = if ($p -and $p.FullChargeCapacity) { $p.FullChargeCapacity } else { $null }
218
+ $batteryList += [PSCustomObject]@{
219
+ name = $b.Name; deviceId = $b.DeviceID; status = $b.Status
220
+ batteryStatusValue = $b.BatteryStatus
221
+ batteryStatus = switch ($b.BatteryStatus) {
222
+ 1 {'Discharging'} 2 {'AC Power'} 3 {'Fully Charged'} 6 {'Charging'} 11 {'Partially Charged'} default {'Unknown'}
223
+ }
224
+ chargePercent = $b.EstimatedChargeRemaining
225
+ estimatedRunTimeMinutes = if ($b.EstimatedRunTime -lt 10000) { $b.EstimatedRunTime } else { $null }
226
+ voltagemV = $b.DesignVoltage
227
+ designCapacitymWh = $designCap; fullChargeCapacitymWh = $fullCap
228
+ healthPercent = if ($designCap -and $fullCap -and $designCap -gt 0) { [math]::Round(($fullCap / $designCap) * 100, 2) } else { $null }
229
+ cycleCount = if ($p) { $p.CycleCount } else { $null }
230
+ manufacturer = if ($p) { $p.Manufacturer } else { $null }
231
+ chemistry = if ($p) { $p.Chemistry } else { $null }
232
+ }
233
+ }
234
+ [PSCustomObject]@{
235
+ hasBattery = $true; batteries = $batteryList
236
+ isCharging = @($batteryList | Where-Object { $_.batteryStatusValue -in 6,7,8,9 }).Count -gt 0
237
+ overallChargePercent = ($batteryList | Measure-Object -Property chargePercent -Average).Average
238
+ }
239
+ }
240
+
241
+ function Get-GpuInfo {
242
+ $gpus = Get-CimInstance Win32_VideoController
243
+ $monitors = Get-CimInstance Win32_DesktopMonitor
244
+ $gpuList = @()
245
+ foreach ($g in $gpus) {
246
+ $hasResolution = $g.CurrentHorizontalResolution -ne $null -and $g.CurrentHorizontalResolution -gt 0
247
+ $nameLower = $g.Name.ToLower()
248
+ $isVirtual = (-not $hasResolution) -or $nameLower -match 'idd|virtual|mirror|dummy|wddm|remote'
249
+ if ($hasResolution) {
250
+ $displayMode = "$($g.CurrentHorizontalResolution)x$($g.CurrentVerticalResolution)@$($g.CurrentRefreshRate)Hz"
251
+ } elseif ($isVirtual) {
252
+ $displayMode = 'Virtual Display'
253
+ } else {
254
+ $displayMode = 'No Active Output'
255
+ }
256
+ $gpuList += [PSCustomObject]@{
257
+ name = $g.Name; isVirtual = $isVirtual; displayMode = $displayMode
258
+ adapterRAMMB = if ($g.AdapterRAM) { [math]::Round($g.AdapterRAM / 1MB, 2) } else { $null }
259
+ driverVersion = $g.DriverVersion; videoProcessor = $g.VideoProcessor
260
+ currentHorizontalResolution = $g.CurrentHorizontalResolution; currentVerticalResolution = $g.CurrentVerticalResolution
261
+ currentRefreshRate = $g.CurrentRefreshRate; currentBitsPerPixel = $g.CurrentBitsPerPixel
262
+ }
263
+ }
264
+ $monitorList = @()
265
+ foreach ($m in $monitors) {
266
+ $monitorList += [PSCustomObject]@{ name = $m.Name; monitorManufacturer = $m.MonitorManufacturer; screenHeight = $m.ScreenHeight; screenWidth = $m.ScreenWidth }
267
+ }
268
+ $primary = $gpuList | Where-Object { -not $_.isVirtual -and $_.currentHorizontalResolution } | Select-Object -First 1
269
+ [PSCustomObject]@{ gpus = $gpuList; monitors = $monitorList; gpuCount = $gpuList.Count; monitorCount = $monitorList.Count; primaryResolution = if ($primary) { $primary.displayMode } else { $null } }
270
+ }
271
+
272
+ function Get-UsbInfo {
273
+ $controllers = Get-CimInstance Win32_USBController
274
+ $devices = Get-CimInstance Win32_PnPEntity | Where-Object { $_.PNPDeviceID -like 'USB\*' -and $_.Name -notlike 'USB Root Hub*' -and $_.Name -notlike 'USB Composite Device*' }
275
+ $controllerList = @()
276
+ foreach ($c in $controllers) { $controllerList += [PSCustomObject]@{ name = $c.Name; manufacturer = $c.Manufacturer; status = $c.Status } }
277
+ $deviceList = @()
278
+ foreach ($d in $devices) { $deviceList += [PSCustomObject]@{ name = $d.Name; description = $d.Description; manufacturer = $d.Manufacturer; status = $d.Status; service = $d.Service } }
279
+ [PSCustomObject]@{ controllers = $controllerList; controllerCount = $controllerList.Count; devices = $deviceList; deviceCount = $deviceList.Count }
280
+ }
281
+
282
+ function Get-ProcessesInfo {
283
+ param($top = 0, $sortBy = 'memory', $includeCmdLine = $false)
284
+ $sortProp = switch ($sortBy) { 'cpu' { 'CPU' } 'name' { 'ProcessName' } default { 'WorkingSet64' } }
285
+ $procs = Get-Process | Sort-Object -Property $sortProp -Descending
286
+ if ($top -gt 0) { $procs = $procs | Select-Object -First $top }
287
+ $cmdLineMap = @{}
288
+ if ($includeCmdLine) {
289
+ foreach ($w in (Get-CimInstance Win32_Process | Select-Object ProcessId, CommandLine)) { $cmdLineMap[$w.ProcessId] = $w.CommandLine }
290
+ }
291
+ $list = @()
292
+ foreach ($p in $procs) {
293
+ $list += [PSCustomObject]@{
294
+ pid = $p.Id; name = $p.ProcessName
295
+ cpuSeconds = if ($p.CPU) { [math]::Round($p.CPU, 2) } else { 0 }
296
+ memoryMB = [math]::Round($p.WorkingSet64 / 1MB, 2); memoryPrivateMB = [math]::Round($p.PrivateMemorySize64 / 1MB, 2)
297
+ threads = $p.Threads.Count; handles = $p.HandleCount
298
+ startTime = if ($p.StartTime) { $p.StartTime.ToString('o') } else { $null }
299
+ path = $p.Path; company = $p.Company
300
+ commandLine = if ($includeCmdLine -and $cmdLineMap.ContainsKey($p.Id)) { $cmdLineMap[$p.Id] } else { $null }
301
+ }
302
+ }
303
+ [PSCustomObject]@{ total = (Get-Process).Count; returned = $list.Count; processes = $list }
304
+ }
305
+
306
+ function Get-ServicesInfo {
307
+ param($runningOnly = $false)
308
+ $filter = if ($runningOnly) { '-Filter "State=''Running''"' } else { '' }
309
+ $services = Invoke-Expression "Get-CimInstance Win32_Service $filter" | Sort-Object Name
310
+ $list = @()
311
+ foreach ($s in $services) {
312
+ $list += [PSCustomObject]@{
313
+ name = $s.Name; displayName = $s.DisplayName; state = $s.State; status = $s.Status
314
+ startMode = $s.StartMode; startName = $s.StartName; pathName = $s.PathName
315
+ description = $s.Description; processId = $s.ProcessId; acceptStop = $s.AcceptStop; serviceType = $s.ServiceType
316
+ }
317
+ }
318
+ [PSCustomObject]@{ total = $list.Count; running = @($list | Where-Object { $_.state -eq 'Running' }).Count; stopped = @($list | Where-Object { $_.state -eq 'Stopped' }).Count; services = $list }
319
+ }
320
+
321
+ function Get-AllInfo {
322
+ param($includeProcesses = $false, $includeServices = $false)
323
+ $result = [PSCustomObject]@{
324
+ timestamp = (Get-Date).ToString('o')
325
+ system = Get-SystemInfo
326
+ memory = Get-MemoryInfo
327
+ disk = Get-DiskInfo
328
+ network = Get-NetworkInfo
329
+ battery = Get-BatteryInfo
330
+ gpu = Get-GpuInfo
331
+ usb = Get-UsbInfo
332
+ }
333
+ if ($includeProcesses) { $result | Add-Member -NotePropertyName processes -NotePropertyValue (Get-ProcessesInfo -top 20) }
334
+ if ($includeServices) { $result | Add-Member -NotePropertyName services -NotePropertyValue (Get-ServicesInfo) }
335
+ return $result
336
+ }
337
+
338
+ # ========== 主循环 ==========
339
+
340
+ Write-Output "READY"
341
+
342
+ while ($true) {
343
+ $line = [Console]::In.ReadLine()
344
+ if ($null -eq $line) { break }
345
+ $line = $line.Trim()
346
+ if ($line -eq "exit" -or $line -eq "quit") { break }
347
+ if (-not $line) { continue }
348
+
349
+ $parts = $line -split ':'
350
+ $cmd = $parts[0]
351
+
352
+ try {
353
+ $data = switch ($cmd) {
354
+ "system" { Get-SystemInfo }
355
+ "memory" { Get-MemoryInfo }
356
+ "disk" { Get-DiskInfo }
357
+ "network" {
358
+ $incWifi = if ($parts[1]) { [bool]::Parse($parts[1]) } else { $false }
359
+ Get-NetworkInfo -includeWifi $incWifi
360
+ }
361
+ "battery" { Get-BatteryInfo }
362
+ "gpu" { Get-GpuInfo }
363
+ "usb" { Get-UsbInfo }
364
+ "processes" {
365
+ $top = if ($parts[1]) { [int]$parts[1] } else { 0 }
366
+ $sortBy = if ($parts[2]) { $parts[2] } else { "memory" }
367
+ $includeCmd = if ($parts[3]) { [bool]::Parse($parts[3]) } else { $false }
368
+ Get-ProcessesInfo -top $top -sortBy $sortBy -includeCmdLine $includeCmd
369
+ }
370
+ "services" {
371
+ $runningOnly = if ($parts[1]) { [bool]::Parse($parts[1]) } else { $false }
372
+ Get-ServicesInfo -runningOnly $runningOnly
373
+ }
374
+ "all" {
375
+ $incProc = if ($parts[1]) { [bool]::Parse($parts[1]) } else { $false }
376
+ $incSvc = if ($parts[2]) { [bool]::Parse($parts[2]) } else { $false }
377
+ Get-AllInfo -includeProcesses $incProc -includeServices $incSvc
378
+ }
379
+ default { [PSCustomObject]@{ error = "Unknown command: $cmd" } }
380
+ }
381
+ Write-Output ($data | ConvertTo-Json -Depth 8 -Compress)
382
+ } catch {
383
+ $errMsg = $_.Exception.Message -replace '"', '\"'
384
+ Write-Output "{`"error`":`"$errMsg`"}"
385
+ }
386
+ }