64

I'm developing an application targeting .NET Framework 2.0 using C# for which I need to be able to find the default application that is used for opening a particular file type.

I know that, for example, if you just want to open a file using that application you can use something like:

System.Diagnostics.Process.Start( "C:\...\...\myfile.html" );

to open an HTML document in the default browser, or

System.Diagnostics.Process.Start( "C:\...\...\myfile.txt" );

to open a text file in the default text editor.

However, what I want to be able to do is to open files that don't necessarily have a .txt extension (for example), in the default text editor, so I need to be able to find out the default application for opening .txt files, which will allow me to invoke it directly.

I'm guessing there's some Win32 API that I'll need to P/Invoke in order to do this, however a quick look with both Google and MSDN didn't reveal anything of much interest; I did find a very large number of completely irrelevant pages, but nothing like I'm looking for.

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
Bart Read
  • 2,717
  • 3
  • 22
  • 32

6 Answers6

81

All current answers are unreliable. The registry is an implementation detail and indeed such code is broken on my Windows 8.1 machine. The proper way to do this is using the Win32 API, specifically AssocQueryString:

using System.Runtime.InteropServices;

[DllImport("Shlwapi.dll", CharSet = CharSet.Unicode)]
public static extern uint AssocQueryString(
    AssocF flags, 
    AssocStr str,  
    string pszAssoc, 
    string pszExtra, 
    [Out] StringBuilder pszOut, 
    ref uint pcchOut
); 

[Flags]
public enum AssocF
{
    None = 0,
    Init_NoRemapCLSID = 0x1,
    Init_ByExeName = 0x2,
    Open_ByExeName = 0x2,
    Init_DefaultToStar = 0x4,
    Init_DefaultToFolder = 0x8,
    NoUserSettings = 0x10,
    NoTruncate = 0x20,
    Verify = 0x40,
    RemapRunDll = 0x80,
    NoFixUps = 0x100,
    IgnoreBaseClass = 0x200,
    Init_IgnoreUnknown = 0x400,
    Init_Fixed_ProgId = 0x800,
    Is_Protocol = 0x1000,
    Init_For_File = 0x2000
}

public enum AssocStr
{
    Command = 1,
    Executable,
    FriendlyDocName,
    FriendlyAppName,
    NoOpen,
    ShellNewValue,
    DDECommand,
    DDEIfExec,
    DDEApplication,
    DDETopic,
    InfoTip,
    QuickTip,
    TileInfo,
    ContentType,
    DefaultIcon,
    ShellExtension,
    DropTarget,
    DelegateExecute,
    Supported_Uri_Protocols,
    ProgID,
    AppID,
    AppPublisher,
    AppIconReference,
    Max
}

Relevant documentation:

Sample usage:

static string AssocQueryString(AssocStr association, string extension)
{
    const int S_OK = 0;
    const int S_FALSE = 1;

    uint length = 0;
    uint ret = AssocQueryString(AssocF.None, association, extension, null, null, ref length);
    if (ret != S_FALSE)
    {
        throw new InvalidOperationException("Could not determine associated string");
    }

    var sb = new StringBuilder((int)length); // (length-1) will probably work too as the marshaller adds null termination
    ret = AssocQueryString(AssocF.None, association, extension, null, sb, ref length);
    if (ret != S_OK)
    {
        throw new InvalidOperationException("Could not determine associated string"); 
    }

    return sb.ToString();
}
Ohad Schneider
  • 36,600
  • 15
  • 168
  • 198
  • 4
    This was down-voted, but worked well for me with just a minor fix (edited above). You'll need "using System.Runtime.InteropServices". I couldn't find the enums, so copied them from MSDN: [ASSOCF](https://msdn.microsoft.com/en-us/library/windows/desktop/bb762471(v=vs.85).aspx) and [ASOCSTR](https://msdn.microsoft.com/en-us/library/windows/desktop/bb762475(v=vs.85).aspx). To always access the text editor, I called this method with AssocStr.ASSOCSTR_EXECUTABLE and ".txt". – Giles Feb 24 '15 at 10:45
  • 4
    I really like and appreciate this answer, but I also had trouble finding references for AssocF and AssocStr. I eventually found an implementation of these flags on another stack overflow answer: http://stackoverflow.com/questions/770023/how-do-i-get-file-type-information-based-on-extention-not-mime-in-c-sharp Upvoted, thank you! – turkinator Sep 20 '16 at 21:43
  • @epicTurkey the first comment by Giles has this covered: http://stackoverflow.com/questions/162331/finding-the-default-application-for-opening-a-particular-file-type-on-windows/17773402?noredirect=1#comment45678230_17773402 – Ohad Schneider Oct 01 '16 at 22:36
  • 3
    AssocStr.Executable doesn't work for image extensions like .png. How could you resolve their container applications? – IngoB Mar 14 '18 at 16:19
  • @IngoB Using the code above, this works for me (Win10 x64 ver.1709): `Console.WriteLine(AssocQueryString(AssocStr.Executable, ".png"))`... – Ohad Schneider Mar 14 '18 at 20:03
  • 2
    I came across this SO question today and was running into the same thing @IngoB was with the .png extension. The difference seems to stem from whether the extension is being handled by a "full" EXE or a UWP (that's still what they are called, right :) app... for example, in my case, .png was being handled by the Photos app, so while `AssocStr.Executable` would fail, requesting `AssocStr.AppId` instead returns `Microsoft.Windows.Photos_8wekyb3d8bbwe!App`. This can then be run using code similar to this answer: https://stackoverflow.com/a/41906116 – NFrank Jun 27 '19 at 20:06
  • ASSOCF link does not work anymore, this page has reference to it https://learn.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-assocquerystringa – Yehor Androsov Oct 02 '19 at 18:41
  • 1
    @pwrigshihanomoronimo thanks, fixed to WayBack Machine link and submitted feedback to MS regarding the bad link (when clicking `ASSOCF` in the doc you mentioned, I was led to a different enum). – Ohad Schneider Oct 17 '19 at 12:34
17

You can check under registry section HKEY_CLASSES_ROOT for the extension and action details. Documentation for this is on MSDN. Alternatively, you can use the IQueryAssociations interface.

Roger Lipscombe
  • 89,048
  • 55
  • 235
  • 380
curtisk
  • 19,950
  • 4
  • 55
  • 71
8

Doh! Of course.

HKEY_CLASSES_ROOT\.txt

includes a reference to

HKEY_CLASSES_ROOT\txtfile

which contains a subkey

HKEY_CLASSES_ROOT\txtfile\shell\open\command

which references Notepad.

Sorted, many thanks!

Bart

Bart Read
  • 2,717
  • 3
  • 22
  • 32
  • 1
    Which is `%SystemRoot%\system32\NOTEPAD.EXE %1`. This is not the default program in my case. Also, You cannot use `Process.Start` with this, because of the `%SystemRoot%` literal and the `%1`. All of which requires special treatment in your code that can't be covered completely&definitely, because who knows what else can be expected in such string. – bytecode77 Dec 24 '16 at 10:21
5

Here is a blog post with about this topic. The code samples are in VB.net, but it should be easy to port them to C#.

xsl
  • 17,116
  • 18
  • 71
  • 112
3

You can just query the registry. First get the Default entry under HKEY_CLASSES_ROOT\.ext

That will give you the classname. For example .txt has a default of txtfile

Then open up HKEY_CLASSES_ROOT\txtfile\Shell\Open\Command

That will give you the default command used.

Tom
  • 1,611
  • 10
  • 11
3

A late answer, but there is a good NUGET package that handles file associations: File Association

Link NUGET File Association

Usage is simple, for instance to add all allowed file extensions to a context menu:

private void OnMenuSourceFileOpening(object sender, ...)
{   // open a context menu with the associated files + ".txt" files
    if (File.Exists(this.SelectedFileName))
    {
        string fileExt = Path.GetExtension(this.SelectedFileNames);
        string[] allowedExtensions = new string[] { fileExt, ".txt" };
        var fileAssociations = allowedExtensions
            .Select(ext => new FileAssociationInfo(ext));
        var progInfos = fileAssociations
            .Select(fileAssoc => new ProgramAssociationInfo (fileAssoc.ProgID));
        var toolstripItems = myProgInfos
            .Select(proginfo => new ToolStripLabel (proginfo.Description) { Tag = proginfo });
        // add also the prog info as Tag, for easy access
        //  when the toolstrip item is selected
        // of course this can also be done in one long linq statement

        // fill the context menu:
        this.contextMenu1.Items.Clear();
        this.contextMenuOpenSourceFile.Items.AddRange (toolstripItems.ToArray());
    }
}
Harald Coppoolse
  • 28,834
  • 7
  • 67
  • 116