ここでは、拡張子に関連付けられた実行ファイルのパスを取得する方法を幾つか紹介します。
まず、Win32 APIのFindExecutable functionを使う方法が考えられます。この場合、例えば次のようにして実行ファイルのパスを取得することができます。
''' <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
/// <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はユーザーごとの設定しか見ておらず、たとえすべてのユーザーに共通の設定があったとしても、ユーザーごとの設定が存在しなければ、その設定は存在しないと判断してしまうそうです。
拡張子に関連付けられている実行可能ファイルパスを取得するためのAPIには、AssocQueryString Functionもあります。これを使用するには、Internet Explorer 5以上が必要となります。
AssocQueryStringを使用して、拡張子に関連付けられている実行ファイルのパスを取得する例を以下に示します。この例は、「pinvoke.net: AssocQueryString (shlwapi)」や「Karl Peterson's Classic VB Solution for Finding Executable Associations -- Visual Studio Magazine」を参考にして作成しました。
'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
//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などの複雑なルールは無視しています。
''' <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
/// <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)); }
(この記事は、「.NETプログラミング研究 第37号」で紹介したものです。)
注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。