The only way to get this to work after a ClickOnce deployment is to add/update several registry entries in code. See below.
It's been 8 years, and even in the latest version of Visual Studio 2019 with Windows 10, the file association settings in ClickOnce deployments still don't work. I suspect this is a Windows version issue, as Microsoft seems to change how file associations are stored in the registry with every new version of Windows. But regardless, Visual Studio and ClickOnce should work for the current Windows release. And it does not.
After completing a clean install of my ClickOnce app, I found this one registry entry for my file extension:
Computer\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.xxxxxx
And it added this registry entry for the ProgID:
Computer\HKEY_CLASSES_ROOT\xxxxxxxxxxx
But it did not add a reference from the file extension to the ProgID.
These two entries do nothing in Windows 10. It does not show the icon, does not open the ClickOnce app when you open the file with that extension, and does not show any ClickOnce option in the Open With lists (as a commenter suggested). Note that the registry entry above was created by the deployment but contains no reference to the ClickOnce application or the ProgID that was set up in the ClickOnce properties. In fact, the only application referenced in that file extension registry entry is to "OpenWith.exe".
Solution
After spending 2 days researching this issue and piecing together various ideas, the solution below is the only one I found that works. Simply execute SetAssociation() every time your app starts (or at least immediately after an install/upgrade), and it will solve the file association problem in all versions of Windows.
using Microsoft.Win32;
using System;
using System.Collections.Generic;
namespace MyLibrary
{
public class FileAssociations
{
[System.Runtime.InteropServices.DllImport("Shell32.dll")]
private static extern int SHChangeNotify(int eventId, int flags, IntPtr item1, IntPtr item2);
private const int SHCNE_ASSOCCHANGED = 0x8000000;
private const int SHCNF_FLUSH = 0x1000;
public static bool SetAssociation(string extension, string progId, string fileTypeDescription, string applicationFilePath)
{
bool madeChanges = false;
madeChanges |= SetKeyDefaultValue(@"Software\Classes\" + extension, progId);
madeChanges |= SetKeyDefaultValue(@"Software\Classes\" + progId, fileTypeDescription);
madeChanges |= SetKeyDefaultValue($@"Software\Classes\{progId}\shell\open\command", "\"" + applicationFilePath + "\" \"%1\"");
return madeChanges;
}
private static bool SetKeyDefaultValue(string keyPath, string value)
{
using (var key = Registry.CurrentUser.CreateSubKey(keyPath))
{
if (key.GetValue(null) as string != value)
{
key.SetValue(null, value);
return true;
}
}
return false;
}
}
}
Then execute this on program start. The first item in Environment.GetCommandLineArgs() contains the full path to your exe:
string[] args = Environment.GetCommandLineArgs();
FileAssociations.SetAssociation(FileExtension, ProgID, FileExtensionDescription, args[0]);
The only problem I found with this approach is if your file association executes your .exe directly, the ClickOnce automatic updates will not work because the update info is stored in the Start Menu shortcut, not with the installed application files.
That is a different problem than the question you asked, but to get around that, if the app starts up with the .exe directly, I programmatically locate the Start Menu shortcut to the ClickOnce app (.appref-ms extension), then restart the app using that path (instead of the .exe), pass it the original command-line arguments that were sent to your .exe, then when it restarts, I retrieve THOSE arguments using AppDomain.CurrentDomain.SetupInformation.ActivationArguments.ActivationData.