DOBON.NET プログラミング道: .NET Framework, VB.NET, C#, Visual Basic, Visual Studio, インストーラ, ...

拡張子に関連付けられた実行ファイルのパスを取得する

ここでは、拡張子に関連付けられた実行ファイルのパスを取得する方法を幾つか紹介します。

Win32 APIのFindExecutableを使用する方法

まず、Win32 APIのFindExecutable functionを使う方法が考えられます。この場合、例えば次のようにして実行ファイルのパスを取得することができます。

VB.NET
コードを隠すコードを選択
''' <summary>
''' 指定されたファイルに関連付けられた実行ファイルのパスを取得する。
''' </summary>
''' <param name="fileName">ファイルのパス。
''' ファイルは存在していなければならない。</param>
''' <returns>見つかった時は、実行ファイルのパス。
''' 見つからなかった時は、空の文字列。</returns>
''' <example>
''' "C:\test\1.txt"に関連付けられた実行ファイルのパスを取得する例
''' <code>
''' string exePath = FindExecutableFile(@"C:\test\1.txt");
''' </code>
''' </example>
Public Shared Function FindExecutableFile(fileName As String) As String
    '結果を受け取るためのStringBuilderオブジェクト
    Dim exePath As New System.Text.StringBuilder(255)
    'fileNameに関連付けられた実行ファイルのパスを取得する
    If FindExecutable(fileName, Nothing, exePath) <= 32 Then
        '失敗した時
        Return String.Empty
    End If
    '成功した時
    Return exePath.ToString()
End Function

<System.Runtime.InteropServices.DllImport("shell32.dll")> _
Private Shared Function FindExecutable(lpFile As String, _
    lpDirectory As String, lpResult As System.Text.StringBuilder) As Integer
End Function
C#
コードを隠すコードを選択
/// <summary>
/// 指定されたファイルに関連付けられた実行ファイルのパスを取得する。
/// </summary>
/// <param name="fileName">ファイルのパス。
/// ファイルは存在していなければならない。</param>
/// <returns>見つかった時は、実行ファイルのパス。
/// 見つからなかった時は、空の文字列。</returns>
/// <example>
/// "C:\test\1.txt"に関連付けられた実行ファイルのパスを取得する例
/// <code>
/// string exePath = FindExecutableFile(@"C:\test\1.txt");
/// </code>
/// </example>
public static string FindExecutableFile(string fileName)
{
    //結果を受け取るためのStringBuilderオブジェクト
    System.Text.StringBuilder exePath =
        new System.Text.StringBuilder(255);
    //fileNameに関連付けられた実行ファイルのパスを取得する
    if (FindExecutable(fileName, null, exePath) <= 32)
    {
        //失敗した時
        return string.Empty;
    }
    //成功した時
    return exePath.ToString();
}

[System.Runtime.InteropServices.DllImport("shell32.dll")]
private static extern int FindExecutable(
    string lpFile, string lpDirectory,
    System.Text.StringBuilder lpResult);

しかし残念ながら、FindExecutableには関連付けられた実行ファイルのパスにスペース文字が含まれる時、はじめのスペースがnull文字で置き換えられてしまうというバグ(仕様)があります。このために、例えば実行ファイルのパスが、

C:\Program Files\Microsoft Office\WINWORD.EXE

であった場合、FindExecutableは、

C:\Program

という文字列を返したかのようになってしまう可能性があります。詳しくは、「マイクロソフト サポート技術情報 - 140724 PRB: FindExecutable() Truncates Result at First Space in LFN」にありますので、そちらをご覧ください。

また、「Win32のFindExecutableでイタイ目にあった」によると、FindExecutableはユーザーごとの設定しか見ておらず、たとえすべてのユーザーに共通の設定があったとしても、ユーザーごとの設定が存在しなければ、その設定は存在しないと判断してしまうそうです。

Win32 APIのAssocQueryStringを使用する方法

拡張子に関連付けられている実行可能ファイルパスを取得するためのAPIには、AssocQueryString Functionもあります。これを使用するには、Internet Explorer 5以上が必要となります。

AssocQueryStringを使用して、拡張子に関連付けられている実行ファイルのパスを取得する例を以下に示します。この例は、「pinvoke.net: AssocQueryString (shlwapi)」や「Karl Peterson's Classic VB Solution for Finding Executable Associations -- Visual Studio Magazine」を参考にして作成しました。

VB.NET
コードを隠すコードを選択
'Imports System.Runtime.InteropServices
'Imports System.Text

''' <summary>
''' 指定された拡張子に関連付けられた実行ファイルのパスを取得する。
''' </summary>
''' <param name="extName">".txt"などの拡張子。</param>
''' <returns>見つかった時は、実行ファイルのパス。
''' 見つからなかった時は、空の文字列。</returns>
''' <example>
''' 拡張子".txt"に関連付けられた実行ファイルのパスを取得する例
''' <code>
''' string exePath = FindAssociatedExecutable(".txt");
''' </code>
''' </example>
Public Shared Function FindAssociatedExecutable(extName As String) As String
    'pszOutのサイズを取得する
    Dim pcchOut As UInteger = 0
    'ASSOCF_INIT_IGNOREUNKNOWNで関連付けられていないものを無視
    'ASSOCF_VERIFYを付けると検証を行うが、パフォーマンスは落ちる
    AssocQueryString(AssocF.Init_IgnoreUnknown, AssocStr.Executable, _
                     extName, Nothing, Nothing, pcchOut)
    If pcchOut = 0 Then
        Return String.Empty
    End If
    '結果を受け取るためのStringBuilderオブジェクトを作成する
    Dim pszOut As New StringBuilder(CInt(pcchOut))
    '関連付けられた実行ファイルのパスを取得する
    AssocQueryString(AssocF.Init_IgnoreUnknown, AssocStr.Executable, _
                     extName, Nothing, pszOut, pcchOut)
    '結果を返す
    Return pszOut.ToString()
End Function

<DllImport("Shlwapi.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function AssocQueryString( _
    flags As AssocF, _
    str As AssocStr, _
    pszAssoc As String, _
    pszExtra As String, _
    <Out> pszOut As StringBuilder, _
    <[In]> <Out> ByRef pcchOut As UInteger) As UInteger
End Function

<Flags> _
Private Enum AssocF
    None = 0
    Init_NoRemapCLSID = &H1
    Init_ByExeName = &H2
    Open_ByExeName = &H2
    Init_DefaultToStar = &H4
    Init_DefaultToFolder = &H8
    NoUserSettings = &H10
    NoTruncate = &H20
    Verify = &H40
    RemapRunDll = &H80
    NoFixUps = &H100
    IgnoreBaseClass = &H200
    Init_IgnoreUnknown = &H400
    Init_FixedProgId = &H800
    IsProtocol = &H1000
    InitForFile = &H2000
End Enum

Private Enum AssocStr
    Command = 1
    Executable
    FriendlyDocName
    FriendlyAppName
    NoOpen
    ShellNewValue
    DDECommand
    DDEIfExec
    DDEApplication
    DDETopic
    InfoTip
    QuickTip
    TileInfo
    ContentType
    DefaultIcon
    ShellExtension
    DropTarget
    DelegateExecute
    SupportedUriProtocols
    Max
End Enum
C#
コードを隠すコードを選択
//using System;
//using System.Runtime.InteropServices;
//using System.Text;

/// <summary>
/// 指定された拡張子に関連付けられた実行ファイルのパスを取得する。
/// </summary>
/// <param name="extName">".txt"などの拡張子。</param>
/// <returns>見つかった時は、実行ファイルのパス。
/// 見つからなかった時は、空の文字列。</returns>
/// <example>
/// 拡張子".txt"に関連付けられた実行ファイルのパスを取得する例
/// <code>
/// string exePath = FindAssociatedExecutable(".txt");
/// </code>
/// </example>
public static string FindAssociatedExecutable(string extName)
{
    //pszOutのサイズを取得する
    uint pcchOut = 0;
    //ASSOCF_INIT_IGNOREUNKNOWNで関連付けられていないものを無視
    //ASSOCF_VERIFYを付けると検証を行うが、パフォーマンスは落ちる
    AssocQueryString(AssocF.Init_IgnoreUnknown, AssocStr.Executable,
        extName, null, null, ref pcchOut);
    if (pcchOut == 0)
    {
        return string.Empty;
    }
    //結果を受け取るためのStringBuilderオブジェクトを作成する
    StringBuilder pszOut = new StringBuilder((int)pcchOut);
    //関連付けられた実行ファイルのパスを取得する
    AssocQueryString(AssocF.Init_IgnoreUnknown, AssocStr.Executable,
        extName, null, pszOut, ref pcchOut);
    //結果を返す
    return pszOut.ToString();
}

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

[Flags]
private 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_FixedProgId = 0x800,
    IsProtocol = 0x1000,
    InitForFile = 0x2000,
}

private enum AssocStr
{
    Command = 1,
    Executable,
    FriendlyDocName,
    FriendlyAppName,
    NoOpen,
    ShellNewValue,
    DDECommand,
    DDEIfExec,
    DDEApplication,
    DDETopic,
    InfoTip,
    QuickTip,
    TileInfo,
    ContentType,
    DefaultIcon,
    ShellExtension,
    DropTarget,
    DelegateExecute,
    SupportedUriProtocols,
    Max,
}
補足:上記の例ではASSOCF_INIT_IGNOREUNKNOWNを使っていますが、これを使わずに関連付けされていない拡張子を調べた場合、HKEY_CLASSES_ROOT\Unknownの設定(OpenWith.exeなど)が返されます。

レジストリを調べる方法

上記のようなWin32 APIを使用しなくても、拡張子の関連付けに関する情報はレジストリに保存されていますので、レジストリを調べれば拡張子に関連付けられた実行ファイルのパスは分かります。

拡張子の関連付けに関する情報は、レジストリのHKEY_CLASSES_ROOTにあります。一般的には、"HKEY_CLASSES_ROOT\(拡張子)"というキーにその拡張子に関連付けられているファイルタイプ名が格納されており、"HKEY_CLASSES_ROOT\(ファイルタイプ名)\shell\(openなどのアクション名)\command"というキーにコマンド(「アクションを実行するアプリケーション」に指定されている、実行ファイルのパスとコマンドライン引数)が格納されています。

ただし、「SystemFileAssociations」のように複雑なルールもありますので、すべてを考慮して調べるのは難しいかもしれません。

補足:HKEY_CLASSES_ROOTは、HKEY_LOCAL_MACHINE\Software\Classes(すべてのユーザー共通の設定)とHKEY_CURRENT_USER\Software\Classes(ユーザーごとの設定)が合成されたものです。両方に存在する場合は、HKEY_CURRENT_USERが優先されます。

以下に、レジストリを調べて拡張子に関連付けられたコマンドを取得するコードの例を示します。この例ではSystemFileAssociationsなどの複雑なルールは無視しています。

VB.NET
コードを隠すコードを選択
''' <summary>
''' 指定されたファイルに関連付けられたコマンドを取得する
''' </summary>
''' <param name="fileName">関連付けを調べるファイル</param>
''' <param name="extra">アクション(open,print,editなど)</param>
''' <returns>取得できた時は、コマンド(実行ファイルのパス+コマンドライン引数)。
''' 取得できなかった時は、空の文字列。</returns>
''' <example>
''' "1.txt"ファイルの"open"に関連付けられたコマンドを取得する例
''' <code>
''' string command = FindAssociatedCommand("1.txt", "open");
''' </code>
''' </example>
Public Shared Function FindAssociatedCommand(fileName As String, _
        extra As String) As String
    '拡張子を取得
    Dim extName As String = System.IO.Path.GetExtension(fileName)
    If extName.Length = 0 OrElse extName(0) <> "."c Then
        Return String.Empty
    End If

    'HKEY_CLASSES_ROOT\(extName)\shell があれば、
    'HKEY_CLASSES_ROOT\(extName)\shell\(extra)\command の標準値を返す
    If ExistClassesRootKey(extName & "\shell") Then
        Return GetShellCommandFromClassesRoot(extName, extra)
    End If

    'HKEY_CLASSES_ROOT\(extName) の標準値を取得する
    Dim fileType As String = GetDefaultValueFromClassesRoot(extName)
    If fileType.Length = 0 Then
        Return String.Empty
    End If

    'HKEY_CLASSES_ROOT\(fileType)\shell\(extra)\command の標準値を返す
    Return GetShellCommandFromClassesRoot(fileType, extra)
End Function

Public Shared Function FindAssociatedCommand(fileName As String) As String
    Return FindAssociatedCommand(fileName, "open")
End Function

Private Shared Function ExistClassesRootKey(keyName As String) As Boolean
    Dim regKey As Microsoft.Win32.RegistryKey = _
        Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(keyName)
    If regKey Is Nothing Then
        Return False
    End If
    regKey.Close()
    Return True
End Function

Private Shared Function GetDefaultValueFromClassesRoot( _
        keyName As String) As String
    Dim regKey As Microsoft.Win32.RegistryKey = _
        Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(keyName)
    If regKey Is Nothing Then
        Return String.Empty
    End If
    Dim val As String = _
        DirectCast(regKey.GetValue(String.Empty, String.Empty), String)
    regKey.Close()

    Return val
End Function

Private Shared Function GetShellCommandFromClassesRoot( _
        fileType As String, extra As String) As String
    If extra.Length = 0 Then
        'アクションが指定されていない時は、既定のアクションを取得する
        extra = GetDefaultValueFromClassesRoot(fileType & "shell") _
            .Split(","c)(0)
        If extra.Length = 0 Then
            extra = "open"
        End If
    End If
    Return GetDefaultValueFromClassesRoot( _
        String.Format("{0}\shell\{1}\command", fileType, extra))
End Function
C#
コードを隠すコードを選択
/// <summary>
/// 指定されたファイルに関連付けられたコマンドを取得する
/// </summary>
/// <param name="fileName">関連付けを調べるファイル</param>
/// <param name="extra">アクション(open,print,editなど)</param>
/// <returns>取得できた時は、コマンド(実行ファイルのパス+コマンドライン引数)。
/// 取得できなかった時は、空の文字列。</returns>
/// <example>
/// "1.txt"ファイルの"open"に関連付けられたコマンドを取得する例
/// <code>
/// string command = FindAssociatedCommand("1.txt", "open");
/// </code>
/// </example>
public static string FindAssociatedCommand(
    string fileName, string extra)
{
    //拡張子を取得
    string extName = System.IO.Path.GetExtension(fileName);
    if (extName.Length == 0 || extName[0] != '.')
    {
        return string.Empty;
    }

    //HKEY_CLASSES_ROOT\(extName)\shell があれば、
    //HKEY_CLASSES_ROOT\(extName)\shell\(extra)\command の標準値を返す
    if (ExistClassesRootKey(extName + @"\shell"))
    {
        return GetShellCommandFromClassesRoot(extName, extra);
    }

    //HKEY_CLASSES_ROOT\(extName) の標準値を取得する
    string fileType = GetDefaultValueFromClassesRoot(extName);
    if (fileType.Length == 0)
    {
        return string.Empty;
    }

    //HKEY_CLASSES_ROOT\(fileType)\shell\(extra)\command の標準値を返す
    return GetShellCommandFromClassesRoot(fileType, extra);
}

public static string FindAssociatedCommand(string fileName)
{
    return FindAssociatedCommand(fileName, "open");
}

private static bool ExistClassesRootKey(string keyName)
{
    Microsoft.Win32.RegistryKey regKey =
        Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(keyName);
    if (regKey == null)
    {
        return false;
    }
    regKey.Close();
    return true;
}

private static string GetDefaultValueFromClassesRoot(string keyName)
{
    Microsoft.Win32.RegistryKey regKey =
        Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(keyName);
    if (regKey == null)
    {
        return string.Empty;
    }
    string val = (string)regKey.GetValue(string.Empty, string.Empty);
    regKey.Close();

    return val;
}

private static string GetShellCommandFromClassesRoot(
    string fileType, string extra)
{
    if (extra.Length == 0)
    {
        //アクションが指定されていない時は、既定のアクションを取得する
        extra = GetDefaultValueFromClassesRoot(fileType + @"shell")
            .Split(',')[0];
        if (extra.Length == 0)
        {
            extra = "open";
        }
    }
    return GetDefaultValueFromClassesRoot(
        string.Format(@"{0}\shell\{1}\command", fileType, extra));
}
  • 履歴:
  • 2014/7/14 「AssocQueryStringを使用する方法」を追加。「FindExecutableを使用する方法」のサンプルをメソッドにした。「レジストリを調べる方法」のサンプルを改良。その他。

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • このサイトで紹介されているコードの多くは、例外処理が省略されています。例外処理については、こちらをご覧ください。
  • コードの先頭に記述されている「Imports ??? がソースファイルの一番上に書かれているものとする」(C#では、「using ???; がソースファイルの一番上に書かれているものとする」)の意味が分からないという方は、こちらをご覧ください。
  • Windows Vista以降でUACが有効になっていると、ファイルやレジストリへの書き込みに失敗する可能性があります。詳しくは、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。