C#を利用したデスクトップアプリケーションの開発において、プログラム内からWindowsエクスプローラーを起動し、特定のフォルダを表示したりファイルを選択させたりする機能は非常に頻繁に利用されます。

ユーザーが保存したファイルの場所を即座に確認できるようにしたり、ログファイルが格納されているディレクトリを案内したりする場合など、利便性を向上させるための重要な要素といえます。

Windows OSと親和性の高いC#では、標準ライブラリである「System.Diagnostics」名前空間を使用することで、数行のコードでエクスプローラーを自在に操作することが可能です。

本記事では、基本的なフォルダの開き方から、引数を利用したファイルの選択表示、さらに実務で役立つエラーハンドリングやWin32 APIを組み合わせた高度な実装方法まで詳しく解説します。

Process.Start を使用した基本的なフォルダの開き方

C#でエクスプローラーを開く最も一般的かつ簡単な方法は、System.Diagnostics.Process.Start メソッドを使用することです。

このメソッドは、指定した実行ファイルやドキュメントを関連付けられたアプリケーションで起動するための機能を提供します。

エクスプローラーを起動する場合、起動対象のパスとしてフォルダのフルパスを渡すだけで、OSが自動的にエクスプローラーを介してその場所を表示します。

ただし、.NET Core以降(.NET 5、.NET 6、.NET 8、および最新の.NET 10など)では、デフォルトで UseShellExecute プロパティが false に設定されているため、注意が必要です。

UseShellExecuteプロパティの重要性

従来の.NET Frameworkでは、Process.Start("C:\\") と記述するだけでエクスプローラーが起動していました。

しかし、モダンな.NET環境ではシェル機能を明示的に有効にする必要があるため、ProcessStartInfo クラスを使用して UseShellExecute を true に設定するのが現在の標準的な実装です。

この設定を行わないと、指定したパスが実行ファイルではないと判断され、例外が発生する原因となります。

以下のコードは、デスクトップ環境で安全にフォルダを開くための基本的な実装例です。

C#
// System.Diagnostics を使用してフォルダを開く
using System.Diagnostics;

public void OpenFolder(string folderPath)
{
    // ProcessStartInfo のインスタンスを作成
    ProcessStartInfo startInfo = new ProcessStartInfo
    {
        FileName = folderPath,
        // シェル実行を有効にする
        UseShellExecute = true 
    };

    // プロセスを開始してエクスプローラーを表示
    Process.Start(startInfo);
}

このコードを実行すると、引数で渡された folderPath がエクスプローラーの新しいウィンドウとして表示されます。

非常にシンプルな実装ですが、OSの標準機能に依存しているため動作が安定しているのが特徴です。

ファイルを選択した状態でエクスプローラーを開く

単にフォルダを開くだけでなく、特定のファイルを「選択状態」にした上でエクスプローラーを表示させたい場面も多く存在します。

例えば、インストーラーの終了後にログファイルの場所を表示したり、出力したPDFファイルをユーザーに示したりする場合です。

この動作を実現するには、エクスプローラーの実体である explorer.exe に対して /select, というコマンドライン引数を渡す必要があります。

この引数を使用することで、フォルダを開くだけでなく、その中の特定のアイテムにフォーカスを当てた状態でウィンドウを表示できます。

コマンドライン引数による制御

コマンドライン引数を利用する場合は、FileNameexplorer.exe を指定し、Arguments にパスを記述します。

この際、パスの中にスペースが含まれている可能性を考慮し、パス全体をダブルクォーテーションで囲むことが推奨されます。

また、/select, の直後にはスペースを入れずにパスを続けるのが一般的な構文です。

具体的な実装方法は以下の通りです。

C#
// 特定のファイルを選択した状態でエクスプローラーを開く
using System.Diagnostics;

public void OpenExplorerWithSelection(string filePath)
{
    // 引数の構成: /select,"ファイルパス"
    string argument = $"/select,\"{filePath}\"";

    ProcessStartInfo startInfo = new ProcessStartInfo
    {
        FileName = "explorer.exe",
        Arguments = argument,
        UseShellExecute = true
    };

    // プロセスの起動
    Process.Start(startInfo);
}

この方法を用いることで、ユーザーは自らファイルを探す手間を省くことができ、アプリケーションのユーザビリティが劇的に向上します。

特に大量のファイルが保存されるディレクトリにおいては、ターゲットを一目で把握できるという大きなメリットがあります。

エクスプローラー操作で利用可能な主な引数

explorer.exe には、動作をカスタマイズするためのいくつかのスイッチ(引数)が用意されています。

開発するアプリケーションの要件に合わせて、これらの引数を使い分けることが重要です。

以下の表に、よく使用される引数の種類と、その動作をまとめました。

引数動作内容
/n新しいウィンドウで指定された場所を開きます。
/e標準の表示形式(ツリー表示を含む)でフォルダを開きます。
/root,<object>指定したオブジェクトをルートとしてエクスプローラーを開きます。
/select,<path>指定したファイルやフォルダを選択した状態で親フォルダを開きます。

日常的な開発においては、/select, が最も多用されますが、特定のフォルダをルートとして制限して見せたい場合には /root, も有効な選択肢となります。

エラーハンドリングとパスの妥当性確認

外部プロセスであるエクスプローラーを起動する際には、必ずエラーハンドリングを実装しておくべきです。

指定されたパスが存在しない場合や、不正な文字列が含まれている場合、アプリケーションが予期せぬ例外(Win32Exception など)をスローして停止してしまう恐れがあります。

まずは、System.IO.Directory.ExistsSystem.IO.File.Exists を使用して、パスの存在チェックを事前に行うのが基本です。

また、ネットワークドライブなどの不安定な場所を指している可能性も考慮し、try-catch ブロックで囲むのが安全な実装と言えます。

C#
// 安全にエクスプローラーを起動する実装例
using System;
using System.IO;
using System.Diagnostics;
using System.ComponentModel;

public void SafeOpenExplorer(string targetPath)
{
    try
    {
        // パスが空でないか、および存在するかを確認
        if (string.IsNullOrWhiteSpace(targetPath) || (!Directory.Exists(targetPath) && !File.Exists(targetPath)))
        {
            throw new FileNotFoundException("指定されたパスが見つかりません。");
        }

        // フォルダかファイルかを判定して引数を切り替える
        string argument = File.Exists(targetPath) ? $"/select,\"{targetPath}\"" : $"\"{targetPath}\"";

        ProcessStartInfo startInfo = new ProcessStartInfo
        {
            FileName = "explorer.exe",
            Arguments = argument,
            UseShellExecute = true
        };

        Process.Start(startInfo);
    }
    catch (Win32Exception ex)
    {
        // OS側のエラー(アクセス拒否など)をキャッチ
        Console.WriteLine($"システムエラーが発生しました: {ex.Message}");
    }
    catch (Exception ex)
    {
        // その他の例外をキャッチ
        Console.WriteLine($"エラー: {ex.Message}");
    }
}

このように実装することで、実行時のクラッシュを防ぎ、ユーザーに対して適切なメッセージを表示することが可能になります。

特に「アクセス権限」が原因で開けないケースも想定されるため、汎用的な例外処理は必須です。

Win32 APIを利用した高度な操作

Process.Start は非常に便利ですが、一つ欠点があります。

それは、既に同じフォルダが開いている場合でも、新しいウィンドウが重複して開いてしまう点です。

より洗練されたユーザー体験を提供したい場合は、WindowsのネイティブAPIである SHOpenFolderAndSelectItems を利用する方法があります。

このAPIを使用すると、既に開いているエクスプローラーのウィンドウがあればそれを再利用し、対象のファイルにフォーカスを当てるという挙動が実現できます。

P/InvokeによるAPI呼び出し

C#からWin32 APIを呼び出すには、P/Invoke(プラットフォーム呼び出し)を利用します。

実装はやや複雑になりますが、プロフェッショナルなツールや商用アプリケーションではこの手法が好まれることがあります。

以下のコードは、APIを利用してファイルを選択する際のスニペットです。

C#
// Win32 API を利用した実装の概念
using System;
using System.Runtime.InteropServices;

public static class NativeMethods
{
    // シェルAPIの定義(簡略化)
    [DllImport("shell32.dll", SetLastError = true)]
    public static extern int SHOpenFolderAndSelectItems(
        IntPtr pidlFolder,
        uint cidl,
        IntPtr[] apidl,
        uint dwFlags);

    // パスからPIDL(ポインタ識別子)を取得する関数なども必要
}

完全な実装には ILangGuidPIDL の管理が必要となるため、学習コストは高いものの、OSと完全に統合されたシームレスな操作感を提供できます。

ただし、通常の業務アプリケーションであれば、保守性の観点から Process.Start を利用した手法で十分であることがほとんどです。

セキュリティとベストプラクティス

エクスプローラーを開く際の注意点として、セキュリティリスクの回避が挙げられます。

外部からの入力値(ユーザーが入力したパスなど)をそのまま Process.Start に渡す構成は、コマンドインジェクション攻撃の隙を与える可能性があります。

例えば、パスに細工をして別の実行ファイルを起動させるといった不正操作を防ぐ必要があります。

パスのサニタイズ

必ず Path.GetFullPath を使用してパスを正規化し、意図しない相対パスの利用を制限してください。

また、使用する引数を組み立てる際には、文字列補完を用いる際にダブルクォーテーションを適切に配置することで、パスに含まれるスペースが引数の区切りとして誤認されるのを防ぎます。

以下のチェックリストを参考に、実装の安全性を確認してください。

  • 入力されたパスがシステム上の有効な形式であるか確認している。
  • Path.GetInvalidPathChars を用いて不正な文字を排除している。
  • 絶対パスのみを許可し、相対パスによる階層移動(../)を防止している。
  • UseShellExecute を適切に設定し、予期せぬ実行ファイルの起動を防いでいる。

これらの対策を講じることで、利便性と安全性を両立させた堅牢なアプリケーションを構築できます。

まとめ

C#からエクスプローラーを操作する方法は、単純なフォルダ表示から特定のファイルの選択まで、多岐にわたる表現が可能です。

最も基本的な Process.StartUseShellExecute の組み合わせをマスターするだけで、多くの要件を満たすことができます。

さらに、コマンドライン引数の /select, を活用することで、ユーザーを迷わせない親切なインターフェースを提供できるでしょう。

実装の際には、必ずパスの存在確認や try-catch による例外処理を行い、堅牢なコードを心がけてください。

より高度な制御が必要な場合には Win32 API の検討も視野に入りますが、まずは標準機能を正しく使いこなすことが、保守性の高いプログラムへの第一歩となります。

今回紹介した手法を活用し、より使いやすいWindowsアプリケーションの開発に取り組んでみてください。