dotnet/wpf · error · InvalidOperationException
SR.FailToLaunchDefaultBrowser
Error message
SR.FailToLaunchDefaultBrowser
What it means
AppSecurityManager.ShellExecuteDefaultBrowser calls ShellExecuteEx to open the URI in the system default browser; when the native call returns false it throws InvalidOperationException wrapping a Win32Exception built from the last Win32 error. It means Windows refused to launch the default browser process, so the (unsafe) navigation could not be performed.
Solutions
- Inspect the inner Win32Exception's NativeErrorCode to identify the shell failure; fix the default browser registration (Settings > Default apps) or re-register the .htm/http association.
- Verify a default browser is installed and set; call ShellExecute directly on a known browser if none exists.
- Handle the exception in UnsafeLaunchBrowser and surface a friendly message or fall back to a manually chosen browser path (e.g. Process.Start with an explicit exe).
Example fix
// before
UnsafeLaunchBrowser(new Uri("https://example.com"));
// after
try
{
UnsafeLaunchBrowser(new Uri("https://example.com"));
}
catch (InvalidOperationException ex)
{
var win32 = ex.InnerException as Win32Exception;
logger.Warn($"Browser launch failed: {win32?.NativeErrorCode}");
Process.Start(new ProcessStartInfo("explorer.exe", uri.ToString()));
} Defensive patterns
Strategy: try-catch
Validate before calling
using var key = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(@"http\shell\open\command"); bool defaultBrowserRegistered = key != null;
Try / catch
try { UnsafeLaunchBrowser(uri); }
catch (InvalidOperationException ex)
when (ex.InnerException is System.ComponentModel.Win32Exception win32)
{
// handle win32.NativeErrorCode, prompt user or fall back
} Prevention
- Verify a default browser/ http association exists before shell-executing external URIs.
- Log the inner Win32Exception native error code for diagnosis.
- Provide a fallback launch path (explicit browser executable) for locked-down machines.
When it happens
Trigger: UnsafeLaunchBrowser path invoking ShellExecuteDefaultBrowser with a URI while ShellExecuteEx fails — e.g. no default browser is registered, the .htm class lookup fails, or the shell refuses execution (corrupt/missing association, policy restrictions).
Common situations: Machines with no default browser set or a broken http/.htm file association; locked-down corporate desktops blocking ShellExecute; securable navigation to external URLs from a WPF app.
Understand the failure class
Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.
Related errors
- ArgumentNullException
- InvalidOperationException
- InvalidOperationException
- new System.ComponentModel.Win32Exception(win32Error)
- PrintConfig.Provider.BindFail
AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14).
Data as JSON: /api/errors/e98d31502e48c764.
Report an issue: GitHub.
Appendix: source
Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/AppSecurityManager.cs:136
UnsafeNativeMethods.ShellExecuteInfo sei = new UnsafeNativeMethods.ShellExecuteInfo();
sei.cbSize = Marshal.SizeOf(sei);
sei.fMask = UnsafeNativeMethods.ShellExecuteFlags.SEE_MASK_FLAG_DDEWAIT;
/*
There is a bug on Windows Vista (with IE 7): ShellExecute via SEE_MASK_CLASSNAME fails for an
http[s]:// URL. It works fine for file://. The cause appears to be that the DDE command template
defined in HKCR\IE.AssocFile.HTM\shell\opennew\ddeexec is used: [file://%1",-1,,,,,]. On XP, the
the key used is (supposedly) HKCR\htmlfile\shell\opennew\ddeexec, and its value is ["%1",,-1,0,,,,].
The workaround here is to add the SEE_MASK_CLASSNAME flag only for non-HTTP URLs. For HTTP,
"plain" ShellExecute just works, incl. with Firefox/Netscape as the default browser.
*/
if (uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps)
{
sei.fMask |= UnsafeNativeMethods.ShellExecuteFlags.SEE_MASK_CLASSNAME;
sei.lpClass = ".htm"; // The default browser is looked up by this.
}
sei.lpFile = uri.ToString(); // It's safe to use Uri.ToString since there's an inheritance demand on it that prevents spoofing by subclasses.
if (!UnsafeNativeMethods.ShellExecuteEx(sei))
throw new InvalidOperationException(SR.FailToLaunchDefaultBrowser,
new System.ComponentModel.Win32Exception(/*uses the last Win32 error*/));
}
}
}
View on GitHub (pinned to 81131a70a4)