Demystifying ClickOnce

Update: ClickOnce support is now available in modern Edge; see the end of this post.

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 like IE 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.

ClickOnce is a Microsoft application deployment framework that aims to allow installation of native-code applications from the web in (approximately) one click.

Chrome and Firefox can successfully install and launch ClickOnce’s .application files if the .application file specifies a deploymentProvider element with a codebase attribute (example). After download, the user double-clicks on the downloaded .application file in the Downloads folder and the install proceeds:

ClickOnce Install Prompt

However, it’s also possible to author and deploy an .application that doesn’t specify a deploymentProvider element (example). Such files will launch correctly from Internet Explorer and pre-Chromium Edge, but fail for downloads from Firefox and Chrome with an error message:

ApplicationCannotBeStarted
ClickOnce fails for a downloaded .application file.

So, what gives? Why does this scenario magically work in Edge Legacy but not Firefox or Chrome?

DirectInvoke

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).

ApplicationManifestRegistry
Registry settings for the Application.Manifest ProgId.

The EditFlags contain the FTA_AlwaysUseDirectInvoke flag, which is documented on MSDN as:

FTA_AlwaysUseDirectInvoke 0x00400000
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.

I wrote more about DirectInvoke here; if you peek in the Application.Manifest‘s 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 (%1):

"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.

Setting FTA_AlwaysUseDirectInvoke also has the side-effect of removing the “Save” button from Edge Legacy’s download manager:

NoSave

…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:

Manifest
Specify “From a Web site” in the ClickOnce Publish Wizard.

This will ensure that even if DirectInvoke isn’t used (e.g. from Chrome or Firefox), the invocation of the ShOpenVerbApplication function can still find the files needed to install your application.

Workarounds for Firefox & Chrome

A company called Meta4 offers a Chrome browser extension that aims to add fuller support for ClickOnce to Chrome. The extension comes in two pieces– a traditional JavaScript extension and a trivial “native” executable (written in C#) that simply invokes the ShOpenVerbApplication call with the URL. The JavaScript extension launches and communicates with the native executable running outside of the Chrome sandbox using Native Messaging.

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).

WARNING: DO NOT INSTALL “ClickOnce” extensions like this one into Microsoft Edge. The extension will break Edge’s built-in ClickOnce handling.

Edge’s ClickOnce Support

ClickOnce support was added to Edge 77+, and is now on-by default.

The feature was disabled-by-default prior to Edge 87, but could be enabled via edge://flags/#edge-click-once or Group Policy. ClickOnce on Windows 7 was not working correctly until 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 Sites Zone could spawn the .application handler without any prompt from the browser. That’s because these older browsers respect the FTA_OpenIsSafe bit in the EditFlags for the application.manifest progid. The new Edge tries to limit its use of Windows Security Zones, and it thus does not support the FTA_OpenIsSafe bit.

-Eric

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: Launch-from-Edge 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(&parameters);

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.

Appendix C: File URLs with non-ASCII characters

Recently, a user of Edge 120 noted that Edge doesn’t successfully launch ClickOnce applications from file:/// schemed URLs that contain non-English characters. The problem here is that Chromium %-escapes the non-English characters into UTF-8, such that a link to \\server\files\Testä.application is written as file://server/files/Test%C3%A4.application. Unfortunately, for historical reasons, Windows expects %-encoded octets in file URLs to be encoded as %-escaped into the client OS’ ANSI codepage, so for my US-English system, that would be file://server/files/Test%E4.application.

After the user clicks “Open” in Edge’s ClickOnce security prompt, they’ll see a dialog from Windows complaining that the file cannot be found:

To workaround this problem, serve your files from HTTPS, or avoid using non-ASCII characters in your application’s filename.


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 think configuring Edge in this way is a bad tradeoff (the convenience of removing one click doesn’t seem worth an increase in attack surface), but it is supported.

Published by ericlaw

Impatient optimist. Dad. Author/speaker. Created Fiddler & SlickRun. PM @ Microsoft 2001-2012, and 2018-, working on Office, IE, and Edge. Now a GPM for Microsoft Defender. My words are my own, I do not speak for any other entity.

40 thoughts on “Demystifying ClickOnce

  1. Semi related: At at one point the Chrome installer used ClickOnce for its first stage.

    1. Indeed, it’s still live, but only gets triggered in certain scenarios (IE on Win7, perhaps). The Chrome . application file, alas, does not specify the deployment codebase attribute.

  2. My Click Once app stopped working all of a sudden last week. I have been using the ClickOnce for Google extension without problems for quite some time.

    Last week, when I click to launch the app, it now tries to download it (.application file), instead of launching it.

  3. Hi Eric,

    Version of Chrome is Version 72.0.3626.119 (Official Build) (64-bit)

    The URL of the application is internal. Its actually an app that is launched from a .NET application to handle document management.

    Were you asking for the URL meaning to see it for yourself?

    1. I asked about the URL for two reasons: 1) If public, I could look myself, and 2) Because most of these extensions work by looking for URLs that have the string “.application” in them in a particular place.

      Other things to check are that the extension is enabled (E.g. you’re not browsing Incognito without extensions). You could also check the links at the very top of my test page: https://bayden.com/test/clickonce/

  4. Well that is very nice of you. I am baffled, and all our developers state they have changed nothing. All I can assume is that something changed in Chrome. If you want to take a look:{elided for privacy}

    This is the app that has been working fine, and now it tries to download.

    1. I’ve taken a look at this, and I understand why it’s happening. If your developers want to workaround this in the web application, they can do so by editing their |window.downloadFile| function call in the JavaScript source code.

      The problem is that the common ClickOnce extensions for Chrome (e.g. the one above, and https://chrome.google.com/webstore/detail/clickonce-for-google-chro/kekahkplibinaibelipdcikofmedafmb) only install an onBeforeRequest listener that watches for URLs containing “.application”.

      The problem is that Chrome isn’t firing the onBeforeRequest event when the link to the .application contains a DOWNLOAD attribute (which is what informs Chrome that the target should go into the download manager).

      I’ve added a test case at the top of https://bayden.com/test/clickonce/ for this scenario.

      I will now attempt to figure out whether this is a change in Chrome.

      1. Eric, that is amazing thank you.

        Yes, there must have been something that changed in Chrome in the past week to have this start failing all of a sudden.

        Thanks for the view! I am going to share this now

      2. This problem turns out to be a bug in Chrome; it’s not specific to a version, but instead the “Network Service” experiment. You should see the problem go away if you change chrome://flags/#network-service setting to Disabled.

        I’ve reported this issue to Chrome: https://crbug.com/935567

  5. Hi Eric,
    Kon Tantos again, just want to give you an update.
    The click once application is ‘online only’. IE users can only start it via the web site. It also has a deployment provider via our code signing certificate.

    We tried a couple of other apps which allowed offline and online access. They all install without any issues.

    Using one of the clickonce extensions (Meta4, Remix) is problematic. They work in some PCs but not in others.

    1. The bugs in the Meta4/Remix addons should no longer reproduce in Chrome Canary as the regressions were corrected there.

      1. Thanks. Any idea when they will release the stable channel?

  6. Hi Eric,
    We are using ClickOnce for deploy some applications from a .net website of our company, without any problem until now.
    But now, with Chrome version 73, when users click on the link, the clickonce installation works fine, but the browser shows ERR_UNSAFE_REDIRECT error.

    Any ideas to solve this?
    Thanks!

    1. Do you have a repro URL? Does the problem reproduce in Chrome Canary? Are you using a ClickOnce browser extension?

  7. Hi Eric, great post, and really informative. If I need to deploy my Click Once application from multiple sites, would I have to republish for each site?
    For example, would I need a publish for our QA site, and another for the Production site?

    Thanks!

    1. I /assume/ that this is how that works, but to be honest my understanding of ClickOnce is limited to just what I’ve written above. Probably straightforward to test this?

    2. ClickOnce just sets up an online installer for your application. It has no bearing on the how that application behaves.

      Typically you have a single install site (URL) for each type of application.
      EG if QA v Production is specific to the application (IE not determined by login credentials) you would install the QA application to one URL (may be internal access only) and Production to another.

  8. If you don’t want to use the extension, and if you can easily copy/paste the clickonce URL to the download, you can make your own launcher.

    Save the following as LaunchClickOnce.vbs.

    Set objShell = WScript.CreateObject( “WScript.Shell” )
    u = InputBox(“ClickOnce Launcher”,”Enter the URL for the ClickOnce application”)
    objShell.Run(“””C:\Windows\System32\rundll32.exe”” “”C:\Windows\System32\dfshim.dll””,ShOpenVerbApplication ” & u)
    Set objShell = Nothing

    To launch a ClickOnce app, double-click the script and paste in the URL

    1. Could this be turned into something that could be ran using javascript from the browser? Our ClickOnce apps are old and I’m not even sure if some of them can be rebuilt (long story) to add the “From a Website” option.

      1. I don’t understand your question. Chrome doesn’t support ClickOnce. Edge does. If you want to use Chrome, you need an extension; JavaScript cannot invoke native code outside of the browser.

  9. I always specify the “From a web site / Specify the URL:” link, but it still doesn’t work in Chrome (and never has for me). Is there perhaps something more that must be done?

    1. I’m not entirely sure what you mean by “doesn’t work”? Chrome does not support ClickOnce invocation; if you download a .application file to your desktop, and double-click it, however, Chrome is no longer in the picture.

      1. After you click the file that is downloaded, you get the old “Cannot download the application. The application is missing required files. Contact application vendor for assistance.” error.

  10. We just had a report that our clients could not use Edge – I just tried in version 903.0.782.0 with edge://flags/#edge-click-once set Enabled and it just downloads the .application file.

    1. You’ll have to share the headers from the HTTP response to be sure, but the most likely explanation is that the response has either Content-Disposition: attachment, an incorrect MIME type, or some other factor that forces download. If you have a public URL, I’m happy to look myself.

      1. Hi Eric, I did some more digging as it was odd we hadn’t had more reports. It works fine on Windows 10, but not on Windows 7. I also checked versions main and dev channels and that makes no difference, and it still works on the Windows 7 with IE (Chrome and FF with Meta4 addon). An example url is https://demo.c3.co.uk/Fusion/launch.aspx

      2. Thanks for the report, Tim. I can confirm that Win7 support for ClickOnce has been broken since at least Edge 86 all the way up until Edge 90. I’ve pinged the feature owners to see whether they can get to fixing this sooner.

  11. Any chance of a policy or setting for Edge in the future to let user/domain trusted or internal sites open ClickOnce apps automatically? (Hopefully not even leaving a blank tab as they do now – and Ideally not even taking focus!) – I sometimes open dozens of links (from e-mail) that lead to a ClickOnce app in a day, and the current behavior is Much more frustrating than the IE experience was, even if in terms of seconds spent it’s not so significant.

    1. 1. I’ve filed a bug against the new ClickOnce code to close the new tab.
      2. There’s a bug to enable the AutoOpenFileTypes policy to apply to .application type, but I don’t know if it will be fixed.

  12. Hi,

    Do you have links to these 2 bugs ?
    We’re facing the same issue (automatic prompt in Edge for our clickonce apps) and we’d like to know if by chance there is any progress.

    1. Alas, no progress yet. If your company has a support contract with Microsoft, please consider escalating via that path.

  13. We came across an interesting case here where ClickOnce worked from IE but not from Edge. The .application file was placed on an Intranet server that required an authentication cookie. The authentication cookie was sent by the dfshim.dll downloader when spawned from IE but not Edge.

    Such a scenario relied upon the Shared WinINET cookie jar — an Auth Cookie set in IE is sent from the DFShim.dll outside of the browser. That doesn’t work in any of the following cases:

    1) The auth cookie wasn’t set in the WinINET Shared cookie jar because the user used Edge or Chrome to load the site and enter the password. The Auth cookie only went into the Chromium cookie jar.

    2) The auth cookie wasn’t set in the WinINET Shared cookie jar because the site was in the Internet Zone (which has an isolated Protected Mode cookie jar)

    3) The auth cookie was deleted before the app got around to using it (e.g. the user cleared their cookies when the browser closed, or at future point)

    Of these, #3 is interesting because ClickOnce hits the original server to check if the application is fresh each time it is started. But if the user ever subsequently clears their IE cookies, that’s startup check is going to start failing and the user’s application will not be kept up-to-date.

    In terms of workaround:

    1) Generally, you shouldn’t put your ClickOnce .application/files somewhere that requires Cookie auth
    2) If you can’t move these files, you could put this site in IEMode to get back to the old sorta-working behavior, but keep in mind limitations #2 and #3 above.

  14. I solved my issue with ClickOnce thanks to your post! Thank you so much..
    .application wasn’t associated with the correct app in Windows 11 for me so I couldn’t open those files. It turns out that the REG_SZ key under “HKEY_CLASSES_ROOT\Application.Manifest\shell\open\command” path pointed incorrectly to “X:\Windows\System32\rundll32.exe” “X:\Windows\System32\dfshim.dll”,ShOpenVerbApplication %1 but when I compared with a computer where the association worked it should’ve been “C:\Windows\System32\rundll32.exe” “C:\Windows\System32\dfshim.dll”,ShOpenVerbApplication %1

    Once I replaced the X:\-path to the C:\ equivalent it all started working.

Leave a comment