Skip to content

Fix WindowWalker crash (Access Denied) when killing certain protected/elevated processes#47397

Draft
Copilot wants to merge 3 commits into
mainfrom
copilot/fix-powertoys-run-unresponsive-window
Draft

Fix WindowWalker crash (Access Denied) when killing certain protected/elevated processes#47397
Copilot wants to merge 3 commits into
mainfrom
copilot/fix-powertoys-run-unresponsive-window

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 29, 2026

Summary of the Pull Request

PowerToys Run crashed with Win32Exception (5): Access is denied when using Window Walker to kill an unresponsive window whose process passed the PROCESS_ALL_ACCESS open-handle test but still denied PROCESS_TERMINATE at kill time (e.g., protected processes, certain elevated processes). The fix falls back to elevated taskkill.exe /f in that case, checks the elevation result, handles the process-exit race condition, and adds guard exception handling in the callers to prevent future crashes.

PR Checklist

  • Communication: I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected
  • Tests: Added/updated and all pass
  • Localization: All end-user-facing strings can be localized
  • Dev docs: Added/updated
  • New binaries: Added on the required places
  • Documentation updated: If checked, please file a pull request on our docs repo and link it here: #xxx

Detailed Description of the Pull Request / Additional comments

Root cause: TestProcessAccessUsingAllAccessFlag only tests whether OpenProcess(PROCESS_ALL_ACCESS) returns error 5. Some processes (PPL, anti-tamper, certain elevated) pass this check yet deny PROCESS_TERMINATE when Kill() is invoked, throwing an unhandled Win32Exception that crashed PowerToys Run.

Changes:

  • WindowProcess.KillThisProcess() (Launcher + CmdPal):

    • Catches Win32Exception with NativeErrorCode == 5 in the direct-kill path and falls back to elevated taskkill.exe /pid <id> /f, matching the existing IsFullAccessDenied path.
    • The return value of OpenInShell is now checked in all call sites (both the IsFullAccessDenied branch and the Win32Exception fallback). If the elevated taskkill fails to launch (e.g. user cancels the UAC prompt), InvalidOperationException is thrown so the caller correctly reports failure instead of treating a non-started kill as success.
    • Process object is properly disposed via using.
  • ContextMenuHelper.KillProcessCommand() (Launcher) and EndTaskCommand.KillProcess() (CmdPal):

    • Added catch (ArgumentException) to handle the process-exit race: if the target process exits between the DoesExist validation check and the actual Kill() call, Process.GetProcessById throws ArgumentException. This is now caught and treated as a successful termination (the process is already gone).
    • Added catch (Win32Exception) and catch (InvalidOperationException) for any remaining failures (including a declined elevation prompt). These are logged and return false to keep PT Run open rather than crashing.

Validation Steps Performed

  • Traced the full exception path from the originally reported Win32Exception (5) stack through each fix point.
  • Verified that the OpenInShell fallback result is checked at all call sites and that failed elevation propagates correctly to the caller.
  • Verified the ArgumentException process-exit race is covered in both the Launcher and CmdPal callers.

When killing an unresponsive window whose process was not flagged as
IsFullAccessDenied (because TestProcessAccessUsingAllAccessFlag passed),
Process.Kill() could still throw Win32Exception (error 5) for certain
protected/elevated processes. This caused PowerToys Run to crash.

- WindowProcess.KillThisProcess() (both launcher and CmdPal): catch
  Win32Exception with NativeErrorCode == 5 and fall back to elevated
  taskkill.exe, matching the existing IsFullAccessDenied path. Also
  properly dispose the Process object via `using`.
- ContextMenuHelper.KillProcessCommand() (launcher): catch Win32Exception
  and InvalidOperationException around KillThisProcess so any remaining
  failures are logged without crashing PowerToys Run.
- EndTaskCommand.KillProcess() (CmdPal): same protective error handling.

Agent-Logs-Url: https://github.com/microsoft/PowerToys/sessions/df39f01b-d1e2-413b-9154-0d402084933d

Co-authored-by: MuyuanMS <116717757+MuyuanMS@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix PowerToys Run issue killing unresponsive window Fix WindowWalker crash (Access Denied) when killing certain protected/elevated processes Apr 29, 2026
Copilot AI requested a review from MuyuanMS April 29, 2026 09:25
@niels9001 niels9001 added the Product-Command Palette Refers to the Command Palette utility label Apr 29, 2026
@MuyuanMS MuyuanMS requested a review from Copilot May 14, 2026 03:09
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates Window Walker process termination in PowerToys Run and CmdPal to avoid crashes when terminating protected/elevated processes fails with access denied.

Changes:

  • Adds access-denied fallback from direct Process.Kill to elevated taskkill.exe.
  • Disposes Process handles after lookup.
  • Adds caller-side exception guards for kill failures.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/WindowProcess.cs Adds direct-kill access-denied fallback to elevated taskkill.
src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/ContextMenuHelper.cs Adds exception handling around launcher kill command.
src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Components/WindowProcess.cs Mirrors access-denied fallback for CmdPal Window Walker.
src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Commands/EndTaskCommand.cs Adds exception handling around CmdPal end-task command.

Comment on lines +110 to +119
catch (Win32Exception ex)
{
Log.Exception($"Failed to kill process '{window.Process.Name}' ({window.Process.ProcessID}) of the window '{window.Title}' ({window.Hwnd}).", ex, typeof(ContextMenuHelper));
return false;
}
catch (InvalidOperationException ex)
{
Log.Exception($"Failed to kill process '{window.Process.Name}' ({window.Process.ProcessID}) of the window '{window.Title}' ({window.Hwnd}).", ex, typeof(ContextMenuHelper));
return false;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Handled in ContextMenuHelper: KillThisProcess is now wrapped with an ArgumentException catch so the process-exit race is treated as success when the PID is already gone.

Comment on lines +69 to +78
catch (Win32Exception ex)
{
ExtensionHost.LogMessage(new LogMessage { Message = $"Failed to kill process '{window.Process.Name}' ({window.Process.ProcessID}) of the window '{window.Title}' ({window.Hwnd}): {ex.Message}" });
return false;
}
catch (InvalidOperationException ex)
{
ExtensionHost.LogMessage(new LogMessage { Message = $"Failed to kill process '{window.Process.Name}' ({window.Process.ProcessID}) of the window '{window.Title}' ({window.Hwnd}): {ex.Message}" });
return false;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Applied the same fix in the CmdPal WindowWalker EndTaskCommand: ArgumentException from the process-exit race is now swallowed so an already-exited process is treated as a successful end-task.

Comment on lines +228 to +229
string killTree = killProcessTree ? " /t" : string.Empty;
Helper.OpenInShell("taskkill.exe", $"/pid {(int)ProcessID} /f{killTree}", null, Helper.ShellRunAsType.Administrator, true);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Updated the launcher WindowProcess fallback path to check Helper.OpenInShell. If the elevated taskkill launch fails, it now throws InvalidOperationException so the caller reports failure instead of incorrectly returning success.

Comment on lines +239 to +240
var killTree = killProcessTree ? " /t" : string.Empty;
ExplorerInfoResultCommand.OpenInShell("taskkill.exe", $"/pid {(int)ProcessID} /f{killTree}", null, ExplorerInfoResultCommand.ShellRunAsType.Administrator, true);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Applied the same elevated-fallback handling in the CmdPal WindowProcess: OpenInShell is now checked and a failed elevated launch raises InvalidOperationException so the kill is not reported as successful.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@MuyuanMS
Copy link
Copy Markdown
Contributor

@copilot please re-review the latest changes.

Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 14, 2026

@copilot please re-review the latest changes.

The latest changes in commit bef5a36 look correct. All four issues from the review are properly addressed:

  • Process-exit race (ArgumentException): Caught in both ContextMenuHelper.KillProcessCommand() and EndTaskCommand.KillProcess(). Since the process is already gone, falling through to the success return is the right behavior.
  • OpenInShell return value: Checked in all three call sites across both WindowProcess implementations (IsFullAccessDenied path and the Win32Exception fallback catch). A false return throws InvalidOperationException, which propagates to the caller's handler.
  • ArgumentException propagation: When Process.GetProcessById throws inside KillThisProcess, it correctly propagates uncaught to the callers where it is handled.
  • Process disposal: using applied in both WindowProcess implementations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Product-Command Palette Refers to the Command Palette utility

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PowerToys Run ran into an issue while trying to kill an unresponsive window

4 participants