Releases: SecretLUL/WinMedic
Release list
v0.4.1
WinMedic v0.4.1
The desktop window that arrived in v0.4.0 gets the interface it was missing, and three findings that survived their own repair stop being raised.
Nothing here changes what WinMedic does to a system beyond the three checks below, which now measure what their repairs can actually reach.
Fixed
Three findings that no repair could clear
Scan, repair, reboot, scan again — and there they were, unchanged. Each of the three measured something the repair it offered could not touch. Diagnosed against a real machine's last_scan.json, audit log and registry rather than from first principles.
wu_reboot_pending — a key the restart leaves behind. The check tested whether Component Based Servicing\RebootPending exists. Windows creates it while servicing and files the outstanding work underneath it; the restart consumes that work and empties the key, but leaves the key itself in place, permanently. On the machine this was found on it had been sitting there empty — 0 subkeys, 0 values — while Auto Update\RebootRequired was absent: a restart that no restart could clear. The CBS key now counts only while it still holds entries, and the two signals a boot really does clear are read alongside it, Auto Update\RebootRequired and PendingFileRenameOperations.
sys_clean_winsxs — a repair that could not move the number. The finding was raised on DISM's own "cleanup recommended" verdict, which also weighs backups and disabled features — reclaimable only by /ResetBase, which WinMedic deliberately never runs. StartComponentCleanup, the one repair offered here, removes superseded packages and nothing else. With 4.43 GB of backups and zero reclaimable packages the flag stayed Yes however often the repair ran, DISM answered "completed successfully" every time, and the audit log recorded a cleaned store. The finding now needs a reclaimable package to exist, the repair reads the store back afterwards and reports what is left, and both DISM calls pass /English — the output arrives in the console code page, so from_utf8_lossy had already cost the parser the store-size and cache lines on a German machine.
sys_clean_setup_logs — the scan finding its own log. "13.2 MB, 2 files" were CBS.log, held open by TrustedInstaller since it was created a month earlier, and dism.log, written by this module's own AnalyzeComponentStore call 34 seconds before the same scan measured it. Both the measurement and the sweep now ask Windows for DELETE access first — the same question remove_file asks later, and with a fully permissive share mode the probe blocks nobody. What is counted is what can be removed; a rotated CbsPersist_*.log still is.
The issue list could not be ticked
Nothing in the findings list responded to a click. The row was an egui::Frame whose response was given a click sense afterwards, and a Frame registers its response after everything drawn inside it — so the row's click target sat on top of its own checkbox and swallowed every click meant for it. The boxes were drawn, they just could not be reached.
The row is now a Ui built with UiBuilder::sense, which registers itself before its contents and leaves the checkbox on top. Ticking a box also moves the detail pane to that finding, the way space already did.
The test suite no longer overwrites the last scan
save_scan_state wrote to %APPDATA%\WinMedic\last_scan.json from any App, including the dozens the suite builds — so ticking a checkbox in a test overwrote the scan the developer's own WinMedic had left behind. Persistence now sits behind the same SystemActions seam that already guards the browser, the UAC prompt and restore points: off by default, switched on by the desktop front end through enable_real_system_actions.
Changed
A desktop interface instead of a terminal layout in a window
v0.4.0 replaced the TUI with a native window but kept the dense terminal layout inside it. That layout is gone: a fixed sidebar, clearer page headings, larger spacing, muted dark surfaces and teal accents, Segoe UI where it is available with bundled fonts as a fallback, and navigation icons drawn as scalable geometry.
The dashboard groups a circular health indicator, live resources and diagnostic module cards, and before the first scan it shows an explicit unscanned state rather than a health score of 100. Severity links open the corresponding findings and clear stale search and module filters. The same cards, buttons and typography carry across scanning, triage, repairs and settings; confirmation dialogs use a dimmed modal backdrop that blocks clicks on the navigation behind it; scan and repair buttons respect the busy state, and long status messages truncate with the full text on hover.
Severity marks are drawn, not written
An octagon for critical, a rounded triangle for warning, a disc for info. egui's bundled fonts have no warning sign in them, so a written mark would have reached some machines as an empty box — the same reason the navigation icons are painted. They replace the [!] CRITICAL text badges in the list, the detail pane, the filter chips and the dashboard cards, and each carries its name for a screen reader. Severity::badge keeps the written form for the report file, which is now its only caller.
Verification
cargo fmt, cargo clippy --all-targets -- -D warnings and the full suite pass: 361 unit and 266 integration tests, 8 of them new for the three findings above — an empty CBS key, a CBS key still holding work, a recommendation with nothing reclaimable, a cleanup that reclaimed nothing, and a log held open with FILE_SHARE_NONE that is neither counted nor reported as a failed deletion. The GUI tests apply the actual theme and cover all five destinations at 960×640, 1400×900 and 1920×1200.
Full changelog: v0.4.0...v0.4.1
v0.4.0
What's Changed
- Merge Backups & Logs into Settings, make scan progress legible, and version releases automatically by @SecretLUL in #34
- feat(updater): install updates in place after verifying their checksum by @SecretLUL in #35
- chore(release): bring main up to v0.3.4 by @SecretLUL in #36
- ci(release): fail the run when the version bump reaches nobody by @SecretLUL in #37
- feat(ci): publish every release to WinGet by @SecretLUL in #38
- feat(modules): add Scheduled Tasks and Page File & Memory diagnostics by @SecretLUL in #43
- fix(modules): format the task id hash without LowerHex by @SecretLUL in #44
- deps: bump sha2 from 0.10.9 to 0.11.0 by @dependabot[bot] in #39
- feat(gui)!: replace the terminal interface with an egui desktop window by @SecretLUL in #48
- Release v0.4.0: WHEA Logger, Crash Dump & BSOD Analyzer, Scan State Persistence & TUI Improvements by @SecretLUL in #46
Full Changelog: v0.3.2...v0.4.0
v0.3.4
What's Changed
- Merge Backups & Logs into Settings, make scan progress legible, and version releases automatically by @SecretLUL in #34
Full Changelog: v0.3.2...v0.3.4
v0.3.2
What's Changed
- feat(repair): explain failing repairs in the log and fix the two that failed by @SecretLUL in #33
Full Changelog: v0.3.1...v0.3.2
v0.3.1
What's Changed
- feat(ui): BIOS tab navigation, numeric settings modal, emoji removal & TUI polish by @SecretLUL in #32
Full Changelog: v0.3.0...v0.3.1
v0.3.0
WinMedic v0.3.0
WinMedic now speaks English end to end — TUI, CLI, reports, audit log and safety layer.
Alongside that: two safety-layer bugs that reported protection they had not provided, a settings-loss bug, a hardened way to build PowerShell scripts, app.rs split into nine focused modules with key dispatch finally under test, and a full dependency refresh that clears the last three open advisories.
Breaking
The interface language changed from German to English
Every user-facing string moved: the dashboard, scanner, triage list, repair view, history, settings, help overlay and footer; all CLI output and exit-code descriptions; the HTML, Markdown and CLI reports (the HTML report's lang attribute went from de to en); config setting labels; audit-log entries; and the error messages from safety::restore_point, safety::reg_backup and utils::updater.
If you parse WinMedic's output — scraping the Markdown report, grepping CLI output, reading the JSONL audit log — those strings have changed. The JSON report's field names are unaffected; only human-readable values were translated.
Four German string comparisons stayed German on purpose, because they match localized Windows output rather than WinMedic's own text:
| Where | Matches | From |
|---|---|---|
system_integrity.rs |
beschädigt, reparierbar |
SFC / DISM |
storage.rs |
beschädigt |
chkdsk / fsutil |
network.rs |
Fehler |
netsh |
system_cleaner.rs |
German branch of parse_winsxs_analysis |
DISM |
Translating those would silently break detection on German-language Windows — the opposite of what the change was for. The German DISM fixtures in the test suite exist to prove that path still works, so they stay German too.
Closes #9.
Fixed
Settings no longer reset themselves silently
Config::load() could not distinguish "no config file yet" from "the config file is unreadable" — both returned Self::default() without a word. On a first run that is correct; on a truncated or hand-edited file it meant every saved setting reverted. Because create_vss_before_repair and auto_backup_registry both default to on, options a user had deliberately switched off came back on unannounced. The next save() then overwrote the unreadable file, destroying the evidence.
load_from now returns a ConfigStatus alongside the config: Missing for a first run, which stays silent; Loaded; or Corrupt, carrying the parse error and where the old file was preserved. An unusable file is renamed to config.json.corrupt-<ts> at load time, before anything can overwrite it. The TUI shows the warning in the status line at startup; the headless path prints it to stderr, where it cannot corrupt --json output being piped somewhere.
save_to writes to a temp file and renames over the target, so an interrupted save leaves the previous config intact instead of the truncated file that caused the problem in the first place. A config written by an older version — valid JSON with fields missing — still loads cleanly rather than being mistaken for corruption.
Closes #7.
Relaunching as Administrator no longer fails outright
Elevating from within WinMedic built Start-Process -FilePath '…' -ArgumentList '…' -Verb RunAs unconditionally. Started without command-line arguments — which is how the TUI is normally launched — that produced -ArgumentList '', and Windows PowerShell rejects an empty string for that parameter:
Das Argument für den Parameter "ArgumentList" kann nicht überprüft werden. Das Argument ist NULL oder leer.
So the elevation prompt never appeared and the relaunch died with a parameter binding error. -ArgumentList is now omitted when there is nothing to pass.
Two further defects in the same call, both invisible while the first one masked it: the arguments were joined into one space-separated string, so an argument containing a space — an --output path, say — arrived at the elevated process split in two; and neither the executable path nor the arguments were escaped, so an apostrophe in either would end the single-quoted literal and run the remainder as code in a process that is about to be elevated. A Windows profile directory can contain one (C:\Users\O'Brien\…). Both now go through utils::cmd::ps_single_quoted, the helper added for #6 — this call site had been missed.
Covered by five tests against the generated script.
The safety layer no longer reports protection it did not provide
Two failures, both of which claimed success while leaving the user unprotected:
Backup index. list_backups() collapsed "index is unreadable" into "no backups exist" via unwrap_or_default(), and save_record_index() rebuilt the file from exactly that empty list — so a single malformed byte erased every recorded backup on the next export, taking the rollback UI's only pointer to the .reg files with it. Reading is now fallible and distinguishes a missing index from a corrupt one; a corrupt index is moved aside as index.json.corrupt-<timestamp> instead of overwritten; writes go through a temp file and rename; and a failure to record a backup propagates, which the registry module already treats as "abort the fix".
Restore points. Windows declines to create one if another was made within the last 24 hours, and reports that refusal as a warning — so -ErrorAction Stop never fired and the script printed its success banner anyway. The checkpoint now compares restore point sequence numbers before and after, which also avoids matching on localized status text, and reports Created / Throttled / Unverified / Failed distinctly. Only a confirmed new restore point counts as protection.
Security
One safe way to interpolate into PowerShell
run_powershell hands a script string to powershell -Command, in a process that is usually elevated. Any caller interpolating a runtime value into that string could end the literal early and have the remainder run as code. Only one site ever interpolated — the restore point description — and it was fixed by escaping, so there was no live vulnerability. The gap was that nothing made the next dynamic script safe.
utils::cmd::ps_single_quoted is now the documented way to do it. It returns the value with its surrounding quotes, so a caller cannot quote it twice or forget the quotes. Single-quoted PowerShell strings do not interpolate at all — $var, $(...), @(...) and backtick escapes are inert inside them — which leaves ' as the only metacharacter, escaped by doubling. No allow-list needed, and no encoding the caller failed to anticipate can defeat it.
Closes #6.
Internals
app.rs split into nine modules
app.rs was 1405 lines and 57 functions holding UI state, filtering, search, run orchestration, backup listing, the confirmation modal and the update notice all at once — the file every feature had to touch, and therefore both the likeliest merge conflict and the hardest place to review a change.
| Module | Lines | Responsibility |
|---|---|---|
mod.rs |
66 | constants, BackgroundEvent, re-exports |
settings.rs |
97 | settings navigation and persistence |
run_control.rs |
162 | starting, cancelling and simulating runs |
history.rs |
186 | backups, restore points, rollback |
confirm.rs |
230 | confirmation modal, parked update notice |
events.rs |
308 | draining scan/repair/background channels |
filters.rs |
352 | severity and module filters, live search |
state.rs |
382 | the App struct, telemetry, log buffers |
input.rs |
447 | key dispatch |
The public API is unchanged — winmedic::app::{App, BackgroundEvent, ConfirmRequest, TAB_*} still resolve, and the integration tests needed no edits.
The substantive part is handle_key. It lived in main.rs, which put the single largest piece of interaction logic in the binary target where no test can reach it. Moving it into app::input put it somewhere testable, and it now covers behaviour that was previously only verifiable by hand: a pending confirmation swallows every other key, search mode captures q as a character instead of quitting, severity filters only bind on the triage tab, and Esc clears filters before it navigates away.
Closes #8.
The Clippy gate actually applies to the whole crate now
src/lib.rs carried eight blanket #![allow(...)]s, which quietly weakened clippy -D warnings in CI for every file. Removing them surfaced 34 warnings. Four of the eight lints had no findings at all — including dead_code, so that suppression was pure inherited baggage. The rest are fixed properly: nested conditionals collapsed into let-chains, Default impls added alongside the existing new() constructors, one consecutive str::replace. Four too_many_arguments sites keep a targeted #[allow] with a note explaining why.
CI & repository
Cargo.lockis enforced and the MSRV (Rust 1.88) is pinned and checked on every pull request, so the published binary is reproducible from the tagged tree.cargo auditruns on a Linux runner — it readsCargo.lockrather than compiling, so a Windows-only crate is still checked without spending Windows runner minutes. A vulnerability fails the build; an informational warning does not.- Dependabot watches cargo and the pinned actions weekly. Minor and patch bumps are grouped into one pull request; majors stay separate.
- Four full compiles per run became two and a half. The
checkjob folded intotest, and the release build — the most expensive job by a wide margin withltoandcodegen-units = 1— now runs only on pushes to main. - Contributor-facing repository files added —
CONTRIBUTING.md,SECURITY.mdand issue templates; the README documents the cleaner, the updater, checksum...
v0.2.0
WinMedic v0.2.0
Zwei neue Module — System & Cache Cleaner und Auto-Updater — dazu Report-Export, parallele Scans, eine testbare Kommando-Architektur und eine Test-Suite mit 335 Tests.
Das erste Release, das der eingebaute Updater auch sieht: v0.1.0 war als Pre-Release markiert und damit für /releases/latest unsichtbar.
Neue Features
System & Cache Cleaner mit WinSxS Deep Clean
Neues Diagnosemodul, das neun Speicherfresser findet und aufräumt:
| Bereich | Was passiert |
|---|---|
| WinSxS Component Store | DISM /AnalyzeComponentStore, danach StartComponentCleanup |
| Delivery Optimization | Delete-DeliveryOptimizationCache, dann manueller Sweep |
| Package Cache | Installer-Reste (RiskScore::High) |
| Browser-Caches | Chrome, Edge, Firefox, Brave, Opera — inkl. aller Profile |
| Setup-Logs | C:\Windows\Panther, CBS-Logs |
| Error Reporting | WER-Archive und Crash-Dumps |
| Shader- & Cert-Caches | D3DSCache, CryptnetUrlCache |
| Papierkorb | Clear-RecycleBin (RiskScore::High) |
| System-Temp | %TEMP%, C:\Windows\Temp |
Gesperrte Dateien werden übersprungen statt den Lauf abzubrechen. Konnte keine Datei entfernt werden, meldet das Modul einen Fehler statt einen leeren Erfolg. Package Cache und Papierkorb sind als RiskScore::High eingestuft und standardmäßig abgewählt — --auto-fix leert sie nicht unbeaufsichtigt. WinSxS-Ergebnisse werden nur ausgewertet, wenn DISM tatsächlich erfolgreich war; das Parsing versteht deutsche und englische DISM-Ausgaben.
Auto-Updater beim Start
Fragt beim Start das neueste GitHub-Release ab und meldet eine neue Version über [U], statt ein Modal aus einem Hintergrund-Event zu öffnen — so kann kein Dialog einen Tastendruck verschlucken. Der Download öffnet sich im Browser.
Abgesichert: vollständiges SemVer-Parsing inklusive Pre-Release-Ordering (1.0.0-beta < 1.0.0), Release-URLs müssen mit https://github.com/ beginnen und dürfen keine Shell-Metazeichen enthalten, Start über explorer.exe statt cmd /c start, Draft- und Pre-Releases werden übersprungen.
Report-Export
Befunde als HTML, Markdown oder JSON — über --output <pfad> oder [E] in der TUI.
Lauf-Steuerung
Abbrechbare Scans und Reparaturen, Rollback, Settings-Tab, Dry-Run-Modus und aussagekräftige Exit-Codes für Skripting.
Oberfläche
- Issue-Liste mit Severity-Filter [c/w/i], Modul-Filter [m], Live-Suche [/] und Reset [x]
- Log-Ansicht als Ring-Buffer (max. 2000 Zeilen) mit PgUp/PgDn/Home/End — lange Läufe fressen keinen Speicher mehr
- Live-Sub-Step-Logs während der Scans, SMART-Telemetrie für Laufwerke
- Dashboard-Karten brechen lange Modulnamen um statt sie abzuschneiden
- Eigenes Icon und PE-Versionsmetadaten in der
winmedic.exe
Performance
- Module scannen parallel über ein
tokio::JoinSetstatt nacheinander - Verzeichnis-Walks laufen in
spawn_blockingund blockieren die Async-Runtime nicht mehr - Audit-Log ist ein append-only JSONL mit O(1)-Schreibzugriff und automatischer Rotation; bestehende Logs werden migriert
Sicherheit & Korrektheit
- Admin-Erkennung über Win32
CheckTokenMembershipmit Elevation-Prompt in der TUI CleanerPathsals injizierbarer Dateisystem-Seam: Tests laufen in einer Sandbox statt gegen echte Browser-Caches, WER-Archive undC:\Windows\Pantherwindows_updates: Dateigrößen wurden auf volle Megabyte abgeschnitten, alles unter 1 MB fiel unter den Tisch — behoben- Update-Check aus
App::newherausgezogen; das Konstruieren einerAppmacht kein Netzwerk-I/O mehr - Gemeinsamer Verzeichnis-Walker in
utils::fs_stats, genutzt vonsystem_cleaner,storageundwindows_updates
Architektur & Tests
CommandRunner-Trait mitSystemCommandRunnerundMockCommandRunner— Module lassen sich ohne echte Systemaufrufe deterministisch testen- 335 Tests: 77 Unit-Tests plus 258 Integrationstests über vier Tiers (Features, Boundaries, Combinations, Scenarios) und mehrere adversariale Suites
- CI prüft Formatierung, Clippy (
-D warnings), Tests und den Release-Build aufwindows-latest
Release-Prozess
Pre-Release-Markierung folgt jetzt SemVer: Nur Tags mit Suffix (v0.3.0-rc1) sind Pre-Releases. Vorher galt jeder Tag vor v1.0 als Pre-Release — dadurch lieferte /releases/latest nichts und der Updater fand grundsätzlich kein Update.
Release Notes kommen aus docs/release-notes/<tag>.md; fehlt die Datei, generiert GitHub die Commit-Liste.
Vollständiges Changelog: v0.1.0...v0.2.0
WinMedic v0.1.0 – Initial Release
🩺 WinMedic v0.1.0
Initial release of WinMedic, the high-performance Windows Self-Healing & Diagnostic TUI in 100% Rust.
- Zero runtime dependencies (single native .exe)
- 6 Modular Diagnostic Engines (System Integrity, Windows Updates, Network, Event Logs, Storage, Registry/Autostart)
- Automated VSS Restore Points & Registry Backups before any repair
- Cyber-Medic Ratatui TUI with real-time telemetry
- Headless CLI support (--scan, --auto-fix, --json)