How to Customize the Microsoft Exception Message Box in .NETWhen an unhandled exception occurs in a .NET application, Windows or the .NET runtime can display a default exception message box that informs users an error happened and often gives options such as viewing details, sending a report, or closing the application. While built-in message boxes are useful for quick diagnostics, production-ready applications usually require more control: user-friendly text, proper logging, internationalization, and options for recovery or safe shutdown.
This article explains how the default exception message box behavior works, shows multiple approaches to customize and replace it, and provides practical examples you can adapt to Windows Forms, WPF, and console applications. You’ll also learn best practices for error presentation, security considerations, and integration with telemetry systems.
Table of contents
- How the default exception message box appears
- Why replace or customize it
- Global exception handling strategies in .NET
- Approaches to customizing the exception UI
- Replace with a custom dialog (WinForms/WPF)
- Use Application Recovery and Restart (Windows API)
- Hook into Windows Error Reporting (WER)
- Create a dedicated crash handler process
- Silent logging and user-friendly notifications
- Implementation examples
- Windows Forms: global handler + custom dialog
- WPF: DispatcherUnhandledException + custom window
- Console app: AppDomain and unhandled exceptions
- Native interop: Registering for Windows error reporting
- Best practices
- Security & privacy considerations
- Telemetry and reporting integration
- Conclusion
1. How the default exception message box appears
When a .NET unhandled exception bubbles to the top of the thread, .NET (or Windows for some native crashes) may show a message box such as “Microsoft .NET Framework — Application Name has stopped working” or a Windows Error Reporting dialog. That dialog is controlled by the runtime/OS and is not directly customizable from managed code.
Key point: For managed unhandled exceptions, you can intercept them before the runtime shows the default dialog by registering appropriate handlers; for native crashes or CLR hard-failures the OS/WER may be invoked and is harder to control.
2. Why replace or customize it
- Provide clearer, actionable messages to end users (avoid technical stack traces).
- Offer graceful recovery options (restart, save work, send report).
- Ensure consistent branding and localization.
- Capture diagnostics and telemetry before the process exits.
- Comply with privacy or security policies (filter stack traces, prompt for consent before sending data).
3. Global exception handling strategies in .NET
Register handlers at the application domain and UI levels to catch exceptions before the default dialog shows:
- AppDomain.CurrentDomain.UnhandledException — catches exceptions on threads without a synchronization context. Note: handler runs on the thread where exception occurred or may run on the runtime’s finalizer; process will typically terminate afterward.
- Application.ThreadException (WinForms) — handles exceptions on the UI thread; allows continuing the app if handled.
- DispatcherUnhandledException (WPF) — for the WPF UI thread; setting e.Handled = true prevents termination.
- TaskScheduler.UnobservedTaskException — catches exceptions from faulted Tasks that weren’t observed; by default these do not crash the app but can be made to.
Example registrations:
// WinForms (Program.cs) Application.ThreadException += (s, e) => ShowCustomError(e.Exception); AppDomain.CurrentDomain.UnhandledException += (s, e) => LogAndShow(e.ExceptionObject as Exception); // WPF (App.xaml.cs) DispatcherUnhandledException += (s, e) => { ShowCustomWindow(e.Exception); e.Handled = true; } AppDomain.CurrentDomain.UnhandledException += ...; TaskScheduler.UnobservedTaskException += (s, e) => { e.SetObserved(); Log(e.Exception); };
4. Approaches to customizing the exception UI
- Replace the UI entirely with a custom dialog or window that matches your app’s branding. Best for desktop apps.
- Use Application Recovery and Restart (ARR) APIs to save state and attempt recovery.
- Integrate with Windows Error Reporting (WER) to provide custom consent dialogs or attach custom dump collection (requires native code and registry configuration).
- Launch a separate crash handler process to present UI and collect diagnostics (useful when main process may be unstable).
- Avoid showing the technical stack trace to end users; show options like “Restart app”, “Save work”, and “Send report”, while logging full diagnostics to disk/telemetry.
5. Implementation examples
Windows Forms: Global handler + custom dialog
- Register handlers in Program.Main before Application.Run.
- Implement a safe, minimal UI for the error dialog that avoids complex dependencies.
- Log details to disk and optionally upload in background.
Example:
// Program.cs [STAThread] static void Main() { Application.SetHighDpiMode(HighDpiMode.SystemAware); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.ThreadException += Application_ThreadException; AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException; Application.Run(new MainForm()); } private static void Application_ThreadException(object sender, ThreadExceptionEventArgs e) { ShowErrorDialog(e.Exception, canContinue: true); } private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) { var ex = e.ExceptionObject as Exception; LogException(ex); ShowErrorDialog(ex, canContinue: false); }
Design a minimal dialog form to show a friendly message, an option to copy technical details, and buttons for “Restart” or “Exit.” Save full dump/logs to a known folder.
WPF: DispatcherUnhandledException + custom window
In App.xaml.cs:
protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); DispatcherUnhandledException += OnDispatcherUnhandledException; AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; } private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) { ShowErrorWindow(e.Exception); e.Handled = true; // prevents default dialog if appropriate }
Keep the error window simple and non-dependent on complex services (avoid remote calls).
Console applications
For console apps, catch exceptions in Main and subscribe to AppDomain.CurrentDomain.UnhandledException to log a friendly message and the technical details to a file. Do not rely on a GUI.
Example:
static int Main(string[] args) { AppDomain.CurrentDomain.UnhandledException += (s, e) => { var ex = e.ExceptionObject as Exception; File.WriteAllText("crash.log", ex?.ToString() ?? "Unknown error"); }; try { return RunApp(args); } catch (Exception ex) { File.AppendAllText("crash.log", ex.ToString()); Console.WriteLine("An unexpected error occurred. Details were written to crash.log"); return -1; } }
Native interop & Windows Error Reporting (WER)
For native crashes or to control OS-level error reporting:
- Configure WER using registry keys to collect custom dump files or launch a custom UI (requires admin and careful testing).
- Use SetUnhandledExceptionFilter or write a native wrapper process to catch native exceptions, create a dump, and then show a managed UI.
This approach is advanced and platform-specific; test across Windows versions.
6. Best practices
- Always log full exception details (stack trace, inner exceptions, environment) to a secured location before showing UI.
- Keep custom error UI minimal and robust; avoid complex dependencies that can fail during an exception.
- Give users clear, non-technical language with an option to view technical details if they want.
- Provide options: restart, save, send report—don’t force automatic uploads without consent.
- For services/servers, prefer silent logging and alerting over user-facing dialogs.
7. Security & privacy considerations
- Scrub or prompt before sending sensitive data (PII, tokens).
- Store logs/dumps securely and rotate/delete them per retention policy.
- If uploading reports, use secure channels (HTTPS) and obtain user consent where required by law.
8. Telemetry and reporting integration
- Integrate with systems like Application Insights, Sentry, or custom endpoints. Capture breadcrumbs, user actions, and environment metadata.
- Ensure correlation IDs are generated so postmortem analysis can link crashes to user sessions and server-side logs.
- Consider uploading minidumps for native failures and include them in telemetry payloads.
9. Conclusion
Customizing the Microsoft exception message box experience in .NET requires intercepting unhandled exceptions, presenting a safe and friendly UI, and collecting diagnostics reliably. For desktop apps, register UI and domain-level handlers and show a simple custom window; for native-level control, integrate with WER or use a separate crash handler. Always balance user experience with privacy, security, and robust logging.
If you want, I can provide a downloadable sample project (WinForms/WPF) that demonstrates the handlers, logging, and a polished custom error dialog.
Leave a Reply