I was troubleshooting a performance issue on my laptop when I noticed something that stopped me cold.
My CPU was sitting at 20% load. Nothing crazy. Just a browser, a terminal, and a couple of background tasks. But the temperature readout I was watching — thrown together in an afternoon — showed 83°C on the hottest core.
Then I checked the battery. 74.4% health after 8 years. The designed capacity was 45,000 mWh. The battery now maxes out at 33,480 mWh. Windows just says "100% charged" like nothing is wrong.
Task Manager told me none of this. Neither did any monitoring software I trusted — they all wanted to install tray icons, telemetry services, or drivers I didn't ask for.
So I built my own.
The Problem with Windows Monitoring
Windows exposes a lot of hardware data. The problem is it's scattered across three different APIs, one of them requires a kernel driver you probably don't have, and none of it is surfaced in the default UI.
- Task Manager: CPU load %, memory GB. That's about it.
- Resource Monitor: Slightly more, still no temps.
- Performance Monitor: Overwhelming. Not useful for at-a-glance health.
- Third-party tools: HWiNFO64 is great but it's a full application. GPU-Z, CPU-Z — each does one thing. Nothing unified and scriptable.
The goal: one Python script, no installer, reads what Windows actually knows about your hardware, and prints it cleanly.
Part 1 — GPU Stats via nvidia-smi
The easiest piece. NVIDIA ships nvidia-smi with every driver install. It has a machine-readable --query-gpu mode that outputs CSV:
import subprocess
r = subprocess.run(
[
"nvidia-smi",
"--query-gpu=temperature.gpu,utilization.gpu,memory.used,memory.free",
"--format=csv,noheader"
],
capture_output=True,
text=True
)
temp, util, used, free = r.stdout.strip().split(", ")
total_mib = int(used.split()[0]) + int(free.split()[0])
print(f"GPU: {temp}C, {util} load, {used}/{total_mib} MiB VRAM")
The --format=csv,noheader flag gives you clean values with units attached (62 C, 45 %, 3273 MiB). Split on ", ", strip the units when you need integers, done.
For the VRAM bar graph I used a simple ratio:
def bar(used, total, width=30):
filled = int((used / total) * width)
return "X" * filled + "." * (width - filled)
Part 2 — Battery Wear via WMI (the interesting one)
Most people don't know this WMI class exists. BatteryFullChargedCapacity and BatteryStaticData live in the root/wmi namespace — not the default root/cimv2 that most WMI examples use.
import wmi
w = wmi.WMI(namespace="root/wmi")
full = list(w.BatteryFullChargedCapacity())[0].FullChargedCapacity
design = list(w.BatteryStaticData())[0].DesignedCapacity
health_pct = round(full / design * 100, 1)
print(f"Battery health: {health_pct}% ({full} / {design} mWh)")
DesignedCapacity is what the manufacturer rated the battery at when it left the factory. FullChargedCapacity is what it can actually hold right now. The ratio is your wear percentage.
This is the same data that powercfg /batteryreport uses internally — you are just reading it directly instead of parsing an HTML report.
My battery: 33,480 / 45,000 = 74.4%. After 8 years that is actually respectable. But I would not have known without digging into this WMI namespace.
Part 3 — SSD SMART via PowerShell StorageReliabilityCounter
Windows 8+ ships with Get-StorageReliabilityCounter, a PowerShell cmdlet that wraps SMART data without needing smartmontools or admin elevation for basic stats:
import subprocess, json
ps = (
"Get-PhysicalDisk | "
"Get-StorageReliabilityCounter | "
"Select-Object PowerOnHours,Temperature,Wear | "
"ConvertTo-Json"
)
r = subprocess.run(
["powershell", "-Command", ps],
capture_output=True,
text=True
)
data = json.loads(r.stdout)
print(
f"SSD: {data['PowerOnHours']} hours on, "
f"{data['Temperature']}C, wear: {data['Wear']}%"
)
PowerOnHours is cumulative lifetime hours. Wear is the SMART wear indicator (0 = new, 100 = end of life). Temperature is the drive's current thermal reading.
My T-FORCE VULCAN Z: 9,101 hours on, 33C, 0% wear. Still healthy.
One caveat: if you have multiple drives, ConvertTo-Json returns an array. Wrap data in a list check (if isinstance(data, list)) and iterate.
Part 4 — CPU Temps via LibreHardwareMonitor WMI
This one is the trickiest. Windows intentionally blocks direct MSR (Model Specific Register) reads from userspace — that is where the actual thermal sensor data lives. To read CPU core temps without a kernel driver, you need something that already has ring-0 access to expose the data through a friendlier API.
LibreHardwareMonitor solves this. When it is running, it exposes all sensor data through its own WMI namespace: root/LibreHardwareMonitor.
import wmi
lhm = wmi.WMI(namespace="root/LibreHardwareMonitor")
sensors = lhm.Sensor()
cpu_temps = [
s for s in sensors
if s.SensorType == "Temperature" and "CPU Core" in s.Name
]
for sensor in sorted(cpu_temps, key=lambda s: s.Name):
temp = float(sensor.Value)
flag = " [HOT]" if temp >= 80 else ""
print(f" {sensor.Name:<20} {temp}C{flag}")
The dependency chain: run LibreHardwareMonitor in the background, then query its WMI namespace from Python. The tool handles this check at startup and warns if LHM is not running.
What the Output Looks Like
============================================================
DESKTOP-V0J6K6M -- Hardware Monitor 2026-05-27 19:00
============================================================
[CPU] Intel Core i7-8750H
Load: 20% Clock: 2208 MHz
CPU Core #1 83.0C [HOT]
CPU Core #2 70.0C
CPU Core #3 72.0C
CPU Core #4 68.0C
CPU Core #5 71.0C
CPU Core #6 69.0C
CPU Package 83.0C Power: 25.2W
[GPU] NVIDIA GeForce GTX 1050 Ti
Temp: 62C
VRAM: [############################..] 3273/4005 MiB
[Storage] T-FORCE VULCAN Z 512GB
Temp: 33C On: 9101 hrs Wear: 0%
[Battery] L17M3PG1
Charge: 100%
Health: [######################........] 74.4%
============================================================
The [HOT] flag triggers at 80C for cores, 85C for package. Thresholds are constants you can tune.
Building It Out
The script grew from "print some temps" into something with a few more features I kept reaching for:
-
--watchmode that refreshes every N seconds (clears the terminal each cycle) -
--jsonflag that dumps all readings as structured JSON for piping into other tools - Graceful degradation: if nvidia-smi is not found, the GPU section is skipped cleanly instead of crashing
- Battery section only appears on laptops (checks if
BatteryStaticDatareturns any results)
The watch mode is where it became genuinely useful. Seeing the temps respond in real time when you open a heavy application tells you a lot more than a one-shot reading.
If You Don't Want to Build It Yourself
I packaged the full version — --watch mode, --json export, the LHM detection logic, and thresholds already tuned — and put it on Gumroad for $5: AI Hardware Monitor for Windows
It is a single .py file. No installer, no tray icon, no telemetry. Dependencies are wmi (pip) and LibreHardwareMonitor for CPU temps. Everything else is stdlib.
If you just want the battery and SSD pieces, the snippets above are complete as-is. The WMI namespaces are the non-obvious part — once you have those, the rest is straightforward Python.
Summary
| Data | API | Needs |
|---|---|---|
| GPU temp, VRAM, utilization |
nvidia-smi subprocess |
NVIDIA GPU + driver |
| Battery health % |
root/wmi via BatteryFullChargedCapacity
|
Laptop, Python wmi package |
| SSD hours, temp, wear | PowerShell Get-StorageReliabilityCounter
|
Windows 8+, no elevation needed |
| CPU per-core temps |
root/LibreHardwareMonitor WMI |
LibreHardwareMonitor running |
The battery one is the sleeper. Most people have no idea root/wmi exposes this. Check yours — you might be surprised what your laptop is hiding from you.
Top comments (0)