A smart Windows PATH inspector and tidier. It lists every component of your
PATH, flags the broken or redundant ones, and can clean or reorder the User or
system PATH — safely, with dry-run previews and automatic backups.
1. ✅ [system] C:\Windows\System32
2. ❌ [user] C:\Tools\gone (missing)
3. ⚠️ [both] c:\windows\system32\ (duplicate of #1)
4. 📄 [user] C:\Users\me\notes.txt (not a folder)
Scope: [system] machine [user] user [both] both [session] process-only
Summary: 4 entries — 1 ok, 1 missing, 1 not-a-folder, 1 duplicate
- Inspect the live
PATH, with each entry flagged:- ❌ missing folder · 📄 path is a file, not a folder · 🚫 empty segment ·
⛔ invalid characters ·
⚠️ duplicate · ✅ healthy - A colored scope tag shows where each entry comes from:
[system](Machine PATH),[user](User PATH),[both], or[session](present only in the live process — injected by a dev shell, or a surplus duplicate beyond what the registry holds; see Scopes).
- ❌ missing folder · 📄 path is a file, not a folder · 🚫 empty segment ·
⛔ invalid characters ·
--cleanremoves empty, invalid, missing, and duplicate entries.--optimizecleans, then reorders: canonical system dirs first, then related tools grouped together (Visual Studio, CUDA, Python, …).- Targets the User PATH (default) or the Machine (system) PATH (
--machine). - Safe by default: clean/optimize are dry-run previews; nothing is written
without
--apply. Applying writes a timestamped backup first, preserves%VAR%references and theREG_EXPAND_SZvalue type, and broadcastsWM_SETTINGCHANGEso new programs pick up the change.
- Windows
- .NET SDK 11 or later
dotnet build
dotnet run # list the PATH
dotnet run -- --clean # preview a cleaned User PATH
# or run the built exe directly:
.\bin\x64\Debug\net11.0-windows\listpath.exe --optimizelistpath List PATH components, flagging problems and scope.
listpath --clean Preview a cleaned PATH (dry-run).
listpath --optimize Preview a cleaned + reordered PATH (dry-run).
listpath --remove <path> Preview removing the named entry (repeatable).
listpath --prune Interactively pick entries to remove.
listpath ... --apply Persist the change (writes a backup first).
Target (default --user):
--user Operate on the User PATH (no admin needed).
--machine Operate on the system PATH. Previews work
unelevated; --apply requires an admin shell.
listpath --help Show help.
listpath # inspect
listpath --clean # what would clean remove from my User PATH?
listpath --optimize --apply # tidy + reorder the User PATH for real
listpath --machine --clean # preview cleaning the system PATH (read-only)
listpath --machine --clean --apply # must be run from an elevated terminal| Code | Meaning |
|---|---|
| 0 | Success (or list mode with no problems found) |
| 1 | List mode found problems / an apply failed to write |
| 2 | Bad arguments (unknown flag, conflicting flags) |
| 3 | --machine --apply attempted without administrator rights |
Entries are compared by their normalized key: %VAR% references are expanded,
the path is resolved to a full path, the case is ignored, and trailing slashes are
stripped. So C:\Bin, c:\bin\, and %SystemDrive%\Bin all count as the same
entry. The first occurrence is kept; later ones are flagged
List mode reads the process PATH, which Windows builds by merging
Machine + User. Scope tags are computed by occurrence counting: the
Machine and User PATHs each supply a fixed number of occurrences per normalized
key, entries are walked in order, and each registry-backed occurrence is
"consumed". Any surplus occurrence — more than the registry accounts for — is
session-injected and tagged [session].
This is why a duplicate can show [session]: e.g. pwsh prepends its own
$PSHOME (C:\Program Files\PowerShell\7) to the process PATH at startup, on
top of the copy already in the Machine PATH. The registry has it once, so the
extra live copy is correctly flagged [session] and --clean leaves the registry
untouched. See Troubleshooting.
Because of this merge, --clean/--optimize operate on one target only
(User by default). When cleaning the User PATH, Machine entries are seeded as
"already seen", so a User entry that merely duplicates a system dir is removed —
without copying Machine entries into the User value. When cleaning the Machine
PATH, deduplication is within Machine only; a Machine entry is never dropped just
because the User PATH also has it.
- Canonical system directories float to the top in order:
System32,Windows,Wbem,WindowsPowerShell\v1.0. - Remaining entries get a group key from their recognizable root (keyword
rules for Visual Studio / Windows Kits / CUDA / Python / PowerShell / .NET /
Java / coreutils, else the child of
Program Files, else the first path segment). A stable sort clusters groups while preserving each group's first-appearance order — deterministic, no surprising reshuffles.
Apply reads the raw registry value (DoNotExpandEnvironmentNames), keeps each
entry's original text (so %VAR% survives), and writes back as REG_EXPAND_SZ.
It backs up the previous raw value to
%USERPROFILE%\.listpath-backups\{User|Machine}-PATH-<timestamp>.txt and prints
the exact restore command. After writing it broadcasts WM_SETTINGCHANGE.
- User PATH:
HKCU\Environment— no admin required. - Machine PATH:
HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment— requires an elevated shell. Without it,--machine --applyrefuses before writing and prints the elevated retry command (exit 3).
--clean/--optimize only touch duplicates, missing, and invalid entries. To
remove a valid entry you simply don't want (e.g. a redundant 32-bit tool):
listpath --remove "<path>"— repeatable, matched by normalized key (case / trailing-slash / %VAR% insensitive), dry-run by default,--applyto persist.listpath --prune— interactive checkbox picker over the target's entries.
Both are surgical (they touch only what you select) and share the same backup +
lossless apply + elevation rules. Use --machine to target the system PATH.
See docs/path-pruning-candidates.md for saved cleanup candidates.
A duplicate won't go away, even after --clean --apply.
Check its scope tag:
⚠️ [session]— the duplicate is injected into your shell at startup, not stored in any registryPATH. EditingPATHcan't remove it; it reappears each launch. The classic case is pwsh adding its own$PSHOMEon top of the Machine entry. Harmless —PATHresolves left-to-right. To stop it you'd change whatever launches the shell, notPATH.⚠️ [system]— it's in the MachinePATH.--cleandefaults to User and won't touch it. Run--machine --clean --applyfrom an elevated shell.⚠️ [user]—listpath --clean --applyremoves it. If it persists, you're looking in the same shell you applied from (it keeps a cachedPATH) — open a new terminal.
--machine --apply says "nothing to do" but I see a [system] duplicate.
The registry holds it once; the extra copy is [session] (injected). The tool is
correct — there's nothing in the registry to remove.
| File | Responsibility |
|---|---|
Program.cs |
Argument parsing → command dispatch; elevation pre-flight. |
PathInspector.cs |
EntryStatus/PathScope/PathEntry types; pure Analyze + normalization. |
PathTransformer.cs |
Pure Clean / Optimize over analyzed entries. |
PathStore.cs |
Registry read/write per target, backups, admin check, WM_SETTINGCHANGE. |
Renderer.cs |
All console output: list view, diff view, colors. |
docs/superpowers/specs/ |
Design specs for each feature (read these to resume). |
TODO.md |
Roadmap and notes. |
The Analyze and Clean/Optimize functions are pure (string/entry-list in,
flagged entries out, no I/O), which keeps the logic easy to reason about and test.
There is no automated test project yet (see roadmap). The transforms are verified manually via dry-run output against crafted and real PATH values.
Verifying --apply without risking your real PATH: PathStore reads an env
var LISTPATH_TEST_HKCU_SUBKEY (unset in normal use) that redirects the User
target to a throwaway HKCU subkey. Point it at a scratch key, seed a Path
value, and run --user --clean --apply against it. Always confirm the redirect
with a dry-run first (the preview should show the scratch data, not your real PATH).
⚠️ Do not test a real--applyagainst your liveHKCU\Environmentvalue. Use the test seam or a throwaway key. When changing the target framework, delete stale build outputs first — running an old binary can ignore the test seam.
- Automated test project (xUnit) around the pure transforms.
-
--user/--machine"clean both" convenience, or a combined report. - Preference to keep
%VAR%form vs literal when both exist as duplicates. - See
TODO.mdfor the current list.
Colors are disabled automatically when output is redirected (piped to a file) or
when the NO_COLOR environment variable is set.