Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prompt user to install .Net Core Runtime #8222

Closed
mkArtakMSFT opened this issue Sep 11, 2019 · 32 comments
Closed

Prompt user to install .Net Core Runtime #8222

mkArtakMSFT opened this issue Sep 11, 2019 · 32 comments
Labels
Milestone

Comments

@mkArtakMSFT
Copy link

@mkArtakMSFT mkArtakMSFT commented Sep 11, 2019

From @Safirion on Wednesday, September 11, 2019 4:51:17 PM

Is your feature request related to a problem? Please describe.

When a user tries to run a non-autonomous .Net Core Desktop application (WPF) without having the .Net Core runtime (+ .NET Core Desktop Runtime) installed, nothing happens. The only thing that happens is the creation of a log in the windows event viewer.

Describe the solution you'd like

Like in .Net Framework, a pop-up prompt the user to install the required version of .Net Core Runtime by redirect him to https://dot.net or, in a perfect world, the required version of .Net Core Runtime is automatically downloaded and installed after asking the users :

image

At least, an error pop-up would be fine for .Net Core 3.0 release.

Copied from original issue: dotnet/aspnetcore#13898

@mkArtakMSFT

This comment has been minimized.

Copy link
Author

@mkArtakMSFT mkArtakMSFT commented Sep 11, 2019

From @blowdart on Wednesday, September 11, 2019 5:42:41 PM

This is the asp.net core repo, not the dotnet core repo which is over at https://github.com/dotnet/

@mkArtakMSFT can you move this?

@mkArtakMSFT

This comment has been minimized.

Copy link
Author

@mkArtakMSFT mkArtakMSFT commented Sep 11, 2019

@livarcocc I moved this over, but realized that this may have been moved to coreclr instead.

@jkotas jkotas transferred this issue from dotnet/corefx Sep 12, 2019
@dagood

This comment has been minimized.

Copy link
Member

@dagood dagood commented Sep 12, 2019

To confirm, by "non-autonomous", do you mean a Framework-dependent executable deployment? (Vs. a "autonomous" self-contained deployment?) I haven't heard this term used this way before.

@Safirion

This comment has been minimized.

Copy link

@Safirion Safirion commented Sep 12, 2019

Yes I mean framework dependent application.

@vitek-karas

This comment has been minimized.

Copy link
Member

@vitek-karas vitek-karas commented Sep 13, 2019

At one point in time the host did popup a dialog with the error for GUI apps - but we were not able to do it for all error cases consistently, so we switched to stderr + EventLog. Long discussion about this is here: #6412

That said, maybe we should treat missing frameworks special. It's probably the most common error and it does have a clear guidance of what to do to fix it. The other errors are much less actionable by an end user.

Just to set expectations: I don't think we will be able to have a UI which will actually run the installer itself, but it should show a URL to go to.

@albahari

This comment has been minimized.

Copy link

@albahari albahari commented Sep 18, 2019

Right now, GUI apps fail silently if netcore hasn't been installed, which is the worst kind of experience for the end-user. A simple messagebox would fix this.

In terms of a presenting a UI which runs the installer, would it work (in the Windows 10 case) to implement this as a separate executable written as a .NET Framework 4 WPF app which is installed to %programfiles%\dotnet\bootstrap.exe and is pushed out as part of Windows update? This would avoid adding complexity to the launcher. The launcher would need only a minimal change: to check for the presence of this bootstrapper should the required version of netcore be missing. If the bootstrapper is present in %programfiles%\dotnet, it would call it, passing its own path as a parameter. If the bootstrapper was absent, it would present the error in a messagebox instead.

@Symbai

This comment has been minimized.

Copy link

@Symbai Symbai commented Sep 30, 2019

This would drastically reduces the amount of work developers need to spend on support especially as of now where WPF / Winforms require TWO runtimes of .NET Core 3 in order to not crash silently.

A downloader would be great but I for myself would be very happy to just see an official notice which runtime is missing and where to get it from and with a native bootloader having all the desired information this should be way more easily to integrate than on .NET Framework.

@Safirion

This comment has been minimized.

Copy link

@Safirion Safirion commented Sep 30, 2019

A full .Net Core Runtime Installer would be great too... Actually I give the sdk download link to my customers instead of tell them to install 3 different runtimes (all needed for my app)

@richlander

This comment has been minimized.

Copy link
Member

@richlander richlander commented Oct 8, 2019

The UI pasted at the top of this issue is a great example to start the conversation, but the following one is really what is desired.

image

UX-wise, this comment nails it for me: #8368 (comment). We should have one model for frameworks, and apply it uniformly.

@Symbai

This comment has been minimized.

Copy link

@Symbai Symbai commented Oct 8, 2019

The UI in the first post is more user friendly. The UI @richlander posted is ugly, looks like a typical "Error with technical information, click instantly yes or ok and raise an issue to the developers the app is not working but throwing an error". This happens a LOT.

If there is a wish to have a uniform user information I would rather update the old and ugly one instead of, in year 2019, continue with that. Because everything that looks like an error instead of an information messagebox is instantly moved into a support ticket for devs. Don't ask me why but it is like that.

@richlander

This comment has been minimized.

Copy link
Member

@richlander richlander commented Oct 8, 2019

I get you, @Symbai. We don't really have the capability of doing that. The UI you posted is based on a large feature in Windows, which we cannot use. The UI that we show has to be implemented in the .NET Core host, which is a component that should have as few dependencies as possible and that we are working on making smaller. The UI I showed is the best we can do, from a technical standpoint.

@Safirion

This comment has been minimized.

Copy link

@Safirion Safirion commented Oct 8, 2019

It's a MessageBox. Just change type from "Error/Critical" to "Information" and it will be a lot better.
I totally agree with Symbai, this box will make users cry and they will create useless tickets on our support websites...

@richlander

This comment has been minimized.

Copy link
Member

@richlander richlander commented Oct 8, 2019

But it is a critical error. I don't know how to think of it otherwise. I also don't see how changing the icon will change user behavior so much that they won't create those useless tickets. You think otherwise?

@Symbai

This comment has been minimized.

Copy link

@Symbai Symbai commented Oct 8, 2019

First of all the original request was about the problem that currently the "why the app isn't working" is not visible for end users. This is solved by @richlander suggestion and I totally agree that, ignoring the design choice, having such a messagebox is MUCH better than having none.

I also understand that the screenshot in first post is part of Windows while the native bootstrapper which hosts the .NET app and is the only member that knows which .NET Core runtime the app needs and can natively check whether it's installed can likely only display a normal messagebox.

So up to that point, I'm totally fine with @richlander choice. But I personally would love to have this notice a bit more "official"-design. I don't know if there is a windows API that can be used to create a better looking message. But from my experience literally everything that looks like an "error" will not be read but instead a support ticket will be created. Even if the error tells the "why" + solution. The problem is that there are too many errors looking like this but have no meaning to end users. So yes, changing the icon will help (a bit). Having a total different design however helps a LOT because people stop by and read.

I would want @richlander solution to be implemented as fast as possible, because the longer it's left like this the more time and money it costs on support, and leave the "how we can improve this dialog" to the future. Because that is something that likely needs more time and isn't that much hurting as the silent crash.

@richlander

This comment has been minimized.

Copy link
Member

@richlander richlander commented Oct 8, 2019

First, I agree that the UI I shared is ugly. If we can get something better, I'm all over that proposal!

I also want to share that the existing .NET Framework experience gets a TON of people hitting "yes" and downloading .NET Framework based on it. The existing experience is successful. I obviously have no idea how many people don't click that button, but our website is busy all day every day serving up this and other pages as a result of clicking that button: https://dotnet.microsoft.com/download/dotnet-framework/net48. Does that help?

Thanks for engaging. Much appreciated.

@Symbai

This comment has been minimized.

Copy link

@Symbai Symbai commented Oct 8, 2019

Absolutely, it would help a lot. I just wonder where the button in your suggestion links to? I mean on .NET Core it would have to be the same version the app is compiled to unlike .NET Framework where it doesn't matter that much. Like a .NET Framework 4.5 app runs on .NET Framework 4.7.2 as well while on .NET Core a 3.0 might break on .NET Core 3.1. Thats why .NET Core is designed to support side-by-side installations as far as I know.

So is there a chance the button could link directly to the required .NET Core version download page instead of the latest? Maybe it could use the download script on the .NET Core site to look for the right version installer. This would also come a step closer to the requested feature of having it auto installing which then might be easier to implement later on.

@vatsan-madhavan

This comment has been minimized.

Copy link
Member

@vatsan-madhavan vatsan-madhavan commented Oct 8, 2019

Writing a nicer dialog in Win32 is easy - TaskDialogs make that very simple. Something like this took me just a couple of minutes to whip up.

image

@richlander

This comment has been minimized.

Copy link
Member

@richlander richlander commented Oct 8, 2019

That looks a lot nicer. Can we do that @vitek-karas ?

@vatsan-madhavan

This comment has been minimized.

Copy link
Member

@vatsan-madhavan vatsan-madhavan commented Oct 8, 2019

Here is the code.

You just need to create an icon that suits your needs and assign it a resource ID (IDI_NETCOREICON below). I just made up an icon that shows ".NET Core" - you might want to choose this more carefully and make up the icon professionally.

image

    #include <commctrl.h>
    #include <shellapi.h>
    // Link to comctl32.lib 


	LPCWSTR szTitle = L"ConsoleApplication9.exe - .NET Framework Initialization Error";
	LPCWSTR szHeader = L"To run this application, you must install one of the following versions of the .NET Framework: .NETFramework,Version=v4.0.1";
	LPCWSTR szBodyText = L"Would you like to downloadand install.NETFramework, Version = v4.0.1 now ? ";
	LPCWSTR pszExpandedInformation = L"<A HREF=\"https://dotnet.microsoft.com/download\">Download .NET Core</A>";

	int nClickedButton; 
	HRESULT hr = S_OK;

	TASKDIALOGCONFIG tdConfig = { 0 };
	tdConfig.cbSize = sizeof(TASKDIALOGCONFIG);
	tdConfig.hwndParent = hWndParent;
	tdConfig.hInstance = hInst;
	tdConfig.dwFlags = TDF_ENABLE_HYPERLINKS | TDF_SIZE_TO_CONTENT;
	tdConfig.dwCommonButtons = TDCBF_OK_BUTTON | TDCBF_CANCEL_BUTTON;
	tdConfig.pszWindowTitle = szTitle;
	tdConfig.hMainIcon = nullptr;
	tdConfig.pszMainIcon = MAKEINTRESOURCE(IDI_NETCOREICON);
	tdConfig.pszMainInstruction = szHeader;
	tdConfig.pszContent = szBodyText;
	tdConfig.nDefaultButton = IDOK;
	tdConfig.pszExpandedInformation = pszExpandedInformation;
	tdConfig.pfCallback = [](HWND hwnd, UINT uNotification, WPARAM wParam, LPARAM lParam, LONG_PTR lpRefData) -> HRESULT
	{
		if (uNotification == TDN_HYPERLINK_CLICKED && lParam != 0)
		{
			LPCWSTR uri = reinterpret_cast<LPCWSTR>(lParam);
			ShellExecute(hwnd, L"open", uri, nullptr, nullptr, SW_SHOW);
		}
		return S_OK;
	};

	hr = TaskDialogIndirect(&tdConfig, &nClickedButton, nullptr, nullptr);
	return hr;

It will also need an app.manifest like this (requires comctl32 v6, avaialble on Windows Vista +).

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
    version="1.0.0.0"
    processorArchitecture="*"
    name="CompanyName.ProductName.YourApplication"
    type="win32"
/>
<description>Your application description here.</description>
<dependency>
    <dependentAssembly>
        <assemblyIdentity
            type="win32"
            name="Microsoft.Windows.Common-Controls"
            version="6.0.0.0"
            processorArchitecture="*"
            publicKeyToken="6595b64144ccf1df"
            language="*"
        />
    </dependentAssembly>
</dependency>
</assembly>
@vitek-karas

This comment has been minimized.

Copy link
Member

@vitek-karas vitek-karas commented Oct 8, 2019

Thanks for the code. I tried it, but unfortunately the manifest is a big problem. Without it this fails horribly (it actually pops a dialog instead of reporting an error code - pretty bad experience). With it, it works. But we don't control the manifest embedded in the .exe. It is fully controlled by the app which builds it. We simply copy any manifest the managed app has.

We only control the manifest for dotnet.exe, but nobody uses that to run UI apps. Otherwise this would work great. I'll ask around if there's some other way to make it work...

@vatsan-madhavan

This comment has been minimized.

Copy link
Member

@vatsan-madhavan vatsan-madhavan commented Oct 8, 2019

@vitek-karas, mind giving this a try instead of using the manifest?

#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")

Put it in the header before including <commctrl.h>

@vitek-karas

This comment has been minimized.

Copy link
Member

@vitek-karas vitek-karas commented Oct 8, 2019

That's basically the same as having the app.manifest - linker will add the right XML to the manifest. The problem with apphost is that we build it in our official build, but then when the app is built on developer's machine, the apphost.exe gets renamed to app.exe and all the native resources from the app.dll are copied to it. So it must not have any native resources to start with (otherwise we would create duplicates and possibly cause issues).

Also - adding this to the manifest (assuming we would instead merge it somehow during the SDK build) could change the behavior of the app - and potentially break it.

If there would be a way to do this programmatically - right before we call the TaskDialog that would be best. In that case breaking the app is not a concern, since the app won't run (it already failed), so we can "ruin" the process in any way want.

@richlander

This comment has been minimized.

Copy link
Member

@richlander richlander commented Oct 8, 2019

I propose we get the ugly dialog option into 3.1 and then see if we can improve on it for 5.0. We don't have a lot of time and don't want to go with a complicated option that might not be fully proven. Cool?

@vatsan-madhavan

This comment has been minimized.

Copy link
Member

@vatsan-madhavan vatsan-madhavan commented Oct 9, 2019

This can be done using a helper DLL and ISOLATION_AWARE_ENABLED, which uses activation context API's behind the scene to get the desired results.

  • Create a helper DLL - let's call this TaskDlgLibrary.dll
    • Before #include <windows.h>, add #define ISOLATION_AWARE_ENABLED 1
#pragma once
#define WIN32_LEAN_AND_MEAN             // Exclude rarely-used stuff from Windows headers
// Windows Header Files
#define ISOLATION_AWARE_ENABLED 1
#include <windows.h>
#include <CommCtrl.h>
#pragma (lib, "comctrl32.lib")
  • Add an application manifest to this DLL app.manifest as before.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
    version="1.0.0.0"
    processorArchitecture="*"
    name="TaskDlgLibrary"
    type="win32"
/>
<description>Your application description here.</description>
<dependency>
    <dependentAssembly>
        <assemblyIdentity
            type="win32"
            name="Microsoft.Windows.Common-Controls"
            version="6.0.0.0"
            processorArchitecture="*"
            publicKeyToken="6595b64144ccf1df"
            language="*"
        />
    </dependentAssembly>
</dependency>
</assembly>
  • export a helper function that shows the dialog like this:
HRESULT __declspec(dllexport) ShowTaskDialogImpl(HINSTANCE hInst, HWND hWndParent, PCWSTR pszMainIcon)
{
	LPCWSTR szTitle = L"ConsoleApplication9.exe - .NET Framework Initialization Error";
	LPCWSTR szHeader = L"To run this application, you must install one of the following versions of the .NET Framework: .NETFramework,Version=v4.0.1";
	LPCWSTR szBodyText = L"Would you like to downloadand install.NETFramework, Version = v4.0.1 now ? ";
	LPCWSTR pszExpandedInformation = L"<A HREF=\"https://dotnet.microsoft.com/download\">Download .NET Core</A>";

	int nClickedButton;

	TASKDIALOGCONFIG tdConfig = { 0 };
	tdConfig.cbSize = sizeof(TASKDIALOGCONFIG);
	tdConfig.hwndParent = hWndParent;
	tdConfig.hInstance = hInst;
	tdConfig.dwFlags = TDF_ENABLE_HYPERLINKS | TDF_SIZE_TO_CONTENT;
	tdConfig.dwCommonButtons = TDCBF_OK_BUTTON | TDCBF_CANCEL_BUTTON;
	tdConfig.pszWindowTitle = szTitle;
	tdConfig.hMainIcon = nullptr;
	tdConfig.pszMainIcon = MAKEINTRESOURCE(IDI_NETCOREICON);
	tdConfig.pszMainInstruction = szHeader;
	tdConfig.pszContent = szBodyText;
	tdConfig.nDefaultButton = IDOK;
	tdConfig.pszExpandedInformation = pszExpandedInformation;
	tdConfig.pfCallback = [](HWND hwnd, UINT uNotification, WPARAM wParam, LPARAM lParam, LONG_PTR lpRefData) -> HRESULT
	{
		if (uNotification == TDN_HYPERLINK_CLICKED && lParam != 0)
		{
			LPCWSTR uri = reinterpret_cast<LPCWSTR>(lParam);
			ShellExecute(hwnd, L"open", uri, nullptr, nullptr, SW_SHOW);
		}
		return S_OK;
	};


	return TaskDialogIndirect(&tdConfig, &nClickedButton, nullptr, nullptr);

}
  • TaskDialogIndirect will in turn call into IsolationAwareTaskDialogIndirect, and will use activation context API's to load comctl32 v6 in a separate activation context seamlessly, obtain the proc address for TaskDialogIndirect, call it etc.

From your main executable, just call into this function when you need to - that's it.

I'm attaching a solution that shows how this works.

TaskDialogExample.zip

@richlander

This comment has been minimized.

Copy link
Member

@richlander richlander commented Oct 9, 2019

The solution cannot involve creating another binary. We only have apphost.exe. I sincerely appreciate your enthusiasm to improve this experience (it could definitely be improved!) but I'm not seeing a path forward given our constraints, which also includes not taking on any new risk.

@Symbai

This comment has been minimized.

Copy link

@Symbai Symbai commented Oct 9, 2019

3.1 release is pretty close and I really want to see having at least something rather than nothing. I agree with adding the ugly message for 3.1 and then see if we can make it look nicer for 5.0, unless it turns out there is a safe and fast way to add @vatsan-madhavan solutions which is exactly what I had in mind.

@vitek-karas

This comment has been minimized.

Copy link
Member

@vitek-karas vitek-karas commented Oct 9, 2019

@Symbai

This comment has been minimized.

Copy link

@Symbai Symbai commented Oct 14, 2019

FYI: Something not mentioned and not showing up in the messagebox screenshot examples: The messagebox needs to tell the end user whether x64 or x86 is missing when it redirects them to https://dotnet.microsoft.com/download/dotnet-core/3.0/runtime/desktop for example as long as both bitness are shown at the same download page.

@dagood

This comment has been minimized.

Copy link
Member

@dagood dagood commented Oct 14, 2019

Good catch, I believe that's particularly important for WindowsDesktop.

I think it will be common for devs to create x86 FDE apps so that they can publish one artifact that runs on both x64 and x86 machines. That requires users on 64-bit Windows to install the x86 runtimes. Without discoverability here, many users will install the runtimes matching their OS's bitness. When they launch the FDE app, they'll hit a "runtime not found" error with no indication they installed the wrong bitness and no documentation on the website suggesting that they may need a different one.

A fix is tracked here: #6396 "Can't find compatible framework message should specify x86/x64 on Windows"

A mitigation would be describing this on the website. I remember in early discussion we had some words in mind like "if you're not sure, install all four" but it isn't in the current version.

@dagood

This comment has been minimized.

Copy link
Member

@dagood dagood commented Oct 14, 2019

#8509 (comment) does mention that the dialog should include x86/x64 in the URL it directs to that filters what's shown on the website. Maybe that's enough. I have a little concern that people will see x86 and think it's a mistake, but maybe not.

@elinor-fung

This comment has been minimized.

Copy link
Member

@elinor-fung elinor-fung commented Oct 22, 2019

Basic/ugly dialog is in 3.1. Filed #8620 for making it nicer.

@EatonZ

This comment has been minimized.

Copy link

@EatonZ EatonZ commented Dec 19, 2019

I think there should be some tweaks done to the dialog put into 3.1 to make it easier for users. See my comment in the new issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
10 participants
You can’t perform that action at this time.