Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
711fa7a
fix(cleanup): guarantee wuauserv/bits restart even on delete failure
danlinyu May 9, 2026
2ffbc2a
fix(cleanup): never follow reparse points during recursive delete
danlinyu May 9, 2026
4698564
fix: replace hardcoded C:\Windows and C:\ProgramData with env vars
danlinyu May 9, 2026
7bb90ef
refactor: drop Format-Bytes-Local duplicate, use exported Format-Bytes
danlinyu May 9, 2026
384ba08
fix(diagnose): set AllowTelemetry policy so DiagTrack disable survive…
danlinyu May 9, 2026
10b1f21
fix(boost): dispose Process objects after EmptyWorkingSet
danlinyu May 9, 2026
d9c319f
docs(readme): cover all 5 tabs and 6 modules; note safety guarantees
danlinyu May 9, 2026
35b0fe8
feat(async): runspace-backed background ops with progress queues
danlinyu May 9, 2026
cf19ce2
feat(cleanup): add -OnProgress callback to Invoke-Cleanup
danlinyu May 9, 2026
c2e943a
feat(ui): run cleanup in background runspace; add Cancel button
danlinyu May 9, 2026
7c36bbb
feat(ui): run dedup scan in background runspace; add Cancel button
danlinyu May 9, 2026
9c25834
feat(ui): run diagnostics in background runspace
danlinyu May 9, 2026
e8f48e1
feat(ui): run Free RAM trim in background runspace
danlinyu May 9, 2026
c44b236
feat(ui): cancel background ops + stop pollers on window close
danlinyu May 9, 2026
f49aa7c
fix(async): Receive-AsyncProgress returns empty array, not null
danlinyu May 9, 2026
4d0accc
fix(diagnose): emit findings unrolled so @(Invoke-Diagnostics) works
danlinyu May 9, 2026
18a71e5
test: add Pester 5 suite covering all 7 modules
danlinyu May 9, 2026
b253281
ci: add GitHub Actions workflow + Run-Tests.ps1 wrapper
danlinyu May 9, 2026
ccdc946
fix(ui): move poll-tick bodies to script-scope functions
danlinyu May 9, 2026
9a6cc92
fix(ui): create pollers at script top level, not inside click handlers
danlinyu May 9, 2026
caea31e
feat(csharp): scaffold .NET 10 WPF port (WinTune.Core + WinTune.App +…
danlinyu May 9, 2026
f87ac68
feat(core): add Model DTOs (PerfSnapshot, ProcessSnapshot, Cleanup*, …
danlinyu May 9, 2026
c9d5529
feat(core): add LibraryImport bindings for psapi (EmptyWorkingSet), s…
danlinyu May 9, 2026
6b73d28
feat(core): port MonitorService with CPU/RAM/Disk snapshot + top-N pr…
danlinyu May 9, 2026
465167d
feat(core): port BoostService with EmptyWorkingSet + DNS flush + Expl…
danlinyu May 9, 2026
abe02f7
feat(core): port CleanupService with reparse-point skip + finally-blo…
danlinyu May 9, 2026
13810e0
feat(core): port StartupService (WMI + registry + Startup folders)
danlinyu May 9, 2026
c803b2d
feat(core): port DiagnoseService (10 checks + 5 fixes)
danlinyu May 9, 2026
e653483
feat(core): port DedupService with two-pass size+hash dedup
danlinyu May 9, 2026
ea51bc7
feat(app): build WPF UI with MVVM (CommunityToolkit.Mvvm + Serilog + DI)
danlinyu May 9, 2026
1e4fccd
ci(csharp): GitHub Actions workflow + README build instructions
danlinyu May 9, 2026
ddb1c98
feat(app): dedupe tab UX — multi-select paths, file investigation, dr…
danlinyu May 10, 2026
acc842c
fix(ci): add RuntimeIdentifiers=win-x64 so cold restore generates RID…
danlinyu May 10, 2026
943058c
chore: retire Launch-WinTune.cmd, promote WinTune.exe to canonical in…
danlinyu May 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
test:
name: Analyze + Pester (Windows PowerShell 5.1)
runs-on: windows-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install Pester 5 + PSScriptAnalyzer
shell: powershell
run: |
Set-PSRepository PSGallery -InstallationPolicy Trusted
Install-Module Pester -MinimumVersion 5.5.0 -Force -SkipPublisherCheck -Scope CurrentUser
Install-Module PSScriptAnalyzer -Force -Scope CurrentUser
Get-Module Pester, PSScriptAnalyzer -ListAvailable | Sort-Object Name, Version | Format-Table Name, Version

- name: Run analyzer + tests
shell: powershell
run: ./Run-Tests.ps1

- name: Upload Pester results
if: always()
uses: actions/upload-artifact@v4
with:
name: pester-results
path: tests/results.xml
45 changes: 45 additions & 0 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: dotnet

on:
push:
branches: [main, csharp-port]
pull_request:
branches: [main, csharp-port]
workflow_dispatch:

jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4

- name: Setup .NET 10
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'

- name: Restore
run: dotnet restore

- name: Build
run: dotnet build -c Release --no-restore

- name: Test
run: dotnet test -c Release --no-build --logger "trx;LogFileName=test-results.trx" --results-directory TestResults

- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
path: TestResults/

- name: Publish single-file self-contained
run: dotnet publish src/WinTune.App/WinTune.App.csproj -c Release -r win-x64 -o publish/win-x64

- name: Upload WinTune.exe artifact
uses: actions/upload-artifact@v4
with:
name: WinTune-win-x64
path: publish/win-x64/WinTune.exe
if-no-files-found: error
19 changes: 19 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
logs/
*.log

# Test runner output
tests/results.xml

# Local toolchain (gh portable, etc.)
.tools/

Expand All @@ -13,3 +16,19 @@ Thumbs.db
desktop.ini
ehthumbs.db
$RECYCLE.BIN/

# .NET build output
bin/
obj/
*.user
.vs/
publish/
artifacts/
TestResults/
*.coverage
*.nupkg

# C# port diagnostic artifacts
diag.log
diag.ready
WinTune.diag.ps1
4 changes: 0 additions & 4 deletions Launch-WinTune.cmd

This file was deleted.

40 changes: 40 additions & 0 deletions PSScriptAnalyzerSettings.psd1
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
@{
# Severity gate. Information findings are advisory; we fail CI only on
# Warning and Error.
Severity = @('Error','Warning')

# Rules suppressed and why:
ExcludeRules = @(
# WinTune is an interactive WPF app, not a pipeline-friendly cmdlet
# library. State-changing UI-helper functions don't need ShouldProcess.
'PSUseShouldProcessForStateChangingFunctions',

# Many catch blocks intentionally swallow errors in UI-event handlers
# and the dispatcher poll loop -- they exist precisely to keep the
# window alive when a single tick fails. Documented at the call sites.
'PSAvoidUsingEmptyCatchBlock',

# Public function names are part of the dot-source surface advertised
# in README.md (Get-CleanupTargets, Get-StartupApps, Find-Duplicates,
# Get-TopProcesses, etc.). Renaming them now would break callers.
'PSUseSingularNouns',

# SHA1 is acceptable for personal-file deduplication and the choice is
# explained in the Dedup.psm1 header. Real-world collision risk on a
# single user's filesystem is effectively zero.
'PSAvoidUsingBrokenHashAlgorithms',

# `Run-Diagnose` in WinTune.ps1 is a local UI helper, not a cmdlet --
# the prefix communicates "click this to do X" to a script reader.
'PSUseApprovedVerbs',

# The Async helper splats $Progress into the user script via @args, so
# PSSA can't see the binding. The parameter is intentionally present.
'PSReviewUnusedParameter',

# Run-Tests.ps1 prints colored section headers to the console. Write-
# Host is the right tool for that; the rule's complaint about
# capturability does not apply to a human-facing test runner.
'PSAvoidUsingWriteHost'
)
}
123 changes: 102 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# WinTune

A small, single-window Windows 11 performance toolkit. Monitor live system load,
clean accumulated junk, and free memory — all from one place. Pure PowerShell +
WPF, no installer, no compiled binary.
clean accumulated junk, and free memory — all from one place. Ships as a
self-contained C# / WPF / .NET 10 executable; original PowerShell + WPF
script preserved as a reference implementation.

## Why this exists

Expand All @@ -20,21 +21,27 @@ WinTune addresses each of these with one click.

## What it does (and what it deliberately doesn't)

### Three tabs
### Five tabs

**Dashboard** — live CPU, RAM, and disk-C: bars (refresh every 2 s) plus a top-10
process list sorted by working-set RAM.

**Clean** — pick what to clear, click Run:
- User Temp (`%TEMP%`)
- System Temp (`C:\Windows\Temp`)
- System Temp (`%SystemRoot%\Temp`)
- Windows Prefetch
- Windows Error Reports (`WER\Report{Archive,Queue}`)
- Windows Update download cache (stops `wuauserv` + `bits` briefly, then restarts them)
- Windows Update download cache (stops `wuauserv` + `bits` briefly, then restarts
them — restart is in a `finally` block so the services come back even if the
delete fails)
- Edge / Chrome / Firefox caches (skipped if the browser is running — close it first)
- Empty Recycle Bin
- Flush DNS resolver cache

Reparse points (junctions, symlinks) inside any cleanup target are skipped — never
followed — so a stray junction can't redirect the recursive delete somewhere
unexpected.

**Boost**
- Free RAM — calls Win32 `EmptyWorkingSet` on every process. Reduces working-set
pressure without killing anything.
Expand All @@ -44,6 +51,32 @@ process list sorted by working-set RAM.
folders), with a button that opens **Task Manager → Startup** if you want to
disable any of them yourself.

**Diagnose** — find what's making Explorer slow. Each finding is colour-coded
(Red = act, Yellow = worth a look, Green = OK) with a one-line hint. Checks
include disk health, free-space pressure, multiple cloud sync shell extensions,
Quick Access bloat, Search index emptiness, DiagTrack telemetry, the Win11
right-click overlay, pagefile placement, startup-app count, and RAM pressure.

Backed by safe one-click fixes:
- **Reset Quick Access** — clears stale `Recent` and `AutomaticDestinations`
shortcuts, a common cause of Explorer hangs on dead network paths.
- **Disable Telemetry** — stops + disables `DiagTrack` *and* sets the
`AllowTelemetry` Group Policy value so Windows Update cumulative updates
don't silently re-enable it.
- **Apply Classic Right-Click** — restores the Win10-style instant context
menu (Win11's "Show more options" overlay is the slow path).
- **Rebuild Search Index** — kicks Windows Search to re-index from scratch.
- **Undo Classic Right-Click** — reverts the above.

**Dedupe** — find and remove duplicate files in user folders.
- Two-pass scan: group by exact size first, then SHA1-hash only size-matches.
- Skips reparse points (OneDrive on-demand files won't trigger a download) and
system files.
- Auto-select helpers: keep oldest / keep newest / keep shortest path.
- Safety stop: refuses to delete every file in a duplicate group — you must
un-tick at least one row per group to keep a copy.
- Deletes go to the Recycle Bin so you can recover.

### Out of scope on purpose

WinTune **does not**:
Expand All @@ -59,16 +92,33 @@ If you want the heavier stuff, use Sysinternals' RAMMap or Autoruns by hand.

## Install & run

1. Clone or download this repo:
```powershell
git clone https://github.com/danlinyu/WinTune.git
```
2. Double-click **`Launch-WinTune.cmd`**.
3. Approve the UAC prompt (admin rights are needed to clear System Temp,
Windows Update cache, and Prefetch).
WinTune ships as a self-contained C# WPF executable. The original PowerShell +
WPF script is preserved in this repo as a reference implementation.

### Self-contained .exe (recommended)

Single ~63 MB `WinTune.exe`, no PowerShell or .NET runtime needed on the
target machine. Either grab the latest CI artifact from the
[Actions tab](https://github.com/danlinyu/WinTune/actions) or build it
yourself with the .NET 10 SDK:

```powershell
dotnet publish src/WinTune.App/WinTune.App.csproj -c Release -r win-x64 -o publish/win-x64
.\publish\win-x64\WinTune.exe
```

UAC will prompt automatically (the app manifest requests `requireAdministrator`).

### PowerShell version (reference implementation)

The original Windows PowerShell 5.1 + WPF version is still here for reference.
WPF on PowerShell 5.1 (STA mode) is required:

```powershell
powershell.exe -NoProfile -STA -ExecutionPolicy Bypass -File .\WinTune.ps1
```

That's it — no install step, no dependencies beyond the Windows PowerShell 5.1
that ships with every Windows 10 / 11 install.
Self-elevation is handled inside the script (UAC prompt fires automatically).

### How safe is it?

Expand All @@ -85,34 +135,65 @@ browser profile.
## Tested on

- Windows 11 Pro for Workstations, build 22631
- Windows PowerShell 5.1 (the launcher pins this rather than pwsh 7 — WPF on
pwsh 7 is currently flaky)
- C# build: .NET 10 SDK, single-file self-contained `win-x64`
- PowerShell reference build: Windows PowerShell 5.1 STA (WPF on pwsh 7 is flaky)

## Project layout

```
WinTune/
├── WinTune.ps1 entry point — builds the WPF window and wires events
├── Launch-WinTune.cmd double-clickable wrapper
├── WinTune.sln .NET 10 solution (Core + App + Tests)
├── src/WinTune.Core/ class library — services and DTOs (no WPF refs)
├── src/WinTune.App/ WPF executable, MVVM via CommunityToolkit.Mvvm
├── tests/ xUnit + FluentAssertions
├── .github/workflows/ CI — dotnet.yml (C#) + ci.yml (PowerShell)
├── WinTune.ps1 PowerShell reference — builds WPF window directly
├── modules/
│ ├── Monitor.psm1 Get-PerfSnapshot, Get-TopProcesses
│ ├── Cleanup.psm1 Invoke-Cleanup, Get-CleanupTargets, Format-Bytes
│ ├── Cleanup.psm1 Invoke-Cleanup, Get-CleanupTargets, Format-Bytes,
│ │ Get-LastCleanupLog
│ ├── Boost.psm1 Clear-WorkingSets, Restart-Explorer, Clear-DNSCacheSafe
│ └── Startup.psm1 Get-StartupApps, Open-StartupTaskManager
│ ├── Startup.psm1 Get-StartupApps, Open-StartupTaskManager
│ ├── Diagnose.psm1 Invoke-Diagnostics + Reset-QuickAccess, Disable-Telemetry,
│ │ Enable-/Disable-ClassicRightClick, Start-SearchIndexRebuild
│ └── Dedup.psm1 Find-Duplicates, Remove-DuplicateFiles, Get-DefaultScanRoots
├── ui/MainWindow.xaml WPF layout (loaded at runtime by WinTune.ps1)
├── README.md
└── LICENSE MIT
```

The four modules are pure functions — easy to dot-source and use from any
The six modules are pure functions — easy to dot-source and use from any
PowerShell prompt:

```powershell
Import-Module .\modules\Monitor.psm1
Get-PerfSnapshot
Get-TopProcesses -Count 5

Import-Module .\modules\Diagnose.psm1
Invoke-Diagnostics | Format-Table Severity, Title, Detail

Import-Module .\modules\Dedup.psm1
Find-Duplicates -Paths $HOME\Downloads -MinSizeBytes 10MB
```

## Development

Run the test gate (PSScriptAnalyzer + Pester) locally:

```powershell
# Once
Install-Module Pester -MinimumVersion 5.5.0 -Force -SkipPublisherCheck -Scope CurrentUser
Install-Module PSScriptAnalyzer -Force -Scope CurrentUser

# Each commit
.\Run-Tests.ps1
```

GitHub Actions runs the same script on every push and PR
([`.github/workflows/ci.yml`](.github/workflows/ci.yml)).

## License

MIT — see [LICENSE](LICENSE).
Loading
Loading