// 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; /// /// Global error handler for Command Palette. /// 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, isRecoverable: true); } private void HandleException(Exception ex, Context context, bool isRecoverable = false) { 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); PInvoke.MessageBox( HWND.Null, "Command Palette has encountered a fatal error and must close.\n\nAn error report has been saved to your desktop.", "Unhandled Error", 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 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, } }