Skip to content
Open
Changes from 1 commit
Commits
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
94 changes: 94 additions & 0 deletions doc/devdocs/tools/installer-diagnostics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# PowerToys Installer & Update Diagnostics

A step-by-step guide for diagnosing installer and update issues reported by users.

## Quick Reference: Key Files

| File/Folder | Path | Contains |
|---|---|---|
| UpdateState.json | `%LOCALAPPDATA%\Microsoft\PowerToys\UpdateState.json` | Persisted update state machine |
| Runner logs | `%LOCALAPPDATA%\Microsoft\PowerToys\RunnerLogs\runner-log_*.log` | Startup, update checks, cleanup |
| Update logs | `%LOCALAPPDATA%\Microsoft\PowerToys\UpdateLogs\update-log_*.log` | PowerToys.Update.exe activity |
| Updates folder | `%LOCALAPPDATA%\Microsoft\PowerToys\Updates\` | Downloaded installer files |
Comment on lines +7 to +12
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Markdown table syntax is incorrect: each row starts with ||, which typically renders as an extra empty column (or a malformed table) in many Markdown renderers. Use a single leading | for each row (and alignment row) so the table renders correctly.

Copilot uses AI. Check for mistakes.

> **Note:** These paths use `%LOCALAPPDATA%` (per-user AppData) regardless of whether PowerToys was installed per-user or per-machine. The data/settings location is always per-user.

## Update State Values

From `src/common/updating/updateState.h` (`UpdateState::State` enum):

| Value | Name | Meaning |
|---|---|---|
| 0 | upToDate | No update needed |
| 1 | errorDownloading | Download or install failed, will retry |
| 2 | readyToDownload | New version found, not yet downloaded |
| 3 | readyToInstall | Installer downloaded, waiting for user action |
| 4 | networkError | GitHub API call failed |
Comment on lines +20 to +26
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This table has the same || issue as above and is likely to render incorrectly. Also, since these values are sourced from an enum in code, consider adding an 'As of version/commit' note here to reduce future drift if the enum changes.

Copilot uses AI. Check for mistakes.

---

## Symptom: Old update installers accumulating on disk

### What to ask the user for

1. Contents of `UpdateState.json`
2. Runner logs (last few days from `RunnerLogs\`)
3. Update logs (from `UpdateLogs\`, if they exist)
4. List of files in `Updates\` folder (names + sizes)

### Step 1: Check the running version

In runner logs, look for the startup line:

```
[info] Scoobe: product_version=v0.XX.X last_version_run=v0.XX.X
```

- **If version < v0.73.0**: The pre-download cleanup (PR #27908) is missing. Each downloaded installer accumulates because cleanup only runs at startup when state is `upToDate`. Ask the user to manually upgrade to the latest version.
- **If version >= v0.73.0**: The pre-download cleanup exists. Accumulation should not happen under normal conditions. Continue to Step 2.

### Step 2: Check UpdateState.json

```json
{"state": 3, "downloadedInstallerFilename": "powertoyssetup-0.98.1-x64.exe", ...}
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code fence is marked as json, but the example isn’t valid JSON due to the .... This can mislead readers and can break tooling that validates/pretty-prints JSON snippets. Consider either removing the ellipsis and keeping the example valid JSON, or switching the fence to jsonc/text and explicitly noting fields are omitted.

Suggested change
```json
{"state": 3, "downloadedInstallerFilename": "powertoyssetup-0.98.1-x64.exe", ...}
```jsonc
{"state": 3, "downloadedInstallerFilename": "powertoyssetup-0.98.1-x64.exe" /* additional fields may be present */}

Copilot uses AI. Check for mistakes.
```

- **state = 0 (upToDate)**: Cleanup should run at startup. If files are accumulating, check runner logs for "Failed to delete" warnings (Step 4).
- **state = 3 (readyToInstall)**: An installer is downloaded but never installed. Cleanup at startup is skipped (by design, to preserve the pending installer). If this state persists across many update cycles, old files can accumulate on versions < v0.73.0.
- **state = 1 (errorDownloading)**: A previous download or install failed. Cleanup should run at next startup (v0.73+).
- **state = 2 or 4**: Transient states. Cleanup should run at next startup (v0.73+).
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The guidance for when cleanup runs is inaccurate. Runner startup cleanup is gated on UpdateState::upToDate (see src/runner/main.cpp), and updating::cleanup_updates() is otherwise only invoked right before downloading a new installer (see src/runner/UpdateUtils.cpp / src/Update/PowerToys.Update.cpp). Please reword these bullets to avoid implying cleanup runs automatically at the next startup for non-upToDate states.

Suggested change
- **state = 3 (readyToInstall)**: An installer is downloaded but never installed. Cleanup at startup is skipped (by design, to preserve the pending installer). If this state persists across many update cycles, old files can accumulate on versions < v0.73.0.
- **state = 1 (errorDownloading)**: A previous download or install failed. Cleanup should run at next startup (v0.73+).
- **state = 2 or 4**: Transient states. Cleanup should run at next startup (v0.73+).
- **state = 3 (readyToInstall)**: An installer is downloaded but never installed. Cleanup at startup is skipped (by design, to preserve the pending installer). On v0.73+, cleanup can still occur later if a future update flow reaches the pre-download cleanup path before fetching another installer.
- **state = 1 (errorDownloading)**: A previous download or install failed. Do not expect automatic cleanup on the next startup; on v0.73+, cleanup is performed before a new installer download is attempted.
- **state = 2 or 4**: No automatic cleanup is expected on the next startup unless the state returns to `upToDate`; on v0.73+, cleanup otherwise happens when a new installer download is about to start.

Copilot uses AI. Check for mistakes.

### Step 3: Check if PowerToys.Update.exe has ever run

- **UpdateLogs directory missing**: `PowerToys.Update.exe` was never launched. The user never triggered an install — either they dismissed all update notifications, or Stage 1 failed before Stage 2 could run.
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The statement "UpdateLogs directory missing: PowerToys.Update.exe was never launched" is too strong. PowerToys.Update.exe does not explicitly create the UpdateLogs subfolder before initializing the logger (it only ensures the root save folder exists), so missing logs could also mean logging initialization failed or logs were removed. Suggest rewording this as a possible indicator rather than a definitive conclusion.

Suggested change
- **UpdateLogs directory missing**: `PowerToys.Update.exe` was never launched. The user never triggered an install — either they dismissed all update notifications, or Stage 1 failed before Stage 2 could run.
- **UpdateLogs directory missing**: This suggests `PowerToys.Update.exe` may never have been launched, or may not have progressed far enough to create logs. It can also mean logging initialization failed or the logs were later removed. The user may never have triggered an install, or Stage 1 may have failed before Stage 2 could run.

Copilot uses AI. Check for mistakes.
- **UpdateLogs exist but show only "logger is initialized"**: The exe launched but the command-line argument didn't match any action (possible argument parsing issue).
- **UpdateLogs show install activity**: The update process ran. Check for success/failure.

### Step 4: Check runner logs for cleanup evidence

Search for these patterns:

| Log pattern | Meaning |
|---|---|
| `Failed to delete installer file ... Access is denied` | File locked by AV, another process, or permissions issue |
| `Failed to delete log file ...` | Same, for old log files |
| `Failed to clean up old update files:` | Exception in cleanup (v0.73+ with exception handling) |
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Failed to clean up old update files: does not appear to be an emitted log message in the current cleanup implementation; updating::cleanup_updates() logs per-file failures as Failed to delete installer file ... / Failed to delete log file .... Please remove or replace this pattern with log strings that actually exist so triage steps don't send people searching for non-existent messages.

Suggested change
| `Failed to clean up old update files:` | Exception in cleanup (v0.73+ with exception handling) |

Copilot uses AI. Check for mistakes.
| `Discovered new version` | Periodic update check ran |
| `New version is already downloaded` | State is `readyToInstall` and filename matches — no re-download, no cleanup |
| No cleanup-related entries at all | Cleanup was never called — likely state gate blocked it |

### Step 5: Check the Updates folder contents

- **All different versions**: Cleanup never ran across multiple update cycles. Points to state gate issue or pre-v0.73 binary.
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"No cleanup-related entries at all" isn't a reliable signal that cleanup was never called: updating::cleanup_updates() only logs when deletions fail, and otherwise is silent. Consider pointing readers to call-site logs (e.g., the download trace lines) or to check the Updates folder state rather than relying on absence of cleanup logs.

Suggested change
| No cleanup-related entries at all | Cleanup was never called — likely state gate blocked it |
### Step 5: Check the Updates folder contents
- **All different versions**: Cleanup never ran across multiple update cycles. Points to state gate issue or pre-v0.73 binary.
| No cleanup-related entries at all | Inconclusive by itself: `cleanup_updates()` is silent on success. Corroborate with call-site runner log entries (for example `Discovered new version` and download-related traces) and with the `%LOCALAPPDATA%\Microsoft\PowerToys\Updates\` folder state in Step 5. |
### Step 5: Check the Updates folder contents
- **All different versions**: Cleanup likely did not run across multiple update cycles, or an older/pre-fix binary is in use. Confirm with runner log call-site traces and update state before concluding a state gate issue.

Copilot uses AI. Check for mistakes.
- **Duplicate filenames**: Unusual — would suggest repeated download without cleanup.
- **Single file matching `downloadedInstallerFilename`**: Normal for `readyToInstall` state.

### Common root causes

| Root cause | Evidence | Fix |
|---|---|---|
| Running pre-v0.73.0 binary | `product_version` < v0.73.0 in runner log | Manually upgrade to latest |
| State stuck at `readyToInstall` (pre-v0.73) | `state: 3` in UpdateState.json, no UpdateLogs | Manually upgrade to latest |
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same || table formatting problem here. Additionally, the Evidence cell uses state: 3 while earlier examples use \"state\": 3; keeping the JSON key format consistent (and valid JSON) will reduce confusion for readers extracting the value from UpdateState.json.

Suggested change
| State stuck at `readyToInstall` (pre-v0.73) | `state: 3` in UpdateState.json, no UpdateLogs | Manually upgrade to latest |
| State stuck at `readyToInstall` (pre-v0.73) | `"state": 3` in UpdateState.json, no UpdateLogs | Manually upgrade to latest |

Copilot uses AI. Check for mistakes.
| File lock preventing deletion | "Failed to delete ... Access is denied" in runner logs | Check AV software, reboot and retry |
| Update installer never launched | No UpdateLogs directory | Check if update notifications are disabled by GPO or setting |
| Install fails silently | UpdateLogs show init but no install activity | Check related issues: #46966, #46967, #46969 |
Loading