65

I've written a program that edits a specific filetype , and I want to give the user the option to set my application as the default editor for this filetype (since I don't want an installer) on startup.

I've tried to write a re-useable method that associates a file for me (preferably on any OS, although I'm running Vista) by adding a key to HKEY_CLASSES_ROOT, and am using it with my application, but it doesn't seem to work.

public static void SetAssociation(string Extension, string KeyName, string OpenWith, string FileDescription)
{
    RegistryKey BaseKey;
    RegistryKey OpenMethod;
    RegistryKey Shell;
    RegistryKey CurrentUser;

    BaseKey = Registry.ClassesRoot.CreateSubKey(Extension);
    BaseKey.SetValue("", KeyName);

    OpenMethod = Registry.ClassesRoot.CreateSubKey(KeyName);
    OpenMethod.SetValue("", FileDescription);
    OpenMethod.CreateSubKey("DefaultIcon").SetValue("", "\"" + OpenWith + "\",0");
    Shell = OpenMethod.CreateSubKey("Shell");
    Shell.CreateSubKey("edit").CreateSubKey("command").SetValue("", "\"" + OpenWith + "\"" + " \"%1\"");
    Shell.CreateSubKey("open").CreateSubKey("command").SetValue("", "\"" + OpenWith + "\"" + " \"%1\"");
    BaseKey.Close();
    OpenMethod.Close();
    Shell.Close();

    CurrentUser = Registry.CurrentUser.CreateSubKey(@"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\" + Extension);
    CurrentUser = CurrentUser.OpenSubKey("UserChoice", RegistryKeyPermissionCheck.ReadWriteSubTree, System.Security.AccessControl.RegistryRights.FullControl);
    CurrentUser.SetValue("Progid", KeyName, RegistryValueKind.String);
    CurrentUser.Close();
}

Any idea why it doesn't work? An example use might be

SetAssociation(".ucs", "UCS_Editor_File", Application.ExecutablePath, "UCS File"); 

The part of the method that uses "CurrentUser" seems to work if I do the same using regedit, but using my application it doesn't.

5
  • have you tried running your program as admin? Commented Apr 21, 2010 at 10:05
  • UAC means that your app doesn't run as admin unless you explicitly require it. You run Vista, Vista includes UAC. Can you double check if the program runs as administrator? Commented Apr 21, 2010 at 10:12
  • I've tried "Run as Administrator" plus UAC has been turned off anyway but the file still isn't associated after the program runs. Commented Apr 21, 2010 at 10:15
  • I think the 3rd to last line in your method may be incorrect. I don't think you want to set "CurrentUser" to be the subkey. Commented May 25, 2011 at 12:33
  • Related question Filetype association with application (C#) Commented May 17, 2013 at 10:43

9 Answers 9

38

Here's a complete example:

public class FileAssociation
{
    public string Extension { get; set; }
    public string ProgId { get; set; }
    public string FileTypeDescription { get; set; }
    public string ExecutableFilePath { get; set; }
}

public class FileAssociations
{
    // needed so that Explorer windows get refreshed after the registry is updated
    [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 void EnsureAssociationsSet()
    {
        var filePath = Process.GetCurrentProcess().MainModule.FileName;
        EnsureAssociationsSet(
            new FileAssociation
            {
                Extension = ".ucs",
                ProgId = "UCS_Editor_File",
                FileTypeDescription = "UCS File",
                ExecutableFilePath = filePath
            });
    }

    public static void EnsureAssociationsSet(params FileAssociation[] associations)
    {
        bool madeChanges = false;
        foreach (var association in associations)
        {
            madeChanges |= SetAssociation(
                association.Extension,
                association.ProgId,
                association.FileTypeDescription,
                association.ExecutableFilePath);
        }

        if (madeChanges)
        {
            SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_FLUSH, IntPtr.Zero, IntPtr.Zero);
        }
    }

    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;
    }
Sign up to request clarification or add additional context in comments.

Comments

38

The answer was a lot simpler than I expected. Windows Explorer has its own override for the open with application, and I was trying to modify it in the last lines of code. If you just delete the Explorer override, then the file association will work.

I also told explorer that I had changed a file association by calling the unmanaged function SHChangeNotify() using P/Invoke

public static void SetAssociation(string Extension, string KeyName, string OpenWith, string FileDescription)
{
    // The stuff that was above here is basically the same

    // Delete the key instead of trying to change it
    var CurrentUser = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\" + Extension, true);
    CurrentUser.DeleteSubKey("UserChoice", false);
    CurrentUser.Close();

    // Tell explorer the file association has been changed
    SHChangeNotify(0x08000000, 0x0000, IntPtr.Zero, IntPtr.Zero);
}

[DllImport("shell32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern void SHChangeNotify(uint wEventId, uint uFlags, IntPtr dwItem1, IntPtr dwItem2);

3 Comments

I know this is old and you may have caught this already, but I noticed in this code segment and in your first post that the first line of CurrentUser = you have the .ucs extension hard coded into your OpenSubKey() call.
Worked very good for me ! but pay attention - requires administrative rights
Deleting subkey for known extensions like .png is working but as soon as I notify explorer, it restores back the UserChoice. How can I stop explorer restoring the UserChoice?
18

You can do that in a managed way via ClickOnce. No fussing with the registry yourself. This is available via tooling (i.e. no xml) in VS2008 and above (including Express) on Project Properties => Publish => Options => File Associations

2 Comments

Nice answer, but unfortunately I'm using VS2005 so will I have to wait until I get VS2010?
That's nice and all, but, how do you use this method and set custom command-line arguments? This method forces you to use app.exe "%1", but what if I wanted it to do app.exe /config "%1"?
11

Solution above did not work for me with Windows 10. Here is my solution to open files with the .myExt extension with %localappdata%\MyApp\MyApp.exe for current user. Optimised after reading comments.

 String App_Exe = "MyApp.exe";
 String App_Path = "%localappdata%";
 SetAssociation_User("myExt", App_Path + App_Exe, App_Exe);

 public static void SetAssociation_User(string Extension, string OpenWith, string ExecutableName)
 {
    try {
                using (RegistryKey User_Classes = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Classes\\", true))
                using (RegistryKey User_Ext = User_Classes.CreateSubKey("." + Extension))
                using (RegistryKey User_AutoFile = User_Classes.CreateSubKey(Extension + "_auto_file"))
                using (RegistryKey User_AutoFile_Command = User_AutoFile.CreateSubKey("shell").CreateSubKey("open").CreateSubKey("command"))
                using (RegistryKey ApplicationAssociationToasts = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\Windows\\CurrentVersion\\ApplicationAssociationToasts\\", true))
                using (RegistryKey User_Classes_Applications = User_Classes.CreateSubKey("Applications"))
                using (RegistryKey User_Classes_Applications_Exe = User_Classes_Applications.CreateSubKey(ExecutableName))
                using (RegistryKey User_Application_Command = User_Classes_Applications_Exe.CreateSubKey("shell").CreateSubKey("open").CreateSubKey("command"))
                using (RegistryKey User_Explorer = Registry.CurrentUser.CreateSubKey("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\." + Extension))
                using (RegistryKey User_Choice = User_Explorer.OpenSubKey("UserChoice"))
                {
                    User_Ext.SetValue("", Extension + "_auto_file", RegistryValueKind.String);
                    User_Classes.SetValue("", Extension + "_auto_file", RegistryValueKind.String);
                    User_Classes.CreateSubKey(Extension + "_auto_file");
                    User_AutoFile_Command.SetValue("", "\"" + OpenWith + "\"" + " \"%1\"");
                    ApplicationAssociationToasts.SetValue(Extension + "_auto_file_." + Extension, 0);
                    ApplicationAssociationToasts.SetValue(@"Applications\" + ExecutableName + "_." + Extension, 0);
                    User_Application_Command.SetValue("", "\"" + OpenWith + "\"" + " \"%1\"");
                    User_Explorer.CreateSubKey("OpenWithList").SetValue("a", ExecutableName);
                    User_Explorer.CreateSubKey("OpenWithProgids").SetValue(Extension + "_auto_file", "0");
                    if (User_Choice != null) User_Explorer.DeleteSubKey("UserChoice");
                    User_Explorer.CreateSubKey("UserChoice").SetValue("ProgId", @"Applications\" + ExecutableName);
                }
                SHChangeNotify(0x08000000, 0x0000, IntPtr.Zero, IntPtr.Zero);
            }
            catch (Exception excpt)
            {
                //Your code here
            }
        }

  [DllImport("shell32.dll", CharSet = CharSet.Auto, SetLastError = true)]
  public static extern void SHChangeNotify(uint wEventId, uint uFlags, IntPtr dwItem1, IntPtr dwItem2);

5 Comments

From this answer: "The RegistryKey class implements IDisposable and so you should wrap your keys in a using statement." Or, alternatively, you should call Close or Dispose on the RegistryKey when you are done with it. This means that chaining calls to CreateSubKey as shown in this example is a bad idea.
Agree with you it's cleaner but it is still interesting to understand what is the worst side effect I can expect from the code above. Ideas ?
If you do not dispose an unmanaged resource (e.g., RegistryKey), your application will suffer from memory leaks. See Relation between resource leaks and memory leaks and performance and IDisposable Interface. Note that the code examples in both the question and the accepted answer include calls to RegistryKey.Close as required.
yikes. spending 30 seconds looking at this code, it's all wrong. The User_Extension key creates a .ext key in CURRENT_USER. It should be CLASSES_ROOT. I stopped looking after that. If that's wrong, the rest is probably wrong too.
@Sonic, no, it is better, because CURRENT_USER\Software\Classes is the current user equivalent of CLASSES_ROOT and it doesn't require admin privileges. The code is wrong for other reasons.
9

If you write the keys into HKEY_CURRENT_USER\Software\Classes instead of HKEY_CLASSES_ROOT, this should work without administrator privileges under Vista and later.

Comments

4

You are using an old version of Visual Studio, Vista is going to treat your program as a "legacy" Windows app. And redirect the registry writes you make. Include a manifest in your program so you'll look Vista-aware. This manifest is automatically included by VS2008 and up.

Beware that this still won't solve the problem for your user, she's very unlikely to run your app with UAC turned off. You'll need to write a separate app that has a manifest as linked and asks for administrator privileges. It needs the manifest with requestedExecutionLevel set to requireAdministrator.

2 Comments

I'm unable to add a manifest to the project because the actual exe isn't in my project. Any way to make it appear so I can add a resource? (going add -> existing item and selecting the .exe in the obj folder just copies it)
You are likely to break things if this really is a legacy app. But you can inject a manifest with the mt.exe SDK tool.
4

If you're using Visual Studio 2015 then install the setup and deployment extension. Create a Setup Wizard, and then attach your .exe file to it. Right click your main program in the solution explorer go to -view, -file types, and then right click on the file types and select add new file type. Change all the properties to your needs and then build the MSI installer.

NOTE: I re-read your question and realized that you did not want an installer. Sorry about that, although you should consider using one because it gives you a lot more customization over your program(s).

Comments

3

the actual way to associate your file extension with your own programm:

using Microsoft.Win32;
using System;
using System.IO;
using System.Runtime.InteropServices;
 
private static void RegisterForFileExtension(string extension, string applicationPath)
    {
        RegistryKey FileReg = Registry.CurrentUser.CreateSubKey("Software\\Classes\\" + extension);
        FileReg.CreateSubKey("shell\\open\\command").SetValue("", $"\"{applicationPath}\" \"%1\"");
        FileReg.Close();

        SHChangeNotify(0x08000000, 0x0000, IntPtr.Zero, IntPtr.Zero);
    }
[DllImport("shell32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern void SHChangeNotify(uint wEventId, uint uFlags, IntPtr dwItem1, IntPtr dwItem2);

EDIT: Thx, i changed the solution with your suggestion.

1 Comment

Actually, the only answer that really helped me. Be sure to escape your application-path and the parameter, however. Otherwise you'll run into issues when you're dealing with filenames that contain a space: I'd set the value like this instead: ...SetValue("", $"\"{applicationPath}\" \"%1\"")
1

the line

FileReg.CreateSubKey("shell\open\command").SetValue("", applicationPath + " %1");

should be modified to

FileReg.CreateSubKey("shell\open\command").SetValue("", $"\"{applicationPath}\" \"%1\"");

if you don't want have problem with spaces in the path like:

C:\my folder\my file.txt

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.