runtime: Process Start fails with URL on .NET Core 3

The following code works on .NET Framework but throws on .NET Core 3:

Process.Start("https://github.com");

System.ComponentModel.Win32Exception
  HResult=0x80004005
  Message=The system cannot find the file specified.
  Source=System.Diagnostics.Process
  StackTrace:
   at System.Diagnostics.Process.StartWithCreateProcess(ProcessStartInfo startInfo)
   at System.Diagnostics.Process.Start()
   at System.Diagnostics.Process.Start(ProcessStartInfo startInfo)
   at System.Diagnostics.Process.Start(String fileName)
   at NetCoreProcessStartBug.MainWindow.ButtonBase_OnClick(Object sender, RoutedEventArgs e) in c:\dev\NetCoreProcessStartBug\NetCoreProcessStartBug\MainWindow.xaml.cs:line 31
   at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
   at System.Windows.RouteItem.InvokeHandler(RoutedEventArgs routedEventArgs)
   at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
   at System.Windows.EventRoute.InvokeHandlers(Object source, RoutedEventArgs args)
   at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
   at System.Windows.UIElement.RaiseEvent(RoutedEventArgs e)
   at System.Windows.Controls.Primitives.ButtonBase.OnClick()
   at System.Windows.Controls.Button.OnClick()
   at System.Windows.Controls.Primitives.ButtonBase.OnMouseLeftButtonUp(MouseButtonEventArgs e)
   at System.Windows.UIElement.OnMouseLeftButtonUpThunk(Object sender, MouseButtonEventArgs e)
   at System.Windows.Input.MouseButtonEventArgs.InvokeEventHandler(Delegate genericHandler, Object genericTarget)
   at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)
   at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
   at System.Windows.RouteItem.InvokeHandler(RoutedEventArgs routedEventArgs)
   at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
   at System.Windows.EventRoute.ReInvokeHandlers(Object source, RoutedEventArgs args)
   at System.Windows.UIElement.ReRaiseEventAs(DependencyObject sender, RoutedEventArgs args, RoutedEvent newEvent)
   at System.Windows.UIElement.CrackMouseButtonEventAndReRaiseEvent(DependencyObject sender, MouseButtonEventArgs e)
   at System.Windows.UIElement.OnMouseUpThunk(Object sender, MouseButtonEventArgs e)
   at System.Windows.Input.MouseButtonEventArgs.InvokeEventHandler(Delegate genericHandler, Object genericTarget)
   at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)
   at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
   at System.Windows.RouteItem.InvokeHandler(RoutedEventArgs routedEventArgs)
   at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
   at System.Windows.EventRoute.InvokeHandlers(Object source, RoutedEventArgs args)
   at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
   at System.Windows.UIElement.RaiseTrustedEvent(RoutedEventArgs args)
   at System.Windows.UIElement.RaiseEvent(RoutedEventArgs args, Boolean trusted)
   at System.Windows.Input.InputManager.ProcessStagingArea()
   at System.Windows.Input.InputManager.ProcessInput(InputEventArgs input)
   at System.Windows.Input.InputProviderSite.ReportInput(InputReport inputReport)
   at System.Windows.Interop.HwndMouseInputProvider.ReportInput(IntPtr hwnd, InputMode mode, Int32 timestamp, RawMouseActions actions, Int32 x, Int32 y, Int32 wheel)
   at System.Windows.Interop.HwndMouseInputProvider.FilterMessage(IntPtr hwnd, WindowMessage msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at System.Windows.Interop.HwndSource.InputFilterMessage(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
   at System.Windows.Threading.Dispatcher.WrappedInvoke(Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
   at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
   at System.Windows.Threading.Dispatcher.Invoke(DispatcherPriority priority, Delegate method, Object arg)
   at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
   at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
   at System.Windows.Threading.Dispatcher.TranslateAndDispatchMessage(MSG& msg)
   at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
   at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
   at System.Windows.Threading.Dispatcher.Run()
   at System.Windows.Application.RunDispatcher(Object ignore)
   at System.Windows.Application.RunInternal(Window window)
   at System.Windows.Application.Run(Window window)
   at System.Windows.Application.Run()
   at NetCoreProcessStartBug.App.Main()

GitHub repro: https://github.com/onovotny/NetCoreProcessStartBug

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 16
  • Comments: 34 (22 by maintainers)

Commits related to this issue

Most upvoted comments

I believe the currently recommended workaround is to set UseShellExecute to true:

ProcessStartInfo psi = new ProcessStartInfo
{
    FileName = url,
    UseShellExecute = true
};
Process.Start (psi);

To clarify for others arriving here, the change is that in .NET Framework, UseShellExecute = true is the default, and in .NET Core UseShellExecute = false is the default. The reasoning is that UseShellExecute is very Windows-specific and significantly slower.

Can that be revisited? I suspect a lot of desktop apps use Process.Start to launch a web page and will break people.

Weird…I tried again and it worked. Maybe hit an up-to-date fluke when I tried before and it ran old code?

Anyway, I would still urge a reconsideration given that it’s another papercut people will have to sort through.

Actually it’s worse than that, here’s what you need to do it right so no cmd window appears:

var psi = new ProcessStartInfo
{
    FileName = "cmd",
    WindowStyle = ProcessWindowStyle.Hidden,
    UseShellExecute = false,
    CreateNoWindow = true,
    Arguments = $"/c start {link.AbsoluteUri}"
};
Process.Start(psi);

The method suggested by @dsplaisted works fine. Why does Microsoft change so many things in .net core? We have to unlearn stuff that we’ve been doing for years Querystring has now become Request.Query and so on…

I see it as more of keeping things from being stagnate. I’m all for the innovation and break changes if it means I can do more. Keeping up with the changes and continually learning is part of the fun being an engineer (imho).

It’s not just performance; it’s also consistency for library authors.

ShellExecute vs. CreateProcess have different feature sets, for example, when you want to redirect input/output. We feel it’s more important to have consistency between all operating systems than to remain behavior compatible with .NET Framework.

@mcp111 in this case the reasoning for this change is higher up - performance.

We’ve tried to maintain a breaking change list at https://github.com/dotnet/corefx/wiki/ApiCompat

To clarify for others arriving here, the change is that in .NET Framework, UseShellExecute = true is the default, and in .NET Core UseShellExecute = false is the default. The reasoning is that UseShellExecute is very Windows-specific and significantly slower.

Which is a very valid reason, thanks for clarifying.

Could anybody confirm if this code works on all platforms?

We’d like it to, but what happens on Unix is that if it doesn’t successfully launch directly, we have to find a program to attempt to use:

https://github.com/dotnet/runtime/blob/c50eae5725623c6d7b6de0d54e1010eb6e2c17fa/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Unix.cs#L414-L441

So that program needs to be installed

https://github.com/dotnet/runtime/blob/c50eae5725623c6d7b6de0d54e1010eb6e2c17fa/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Linux.cs#L109-L120

and on macOS and other Unix currently we don’t look for one. https://github.com/dotnet/runtime/blob/c50eae5725623c6d7b6de0d54e1010eb6e2c17fa/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.UnknownUnix.cs#L85-L89

So it’s “best effort”. If you have suggestions for doing better, please share as it would be good to do better.

@dsplaisted I tried that and it does not work.

You need this instead

ProcessStartInfo psi = new ProcessStartInfo
{
    FileName = "cmd",
    Arguments = "/c start https://www.baidu.com/s?wd=beijing%20time"
};
Process.Start(psi);

Which is ugly…and more likely to bite people, I think.