19
27

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【C#】エクスプローラ操作の自動化例:リフレクションとShellでファイルを選択して起動

Last updated at Posted at 2025-09-13

[C#] Example of Explorer Operation Automation: Selecting and Launching Files with Reflection and Shell

枯れたおっさんはいつもC#しか書かない

既出な気がするけど、Shell使ったExplorer操作の例があまりないようなので。

この記事のCodeはAI6割、手打ちでの調整が4割ぐらいです(たぶん)
Linkは自分で普通に探してます(AIによるWebSearchも使用する)

既存のExplorerに対して確実に操作を行う方法が判明したので3ストック以上で公表します。
→公表済み

最近の流行っぽいので真似しましたが、AIの使用はもはや暗黙の了解だと考えております。リファレンスとして実際のアクセス可能なLinkを貼ればよいと思う。

参考記事

C#: 実行時に名前を指定してメソッドを呼び出す
https://qiita.com/nicklegr/items/296bf3533c592a5563c1

C# dynamic型の罠:マメにキャストして型を明示しないと・・・コンパイル時の型チェックが甘くなる&実行時の処理が増える
https://qiita.com/kob58im/items/6361556750fb0b575800

Developing with Windows Explorer
https://learn.microsoft.com/en-us/windows/win32/shell/developing-with-windows-explorer

エクスプローラ(Explorer.EXE)で開いているフォルダのパスを取得する
http://tinqwill.blog59.fc2.com/blog-entry-84.html

C++20&WinAPI &WIL Shell.Applicationでエクスプローラーウィンドウの情報を取得する
https://potisan-programming-memo.hatenablog.jp/entry/2022/04/24/102126

エクスプローラーで特定のフォルダを開くときはWinApiを使おう
https://zenn.dev/nabezokodaikon/articles/83701c6d29eb69
今回参考度が高い記事。
Shell.Open()だと新規Windowとして開いてしまうのでこちらも併用した。

前置き

普通のプロセス起動(Process.Start)だとExplorer.exeが何度も開いてしまい、メモリを圧迫します。これを回避するCode例です。

ファイルを開く、特定のフォルダを新規タブで開くなど、かなり色々やれます。無理だと思い込んでいた

型チェックが利かないので実行時エラーになりません
メソッド名が間違っててもわかりません
→ 引数が要るのか要らないのかすら一見して分かりません。

Win32APIも併用してます。

公開されたCOM Interfaceにより安定したExplorerの操作が実現されます。

内部APIが非公開のプロセスを操作する場合
→ APIやCOM Interfaceが非公開だとリバースエンジニアリングで解析して無理やり呼ぶしかなく、不安定。
 → その場合でもUIAutomationという手段が用意されている

Explorerは公開APIがあるので可能なんです。
Shell.Openで開いたexeは重複チェックができない 
 ┗ Explorer.exeと同様の公開Com インターフェースがあれば可能。
  ┗ 自分のウィンドウやドキュメントを列挙する仕組みを公開していれば、Explorer と同じように「既存インスタンスを探してアクティブ化」できる

$\color{lightblue}{\tiny \textsf{電卓を操作するだけの記事とかつまらない}}$

想定以上に反響があったので(この前寄付を貰ったので)

Gitを置いておきます。

ご支援頂けた方

ありがとうございました。

Explorer.exeを開い

て特定のファイルを選択状態にする場合 その1

そんな簡単に出てこないネタなんだが勿体ない

確実に既存のExplorer.exeを列挙するCodeです。
 →その2だとなぜかユーザー操作のExplorer Windowが取得できない

他言語の情報だろうと役に立つことはよくあります。

詳細な説明

こんな記事もあるが
C#でIEを操作する
→ 仕組みとしては「ShellWindows = Explorer + ブラウザ(かつては IE)」という仕様
なので実はExplorer(ファイラー)も操作できる。
流石にそんなこと誰も知らないだろう。

→ 今ではNavigateメソッドが関連付けされたブラウザで開く仕様になってる

昔はInternet Explorere(IE)が存在したのでexplorer.exeと混ざる可能性があったが、今(Windows11)では存在していないので区別しなくていい

実行結果 その1

動画では1秒待機にしているので見かけ上もっさり気味。
50msでぬる挙動。

必要なもの

SHDocVw (Microsoft Internet Controls) を参照追加

Microsoft Internet Controls → これが SHDocVw 名前空間になる
(InternetExplorer / IWebBrowser2 などが入っている)

image.png

Code

新規で開く場合にファイル選択状態にするため、
Process.Start("explorer.exe", $"/select , \"{_filePath}\"");も併用している

方法2と同様Com Interfaceを介して呼んでいるのでDynamic(リフレクション)的な取り扱いが可能なうえに、使えるメソッドが大幅に増える。

便宜上、一部抜粋するがこれで動くだろう。

testButton
using Microsoft.Win32;
using SHDocVw;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows;

public partial class MainWindow : Window
   {
     public MainWindow()
       {
         InitializeComponent();
       }
       

       [DllImport("user32.dll")]
       [return: MarshalAs(UnmanagedType.Bool)]
       private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

       [DllImport("user32.dll")]
    private static extern bool SetForegroundWindow(IntPtr hWnd);


       string _filePath = string.Empty;
        private void testNutton_Click(object sender, RoutedEventArgs e)
        {
            //  Shell COM を列挙して HWND と一致するものを探す
            Type? shellType = Type.GetTypeFromProgID("Shell.Application");



            if (shellType is null)
                throw new InvalidOperationException("Shell.Application COM が利用できません。");

            dynamic? dynamicShell = Activator.CreateInstance(shellType);
            if (dynamicShell == null)
            {
                throw new InvalidOperationException("");
            }
            ShellWindows windows = dynamicShell.Windows();

            string? fileName = Path.GetFileName(_filePath);
            string? folderName = Path.GetDirectoryName(_filePath);

            try
            {
            //InternetExplorer を列挙
+            foreach (InternetExplorer window in windows)
           列挙
                //Explorerを開く(Navigate1)はブラウザを開く
                window.Navigate2(folderName);


   ///////System.Runtime.InteropServices.COMException: 'Unexpected HRESULT has been returned from a call to a COM component.' 対策
+   if (window.ReadyState != SHDocVw.tagREADYSTATE.READYSTATE_COMPLETE)
+       Thread.Sleep(50); //Load完了していない場合に50ms待機





                foreach (var item in window.Document.Folder.Items())
                    if (string.Equals(item.Name, fileName, StringComparison.OrdinalIgnoreCase))
                    {

+                        window.Document.SelectItem(item, 0x4);   // 既存解除
                        window.Document.SelectItem(item, 1);   // 選択

                    }
                //Activae Window

                ShowWindow((nint)window.HWND, 9);
                //private const int SW_RESTORE = 9; // 最小化から復元


                SetForegroundWindow((nint)window.HWND);
                return;
            }

          //Explorer Window not found
            //dynamicShell.Explore(folderName);
            Process.Start("explorer.exe", $"/select , \"{_filePath}\"");

        }
        catch (Exception ex)
        {
        MessageBox.Show(ex.Message);
        }


ファイルパス取得用(ファイル選択に使う)

 private void reffernceFolderButton_Click(object sender, RoutedEventArgs e)
        {
            // ダイアログのインスタンスを生成
            var dialog = new OpenFileDialog();

            // ファイルの種類を設定
            dialog.Filter = "全てのファイル (*.*)|*.*";

            // ダイアログを表示する
            if (dialog.ShowDialog() == true)
            {
                // 選択されたファイル名 (ファイルパス) をメッセージボックスに表示
                testBox.Text = dialog.FileName;
                _filePah = dialog.FileName;
            }
        }

ファイル選択フラグの説明表

XAML

Grid周りだけおいとくので、新規プロジェクトで置き換えて
クソレガシーWinformより便利だと実感すべし

 <Grid>
     <StackPanel Orientation="Vertical" VerticalAlignment="Center">
     <StackPanel Orientation="Horizontal" 
     HorizontalAlignment="Center">
         
             <TextBox x:Name="testBox" Width="200" Height="50"
     TextWrapping="NoWrap"
     Margin="25"/>
             <Button x:Name="reffernceFolderButton" Width="80" Height="50"  FontSize="30"
         Click="reffernceFolderButton_Click"
         Content="file"/>
             <Button Content="OpenExplorer" x:Name="OpenExplorer" Height="50"  Width="130"
            Click="OpenExplorer_Click"/>
         
     </StackPanel>
         <Button x:Name="testNutton" Content="test" Width="50" Height="50"
         Click="testNutton_Click"/>
     </StackPanel>
 </Grid>

あとがき

こんな面倒な手間をかけさせてくれてどうもありがとう(もう少しストックに時間掛かると思っていた ^_^###)
みなさん結構知りたがりですね。

Explorer.exeを開いて特定のファイルを選択状態にする場合 その2

実行結果(Twitter)

(ユーザー操作のものは列挙されず、2つ目が開いてしまう)

Code

以下のCodeだと既に開いている(ユーザー操作の)Explorer.exeがあるとチェックされず、追加でWindowが開く。

一応重複チェックは出来てるのでSelectFileInExplorerメソッドは要らないかも。一応実験目的で追加した。

→ (プログラム外で)既に開いているExplorer.exeがあっても再利用されず、必ず新規のExplorerから重複チェックが行われます。

Windows11の場合の解決方法がちょっと不明です。
拘る必要はないと思われるが

→ 既に方法が判明しており、ユーザー操作ののExplorer.exeに対して開く事が出来るようになりました。

using Microsoft.Win32;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows;


namespace ExplorerReflectionTest
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }


        [DllImport("user32.dll")]
        private static extern bool SetForegroundWindow(IntPtr hWnd);


        [DllImport("shell32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
        public static extern int SHParseDisplayName(
    [MarshalAs(UnmanagedType.LPWStr)] string pszName,
    IntPtr pbc,
    out IntPtr ppidl,
    uint sfgaoIn,
    out uint psfgaoOut);

        [DllImport("shell32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
        public static extern int SHOpenFolderAndSelectItems(
    IntPtr pidlFolder,
    uint cidl,
    [MarshalAs(UnmanagedType.LPArray)] IntPtr[] apidl,
    uint dwFlags);



        public static void ActivateExplorerWindow(dynamic explorer)
        {
            try
            {
                // explorer.HWND でウィンドウハンドルを取得
                IntPtr hwnd = (IntPtr)explorer.HWND;
                if (hwnd == IntPtr.Zero)
                    return;
                SetForegroundWindow(hwnd); // 前面に表示
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"Explorer をアクティブにできません: {ex.Message}");
            }
        }

        string _filePah = string.Empty;

        private void reffernceFolderButton_Click(object sender, RoutedEventArgs e)
        {
            // ダイアログのインスタンスを生成
            var dialog = new OpenFileDialog();

            // ファイルの種類を設定
            dialog.Filter = "全てのファイル (*.*)|*.*";

            // ダイアログを表示する
            if (dialog.ShowDialog() == true)
            {
                // 選択されたファイル名 (ファイルパス) をメッセージボックスに表示
                testBox.Text = dialog.FileName;
                _filePah = dialog.FileName;
            }
        }

        public static void SelectFileInExplorer(string filePath)
        {
            IntPtr pidlFolder, pidlFile;
            uint attrs;

            string? folder = Path.GetDirectoryName(filePath);

            // フォルダ PIDL
            SHParseDisplayName(folder, IntPtr.Zero, out pidlFolder, 0, out attrs);

            // ファイル PIDL
            SHParseDisplayName(filePath, IntPtr.Zero, out pidlFile, 0, out attrs);

            try
            {
                SHOpenFolderAndSelectItems(pidlFolder, 1, new[] { pidlFile }, 0);
            }
            finally
            {
                Marshal.FreeCoTaskMem(pidlFolder);
                Marshal.FreeCoTaskMem(pidlFile);
            }
        }



        private void OpenExplorer_Click(object sender, RoutedEventArgs e)
        {
            string filePath = _filePah;

            if (!File.Exists(filePath))
                return;

            string? folderName = Path.GetDirectoryName(filePath);
            string fileName = Path.GetFileName(filePath);

            dynamic? shell = null;
            try
            {
                Type? shellType = Type.GetTypeFromProgID("Shell.Application");
                if (shellType is null)
                    return;

                shell = Activator.CreateInstance(shellType)!;
                //COM のインスタンスなので、使い終わったら必ず ReleaseComObject。

                


                
                dynamic windows = shell.Windows();
                dynamic? explorer = null;

                foreach (dynamic window in windows)
                {
                    string? appPath = window.FullName as string;
                    if (string.IsNullOrEmpty(appPath))
                        continue;

                    // explorer.exe 以外はスキップ
                    if (!appPath.EndsWith("explorer.exe", StringComparison.OrdinalIgnoreCase))
                        continue;

                    string path = Path.GetFullPath(window.Document.Folder.Self.Path);

                    if (string.Equals(path.TrimEnd('\\'), folderName, StringComparison.OrdinalIgnoreCase))
                    {
                        explorer = window;
                        break;
                    }
                }

                if (explorer != null)
                {
                    dynamic items = explorer.Document.Folder.Items();
                    foreach (var item in items)
                    {
                        if (string.Equals(item.Name, fileName, StringComparison.OrdinalIgnoreCase))
                        {
                            explorer.Document.SelectItem(item,0x4); // Erase Selection
                            explorer.Document.SelectItem(item, 0x1); // select
                            break;
                        }
                    }

                    ActivateExplorerWindow(explorer);

                    shell.Open(filePath);
                    //File Open(関連付けられたexeで開く)

                }
                else
                {

                    SelectFileInExplorer(filePath);
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
            finally
            {
                if (shell != null)
                    Marshal.ReleaseComObject(shell);
            }
        }
    }
}

注意書きとフラグのパラグラフ

ILFreee関数 (shlobj_core.h)というのがあるが、Win95/98/Meのレガシー環境向けなので使用しないこと.

https://stackoverflow.com/questions/63736090/should-i-cotaskmemfree

explorer.Document.SelectItem(item, 0x1)の第2引数について

Windows Shell API における「アイテム選択フラグ (SVSIF_xxx)」 の一つです。

_SVSIF列挙体 _SVSIF enumeration (shobjidl_core.h)

以下表(翻訳済み)

定数 16進数 説明
SVSI_DESELECT 0 0x00000000 項目の選択を解除します。
SVSI_SELECT 0x1 0x00000001 項目を選択します。
SVSI_EDIT 0x3 0x00000003 アイテムの名前を名前変更モードにします。この値にはSVSI_SELECTが含まれます。
SVSI_DESELECTOTHERS 0x4 0x00000004 選択されている項目以外のすべての項目を選択解除します。項目パラメータがNULLの場合、すべての項目の選択を解除します。
SVSI_ENSUREVISIBLE 0x8 0x00000008 1画面にすべての内容を表示できないフォルダーの場合は、選択した項目を含む部分を表示します。
SVSI_FOCUSED 0x10 0x00000010 複数の項目が選択されている場合は、選択された項目にフォーカスを与え、メソッドによって返されるコレクションのリストの先頭に項目を配置します。
SVSI_TRANSLATEPT 0x20 0x00000020 入力ポイントを画面座標からリストビュークライアント座標に変換します。
SVSI_SELECTIONMARK 0x40 0x00000040 IFolderView::GetSelectionMarkedItemを使用してクエリできるように項目をマークします。
SVSI_POSITIONITEM 0x80 0x00000080 ウィンドウのデフォルトビューを使用して項目を配置します。ほとんどの場合、項目は最初に利用可能な位置に配置されます。ただし、マウスで位置付けされたコンテキストメニューの処理中に呼び出しが行われた場合は、コンテキストメニューの位置に基づいて項目が配置されます。
SVSI_CHECK 0x100 0x00000100 アイテムはチェックされている必要があります。このフラグは、チェックモードがサポートされているビュー内のアイテムで使用されます。
SVSI_CHECK2 0x200 0x00000200 ビューがトライチェックモードの場合の2番目のチェック状態。チェック状態には3つの値があります。トライチェックモードを指定するには、IFolderView2::SetCurrentFolderFlagsで FWF_TRICHECKSELECT を指定します。FWF_TRICHECKSELECT の3つの状態は、チェックなし、SVSI_CHECK、SVSI_CHECK2 です。
SVSI_KEYBOARDSELECT 0x401 0x00000401 項目を選択し、キーボードで選択されたものとしてマークします。この値にはSVSI_SELECTが含まれます。
SVSI_NOTAKEFOCUS 0x40000000 0x40000000 項目を選択またはフォーカスする操作では、ビュー自体にフォーカスを設定しないでください。

FreeCoTaskMemMarshal.ReleaseComObjectの違い

① Marshal.FreeCoTaskMem
対象: Marshal.AllocCoTaskMem などで 自分で確保したアンマネージメモリ領域
役割: 確保した生のポインタを OS (CoTaskMemAlloc で割り当てられたメモリ) に返す。
https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.marshal.freecotaskmem?view=net-9.0

②Marshal.ReleaseComObject

対象: Activator.CreateInstance や Shell.Application で取得した COM オブジェクト

役割: .NET ランタイムが保持している COM オブジェクト参照のカウント(AddRef/Release)を減らす。

まとめ

  • Marshal.FreeCoTaskMem
    → 「生メモリ(IntPtr)を解放」する。

  • Marshal.ReleaseComObject
    → 「COM オブジェクトの参照カウントを減らして、最終的に解放」する。

Shell.Openと似たようなメソッド(Com interface経由)の説明

メソッド名 説明 リファレンス
Open 指定されたフォルダパスをエクスプローラーウィンドウで開く(新規ウィンドウとして開く)。ファイルパスを指定した場合、デフォルトの動作(関連付けられたアプリで開く)を実行します。 https://learn.microsoft.com/en-us/windows/win32/shell/shell-open
Explore 指定されたフォルダを2ペインのエクスプローラビュー(ツリーとリスト)で開く。引数はフォルダパス(文字列またはShellSpecialFolderConstan https://learn.microsoft.com/en-us/windows/win32/shell/shell-explore
BrowseForFolder ユーザーにフォルダ選択ダイアログを表示し、選択されたフォルダを返す。引数として親ウィンドウハンドルやタイトルを指定可能。 https://learn.microsoft.com/en-us/windows/win32/shell/shell-browseforfolder
ShellExecute (WScript.Shell経由) ファイルやURLを指定し、関連付けられたアプリケーションで実行(起動)。引数にファイルパス、動詞(open/editなど)を指定。 https://learn.microsoft.com/en-us/windows/win32/shell/shell-shellexecute

その他

メソッド名 説明 リファレンス
FindFiles 「ファイルの検索」ダイアログを表示(Startメニューの検索相当)。 https://learn.microsoft.com/en-us/windows/win32/shell/shell-findfiles
shell.Application Shellオブジェクト自体を返す(メタプロパティ)。親オブジェクト参照に。 https://learn.microsoft.com/en-us/windows/win32/shell/shell-appliction
Shell.Windows オープン中のShell関連ウィンドウ(Explorerなど)のコレクションを取得。ウィンドウ数や操作に便利。 https://learn.microsoft.com/en-us/windows/win32/shell/shell-windows 本稿の既存Explorer検索に活用。

あとがき

如何でしたか?(構文)
確かにいつもC#しかかかないがそれは時間がないからで

他の言語の記事はけっこう手当たり次第に読んでるつもりです。参考になることも多いです:smile_cat:
C#自体がけっこう"器用"な言語なんで、だいたい取り入れることが出来る
高級言語だからどうしても実行速度とか、出来ないことが出てくるのが唯一の難点ですかねえ

お願い

GitHub Sponserdと連携しました!私も誰かに寄付しようと思います

良い記事を書き続けたいので寄付をお願いします。500円くらいで良いです。動物園通いをしたいの
image.png

リポジトリへのスターもお願いします。
彼女も紹介してください。
仕事を紹介してください。
海外旅行も行きたいです
....etc

19
27
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
27

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?