Compare commits

..

4 Commits

Author SHA1 Message Date
Muyuan Li (from Dev Box)
f5b90e89da Address review: walk inner exceptions in IsExceptionFromUserPlugin
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-14 15:53:02 +08:00
copilot-swe-agent[bot]
5e62ab4955 Improve catch block comment in IsExceptionFromUserPlugin
Agent-Logs-Url: https://github.com/microsoft/PowerToys/sessions/cd2e901b-d98e-4f74-8fe3-714604091a9d

Co-authored-by: MuyuanMS <116717757+MuyuanMS@users.noreply.github.com>
2026-04-29 10:41:24 +00:00
copilot-swe-agent[bot]
f6890a0a5e Fix: suppress crash report popup for unhandled exceptions from user-installed plugins
When a third-party PowerToys Run plugin throws an unhandled exception that
propagates to the WPF dispatcher, PowerToys was showing a confusing crash
report dialog even though the exception was not caused by PowerToys itself.

Add IsExceptionFromUserPlugin() helper to ErrorReporting that inspects the
exception's stack trace frames. If any frame's assembly is loaded from
Constant.PluginsDirectory (the user-installed plugins folder), the exception
is treated as a plugin exception: it is logged at Error level but the crash
report window is not shown.

This fix was motivated by a third-party plugin
(Community.PowerToys.Run.Plugin.UniversalSearchSuggestions) throwing
DirectoryNotFoundException from UpdatePlugin() when the PowerToys Run Plugin
Updater directory didn't exist, causing a confusing crash popup on every launch.

Fixes #38033

Agent-Logs-Url: https://github.com/microsoft/PowerToys/sessions/cd2e901b-d98e-4f74-8fe3-714604091a9d

Co-authored-by: MuyuanMS <116717757+MuyuanMS@users.noreply.github.com>
2026-04-29 10:40:28 +00:00
copilot-swe-agent[bot]
f5eb3bdb09 Initial plan 2026-04-29 08:49:24 +00:00
2 changed files with 69 additions and 13 deletions

View File

@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Threading;
@@ -66,12 +67,79 @@ namespace PowerLauncher.Helper
var logger = LogManager.GetLogger(LoggerName);
logger.Error($"From {(isNotUIThread ? "non" : string.Empty)} UI thread's exception: {ExceptionFormatter.FormatException(e)}");
}
else if (IsExceptionFromUserPlugin(e))
{
// Exceptions thrown by user-installed third-party plugins should be logged
// but must not show the crash report window, since the exception is not
// caused by PowerToys itself and the popup is confusing and alarming to the user.
var logger = LogManager.GetLogger(LoggerName);
logger.Error($"From {(isNotUIThread ? "non" : string.Empty)} UI thread - Exception from user plugin: {ExceptionFormatter.FormatException(e)}");
}
else
{
Report(e, isNotUIThread);
}
}
/// <summary>
/// Determines whether an exception originated from a user-installed third-party plugin
/// (i.e., an assembly loaded from <see cref="Constant.PluginsDirectory"/>).
/// </summary>
private static bool IsExceptionFromUserPlugin(Exception e)
{
var current = e;
while (current != null)
{
if (HasPluginStackFrames(current))
{
return true;
}
if (current is AggregateException aggregateException)
{
foreach (var innerException in aggregateException.InnerExceptions)
{
if (IsExceptionFromUserPlugin(innerException))
{
return true;
}
}
}
current = current.InnerException;
}
return false;
}
private static bool HasPluginStackFrames(Exception e)
{
try
{
var pluginsDir = Constant.PluginsDirectory;
var stackTrace = new StackTrace(e, fNeedFileInfo: false);
foreach (var frame in stackTrace.GetFrames() ?? [])
{
var assemblyLocation = frame.GetMethod()?.DeclaringType?.Assembly?.Location;
if (!string.IsNullOrEmpty(assemblyLocation) &&
assemblyLocation.StartsWith(pluginsDir, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
}
catch (System.Exception)
{
// Reflection-based stack inspection can throw a variety of exceptions
// (e.g., SecurityException, TypeLoadException, BadImageFormatException).
// In every failure case the safe fallback is to treat the exception as
// NOT originating from a plugin so that it still surfaces as a crash report.
}
return false;
}
private static void Report(Exception e, bool waitForClose)
{
if (e != null)

View File

@@ -9,7 +9,6 @@ using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
@@ -136,18 +135,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
private void Frame_NavigationFailed(object sender, NavigationFailedEventArgs e)
{
var sourcePage = e.SourcePageType?.FullName ?? "<unknown>";
if (e.Exception is null)
{
Logger.LogWarning($"Navigation to '{sourcePage}' failed without an exception.");
}
else
{
Logger.LogError($"Navigation to '{sourcePage}' failed.", e.Exception);
}
e.Handled = true;
throw e.Exception;
}
private void Frame_Navigated(object sender, NavigationEventArgs e)