As we rebuild Microsoft Edge atop the Chromium open-source platform, we are working through various scenarios that behave differently in the new browser. In most cases, such scenarios also worked differently between 2018’s Edge Legacy (aka “Spartan”) and Chrome, but users either weren’t aware of the difference (because they used Trident-derived browsers inside their enterprise) or were aware and simply switched to a Microsoft-browser for certain tasks.
One example of a behavioral gap is related to running ClickOnce apps. Update: ClickOnce support is now available in modern Edge; see the end of this post.
ClickOnce is a Microsoft application deployment framework that aims to allow installation of native-code applications from the web in (around) one click.
However, it’s also possible to author and deploy an .application that doesn’t specify a deploymentProvider element (example). Such files launch correctly from Internet Explorer and pre-Chromium Edge, but fail in Firefox and Chrome with an error message:
So, what gives? Why does this scenario magically work in Edge Legacy but not Firefox or Chrome?
The secret can be found in the EditFlags for the Application.Manifest ProgId (to which the .application filename extension and application/x-ms-application MIME type are mapped):
The EditFlags contain the FTA_AlwaysUseDirectInvoke flag, which is documented on MSDN as
Introduced in Windows 8. Ensures that the verbs for the file type are invoked with a URL instead of a downloaded version of the file. Use this flag only if you’ve registered the file type’s verb to support DirectInvoke through the SupportedProtocols or UseUrl registration.
If you peek in the
Shell\Open\Command value, you’ll find that it calls for running the
ShOpenVerbApplication function inside
dfshim.dll, passing along the .application file’s path or URL in a parameter (
“C:\Windows\System32\rundll32.exe” “C:\Windows\System32\dfshim.dll”,ShOpenVerbApplication %1
And therein lies the source of the behavioral difference.
When you download and open an Application.Manifest file from Edge Legacy, it passes the source URL for the .application to the handler. When you download the file in Firefox or Chrome, it passes the local file path of the downloaded .application file. With only the local file path, the
ShOpenVerbApplication function doesn’t know how to resolve the relative references in the Application Manifest’s XML and the function bails out with the Cannot Start Application error message.
FTA_AlwaysUseDirectInvoke also has the side-effect of removing the “Save” button from Edge Legacy’s download manager:
…helping prevent the user from accidentally downloading an .application file that won’t work if opened outside of the browser from the Downloads folder (since the file’s original URL isn’t readily available to Windows Explorer).
Advice to Publishers
If you’re planning to distribute your ClickOnce application from a website, specify the URL in Visual Studio’s ClickOnce Publish Wizard:
This will ensure that even if DirectInvoke isn’t used,
ShOpenVerbApplication can still find the files needed to install your application.
Unfortunately, the extension is a bit hacky– it installs a blocking onBeforeRequest handler which watches all requests (not just downloads), and if the target URL’s path component ends in .application, it invokes the native executable. Alas, it’s not really safe to make any assumptions about extensions in URLs (the web is based on MIME types, rather than filenames).
ClickOnce support is available in the new Edge 77+. It’s off-by-default prior to Edge 87, but can be enabled via
edge://flags/#edge-click-once or Group Policy. Note: ClickOnce on Windows 7 was broken, but will be supported starting in Edge 91.
Note that the ClickOnce implementation in Edge will always1 prompt the user before the handler is invoked. In Edge Legacy/IE, sites in your Intranet/Trusted Zone could spawn the
.application handler without any prompt. That’s because these older browsers respect the FTA_OpenIsSafe bit in the
EditFlags for the
application.manifest progid. The new Edge doesn’t really use Windows Security Zones as a primitive, and it thus does not support the
Do you use ClickOnce to deploy your applications? If so, are you specifying the deployment URL in the manifest file?
Appendix A: IE Implementation
Notably, Internet Explorer (and thus IE Mode) doesn’t rely upon the DirectInvoke mechanism for ClickOnce; removing the
EditFlags value entirely causes IE to show an additional prompt but the install still succeeds. That’s because IE activates the file using a MIME handler (see the CLSID subkey of
Application.Manifest) much like it does for .ZIP files. The DirectInvoke mechanism was invented, in part, to replace the legacy MIME handler mechanism.
Appendix B: Edge Launch Problems?
If you have a problem whereby each time you click “Open” on the ClickOnce prompt, the browser simply opens a new tab to ask the same question, this is caused by buggy security software. Edge handles ClickOnce by passing the URL into
ShellExecuteEx, with the
SEE_CLASS_MASK flag set and the
hkeyClass pointed at the
ASSOCKEY_SHELLEXECCLASS for the
.application file extension:
parameters.fMask = SEE_MASK_NOZONECHECKS | SEE_MASK_NOASYNC | SEE_MASK_CLASSKEY; std::wstring lpFile = base::UTF8ToWide(url.spec()); parameters.lpFile = lpFile.c_str(); parameters.hkeyClass = handler_class; parameters.nShow = SW_SHOWNORMAL; ShellExecuteExW(¶meters);
It appears that the security software’s thunk does not understand the significance of the
SEE_MASK_CLASSKEY and it effectively strips it out.
ShellExecuteEx, thus handed what is effectively a plain old HTTPS URL, then launches the default web browser passing the URL. We then end up with a new tab trying to invoke the ClickOnce URL, and the process repeats, creating a new tab each time you click the Open button. If your admin had set the policy to allow ClickOnce to automatically open without a prompt, I assume the browser will endlessly open tabs until your machine melts.
1 Not really always. Starting in Edge 93, you can use the AutoOpenFileTypes and AutoOpenAllowedForUrls policies to bypass prompts for both ClickOnce and DirectInvoke. If you configure
.application files to automatically open, the ClickOnce prompt will be bypassed and ClickOnce will launch from sites of your choosing. I don’t think this is a great idea (removing one click doesn’t seem worth the increase in attack surface), but it works.