mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 11:48:06 +01:00
## Summary of the Pull Request
This PR localizes the message displayed when an unhandled exception
causes CmdPal to crash. It also resolves a regression from commit
a991a118dc, which disabled storing to the desktop but failed to
update the corresponding message.
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
- [x] Related to: #41392
- [ ] **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
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx
<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
150 lines
5.6 KiB
C#
150 lines
5.6 KiB
C#
// Copyright (c) Microsoft Corporation
|
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
// See the LICENSE file in the project root for more information.
|
|
|
|
using ManagedCommon;
|
|
using Microsoft.CmdPal.Core.Common.Helpers;
|
|
using Windows.Win32;
|
|
using Windows.Win32.Foundation;
|
|
using Windows.Win32.UI.WindowsAndMessaging;
|
|
using SystemUnhandledExceptionEventArgs = System.UnhandledExceptionEventArgs;
|
|
using XamlUnhandledExceptionEventArgs = Microsoft.UI.Xaml.UnhandledExceptionEventArgs;
|
|
|
|
namespace Microsoft.CmdPal.UI.Helpers;
|
|
|
|
/// <summary>
|
|
/// Global error handler for Command Palette.
|
|
/// </summary>
|
|
internal sealed partial class GlobalErrorHandler
|
|
{
|
|
// GlobalErrorHandler is designed to be self-contained; it can be registered and invoked before a service provider is available.
|
|
internal void Register(App app)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(app);
|
|
|
|
app.UnhandledException += App_UnhandledException;
|
|
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
|
|
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
|
|
}
|
|
|
|
private void App_UnhandledException(object sender, XamlUnhandledExceptionEventArgs e)
|
|
{
|
|
// Exceptions thrown on the main UI thread are handled here.
|
|
if (e.Exception != null)
|
|
{
|
|
HandleException(e.Exception, Context.MainThreadException);
|
|
}
|
|
}
|
|
|
|
private void CurrentDomain_UnhandledException(object sender, SystemUnhandledExceptionEventArgs e)
|
|
{
|
|
// Exceptions thrown on background threads are handled here.
|
|
if (e.ExceptionObject is Exception ex)
|
|
{
|
|
HandleException(ex, Context.AppDomainUnhandledException);
|
|
}
|
|
}
|
|
|
|
private void TaskScheduler_UnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
|
|
{
|
|
// This event is raised only when a faulted Task is garbage-collected
|
|
// without its exception being observed. It is NOT raised immediately
|
|
// when the Task faults; timing depends on GC finalization.
|
|
e.SetObserved();
|
|
HandleException(e.Exception, Context.UnobservedTaskException);
|
|
}
|
|
|
|
private static void HandleException(Exception ex, Context context)
|
|
{
|
|
Logger.LogError($"Unhandled exception detected ({context})", ex);
|
|
|
|
if (context == Context.MainThreadException)
|
|
{
|
|
var error = DiagnosticsHelper.BuildExceptionMessage(ex, null);
|
|
var report = $"""
|
|
This is an error report generated by Windows Command Palette.
|
|
If you are seeing this message, it means the application has encountered an unexpected issue.
|
|
You can help us fix it by filing a report at https://aka.ms/powerToysReportBug.
|
|
{error}
|
|
""";
|
|
|
|
StoreReport(report, storeOnDesktop: false);
|
|
|
|
string message;
|
|
string caption;
|
|
try
|
|
{
|
|
message = ResourceLoaderInstance.GetString("GlobalErrorHandler_CrashMessageBox_Message");
|
|
caption = ResourceLoaderInstance.GetString("GlobalErrorHandler_CrashMessageBox_Caption");
|
|
}
|
|
catch
|
|
{
|
|
// The resource loader may not be available if the exception occurred during startup.
|
|
// Fall back to hardcoded strings in that case.
|
|
message = "Command Palette has encountered a fatal error and must close.";
|
|
caption = "Command Palette - Fatal error";
|
|
}
|
|
|
|
PInvoke.MessageBox(
|
|
HWND.Null,
|
|
message,
|
|
caption,
|
|
MESSAGEBOX_STYLE.MB_ICONERROR);
|
|
}
|
|
}
|
|
|
|
private static string? StoreReport(string report, bool storeOnDesktop)
|
|
{
|
|
// Generate a unique name for the report file; include timestamp and a random zero-padded number to avoid collisions
|
|
// in case of crash storm.
|
|
var name = FormattableString.Invariant($"CmdPal_ErrorReport_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{Random.Shared.Next(100000):D5}.log");
|
|
|
|
// Always store a copy in log directory, this way it is available for Bug Report Tool
|
|
string? reportPath = null;
|
|
if (Logger.CurrentVersionLogDirectoryPath != null)
|
|
{
|
|
reportPath = Save(report, name, static () => Logger.CurrentVersionLogDirectoryPath);
|
|
}
|
|
|
|
// Optionally store a copy on the desktop for user (in)convenience
|
|
if (storeOnDesktop)
|
|
{
|
|
var path = Save(report, name, static () => Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory));
|
|
|
|
// show the desktop copy if both succeeded
|
|
if (path != null)
|
|
{
|
|
reportPath = path;
|
|
}
|
|
}
|
|
|
|
return reportPath;
|
|
|
|
static string? Save(string reportContent, string reportFileName, Func<string> directory)
|
|
{
|
|
try
|
|
{
|
|
var logDirectory = directory();
|
|
Directory.CreateDirectory(logDirectory);
|
|
var reportFilePath = Path.Combine(logDirectory, reportFileName);
|
|
File.WriteAllText(reportFilePath, reportContent);
|
|
return reportFilePath;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.LogError("Failed to store exception report", ex);
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
private enum Context
|
|
{
|
|
Unknown = 0,
|
|
MainThreadException,
|
|
BackgroundThreadException,
|
|
UnobservedTaskException,
|
|
AppDomainUnhandledException,
|
|
}
|
|
}
|