Fix WindowWalker crash (Access Denied) when killing certain protected/elevated processes#47397
Fix WindowWalker crash (Access Denied) when killing certain protected/elevated processes#47397Copilot wants to merge 3 commits into
Conversation
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>
There was a problem hiding this comment.
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.Killto elevatedtaskkill.exe. - Disposes
Processhandles 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. |
| 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; | ||
| } |
There was a problem hiding this comment.
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.
| 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; | ||
| } |
There was a problem hiding this comment.
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.
| string killTree = killProcessTree ? " /t" : string.Empty; | ||
| Helper.OpenInShell("taskkill.exe", $"/pid {(int)ProcessID} /f{killTree}", null, Helper.ShellRunAsType.Administrator, true); |
There was a problem hiding this comment.
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.
| var killTree = killProcessTree ? " /t" : string.Empty; | ||
| ExplorerInfoResultCommand.OpenInShell("taskkill.exe", $"/pid {(int)ProcessID} /f{killTree}", null, ExplorerInfoResultCommand.ShellRunAsType.Administrator, true); |
There was a problem hiding this comment.
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>
|
@copilot please re-review the latest changes. |
The latest changes in commit
|
Summary of the Pull Request
PowerToys Run crashed with
Win32Exception (5): Access is deniedwhen using Window Walker to kill an unresponsive window whose process passed thePROCESS_ALL_ACCESSopen-handle test but still deniedPROCESS_TERMINATEat kill time (e.g., protected processes, certain elevated processes). The fix falls back to elevatedtaskkill.exe /fin 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
Detailed Description of the Pull Request / Additional comments
Root cause:
TestProcessAccessUsingAllAccessFlagonly tests whetherOpenProcess(PROCESS_ALL_ACCESS)returns error 5. Some processes (PPL, anti-tamper, certain elevated) pass this check yet denyPROCESS_TERMINATEwhenKill()is invoked, throwing an unhandledWin32Exceptionthat crashed PowerToys Run.Changes:
WindowProcess.KillThisProcess()(Launcher + CmdPal):Win32ExceptionwithNativeErrorCode == 5in the direct-kill path and falls back to elevatedtaskkill.exe /pid <id> /f, matching the existingIsFullAccessDeniedpath.OpenInShellis now checked in all call sites (both theIsFullAccessDeniedbranch and theWin32Exceptionfallback). If the elevated taskkill fails to launch (e.g. user cancels the UAC prompt),InvalidOperationExceptionis thrown so the caller correctly reports failure instead of treating a non-started kill as success.Processobject is properly disposed viausing.ContextMenuHelper.KillProcessCommand()(Launcher) andEndTaskCommand.KillProcess()(CmdPal):catch (ArgumentException)to handle the process-exit race: if the target process exits between theDoesExistvalidation check and the actualKill()call,Process.GetProcessByIdthrowsArgumentException. This is now caught and treated as a successful termination (the process is already gone).catch (Win32Exception)andcatch (InvalidOperationException)for any remaining failures (including a declined elevation prompt). These are logged and returnfalseto keep PT Run open rather than crashing.Validation Steps Performed
Win32Exception (5)stack through each fix point.OpenInShellfallback result is checked at all call sites and that failed elevation propagates correctly to the caller.ArgumentExceptionprocess-exit race is covered in both the Launcher and CmdPal callers.