ファイル プロパティの取得

Shell32.Folder

Shell32を利用するには、参照にCOMの[Microsoft Shell Controls And Automation]を追加します。

[STAThread]
static void Main()
{
    string path = @"C:\sample.mp3";

    string directoryName = Path.GetDirectoryName(path);
    string fileName = Path.GetFileName(path);

    Shell32.Shell shell = new Shell32.Shell();
    Shell32.Folder folder = shell.NameSpace(directoryName);
    Shell32.FolderItem folderItem = folder.ParseName(fileName);

    // すべてのプロパティを列挙
    for (int i = 0; i < 1000; i++)
    {
        Console.WriteLine("{0}:{1},{2}",
            i,
            folder.GetDetailsOf(null, i),
            folder.GetDetailsOf(folderItem, i));
    }

    // 指定のプロパティを取得
    string prop0 = folder.GetDetailsOf(folderItem, 0);
    string prop1 = folder.GetDetailsOf(folderItem, 1);
}

アプリケーションのエントリポイントにSTAThread属性を指定しないと、「型 'System.__ComObject' の COM オブジェクトをインターフェイス型 'Shell32.Shell' にキャストできません。IID '{286E6F1B-7113-4355-9562-96B7E9D64C54}' が指定されたインターフェイスの COM コンポーネント上での QueryInterface 呼び出しのときに次のエラーが発生したため、この操作に失敗しました: インターフェイスがサポートされていません (HRESULT からの例外:0x80004002 (E_NOINTERFACE))。」としてSystem.InvalidCastException例外が発生します。

GetDetailsOf()

フォルダ内の項目の詳細を取得できます。

string GetDetailsOf(
    object vItem, // 取得対象のShell32.FolderItem
    int iColumn   // 取得対象のインデックス
    )
Folder.GetDetailsOf method (Windows) | MSDN

vItemをnullとすると、プロパティ名が返されます。

Microsoft.WindowsAPICodePack.Shell.ShellFile

Microsoft.WindowsAPICodePackに含まれるMicrosoft.WindowsAPICodePack.Shell.ShellFileクラスから、プロパティを取得できます。

string path = "sample.mp3";

ShellFile shellFile = ShellFile.FromFilePath(path);
ShellProperties shellProperties = shellFile.Properties;


// すべてのプロパティを列挙
ShellPropertyCollection shellPropertyCollection = shellProperties.DefaultPropertyCollection;
foreach (IShellProperty shellProperty in shellPropertyCollection)
{
    Console.WriteLine("{0},{1}",
        shellProperty.CanonicalName,
        shellProperty.ValueAsObject
        );
}

// 指定のプロパティを取得
ShellProperties.PropertySystem propertySystem = shellProperties.System;

string   title   = propertySystem.Title.Value;
string[] authors = propertySystem.Author.Value;

一方でプロパティへ設定するには、取得に用いたValueプロパティへ設定します。

propertySystem.Title.Value = "sample";
propertySystem.Author.Value = new[] { "sample1", "sample2" };

無効な設定をしても例外などは発生しないため、設定が反映されたかどうかは取得して確認します。設定できない原因についてはエクスプローラのプロパティから設定してみることで、「エラー 0xC00D3E8D: 要求されたプロパティは見つかりませんでした。」のように確認できます。

ShellPropertyWriterクラス

複数のプロパティへ一括して設定するならば、ShellPropertyWriter.WriteProperty()を用います。ただしこの方法だと、無効な設定が1つでも含まれていると、すべての設定に失敗します。

using (ShellPropertyWriter shellPropertyWriter = shellProperties.GetPropertyWriter())
{
    shellPropertyWriter.WriteProperty(SystemProperties.System.Title, "Sample");
    shellPropertyWriter.WriteProperty(SystemProperties.System.Author, new[] { "Sample1", "Sample2" });
} // shellPropertyWriter.Close()は、Dispose()から呼ばれる
Solution 2016 - c# - How to set extended file properties? - Stack Overflow

無効なファイルに設定しようとすると、GetPropertyWriter()の呼び出し時に「Unable to get writable property store for this property.」としてMicrosoft.WindowsAPICodePack.Shell.PropertySystem.PropertySystemExceptionが投げられます。そのときInnerExceptionは、

  • COMExceptionで「データが無効です。 (HRESULT からの例外:0x8007000D)」ならば、不正なファイル
  • COMExceptionで「アクセスが拒否されました。 (HRESULT からの例外:0x80030005 (STG_E_ACCESSDENIED))」ならば、非対応のファイル
  • FileLoadExceptionで「プロセスはファイルにアクセスできません。別のプロセスが使用中です。 (HRESULT からの例外:0x80070020)」ならば、共有違反

となります。

WriteProperty()では内部でIPropertyStore::SetValue()が呼ばれることで値が設定され、Close()ではIPropertyStore::Commit()により保存されます。

書き換え手順

書き換えの処理ではファイルの内容が変更されるのではなく、実際には一時ファイルが作成された上でリネームされます。たとえばC:\sample.wmvのプロパティを書き換えると、次のように処理されます。

  1. 作成: C:\sample.tmp … ファイル名の「.wmv」が「.tmp」に置換された一時ファイルが作成される
  2. 変更: C:\sample.tmp
  3. 作成: C:\sample.wmv~RF***.TMP … ファイル名の「.wmv」が「.wmv~RF***.TMP」に置換された一時ファイルが作成される
  4. 削除: C:\sample.wmv~RF***.TMP
  5. 名前の変更: C:\sample.wmv -> C:\sample.wmv~RF***.TMP … 元のファイルが一時ファイルの名前にリネームされる
  6. 名前の変更: C:\sample.tmp -> C:\sample.wmv … 一時ファイルが元のファイルの名前にリネームされる
  7. 削除: C:\sample.wmv~RF***.TMP

このうち工程3以降のファイルの置換の処理は、File.Replace()と同じです。

Microsoft Learnから検索