ファイルの使用者を調べる PowerShell Cmdlet

(2007年8月8日追記)本記事は「どうやって公開 API だけでファイルの使用者を調べるか?」という技術的興味で書かれたもので,実際にファイルやフォルダが操作できなくなって困ったときの助けになるものではありません.もしファイルやフォルダを移動・削除できなくて困っているのであれば,id:NyaRuRu:20070808:p1 で書いた handle.exe や Process Explorer を使って調べる方法を試してみてください.
(2007年5月6日追記)プロセス開始時刻の 32 bit 目が立っているときに判定を誤るというバグを修正しました.
(2007年7月20日追記)FileInfo 以外を無視するように変更しました.また,エラー発生時にも次のパイプラインオブジェクトの処理を継続するように変更しました.
元ネタは『MSDN Magazine の 2007 年 4 月号』.

Q アプリケーションがファイルにアクセスしようとしたとき、ファイルが別のアプリケーションで使用されているためにアクセスが拒否されます。以前は、Sysinternals (microsoft.com/technet/sysinternals) のツールを使用してそのアプリケーションを見つけていましたが、使用中のアプリケーションからプログラムで見つけたいと思います。特定のファイルを現在使用しているプロセスをプログラムで特定する方法はありますか。

A この質問は何度か受けていますが、ごく最近までその方法がなかったので、お答えするのを毎回差し控えてきました。任意のプロセスによって開かれているファイルに関する情報にはカーネル モード ドライバからアクセスできますが、その情報を公開するユーザー モードの Win32® または Microsoft® .NET Framework API が今まで文書化されていませんでした。しかし、Windows Vista™ のリリースに伴い、少し工夫することによって、ユーザー モードのアプリケーションからこの情報を確認できるようになりました。ただし、適切な名前付きメソッドの API ドキュメントではなく、少し見方を変えて、再起動マネージャ API に注目してみましょう。

再起動マネージャは Vista で導入された,インストーラ支援用の仕組みですが,これが「あるファイルをオープンしていて離さない」プロセスの検出に使える,という話です.

Windows Vista の新機能である再起動マネージャ API は、ソフトウェアのインストール時に必要なシステムの再起動の回数を減らすために追加されました。ソフトウェアのインストールや更新によってシステムの再起動が必要になる主な理由は、インストール時にアクセスする必要があるファイルが実行中のプロセスによって使用されているためです。再起動マネージャ API は、インストーラがアクセスするリソースを保持している再起動マネージャ対応アプリケーションを、インストーラが適切にシャットダウンできるようにすることで、この競合を解決します。さらにこの処理の一環として、再起動マネージャ API によって、インストーラは必要に応じて以前に登録したリソースを保持しているプロセスを照会することができます。この機能の利点は、どのアプリケーションも "インストーラ" と見なすことができることで、アプリケーションは再起動マネージャ API を使用して、特定のリソースを保持している他のプロセスを照会することができます。そこで、目的のファイルへのパスをパラメータとして受け取るメソッドを作成し、そのメソッドで再起動マネージャ API を使用して、ファイルを使用しているプロセスの一覧を取得できます。

まあ何にせよ,カーネルモードの非公開構造体を弄らなくても良いというのは確かにありがたい話です.記事には C#コマンドライン版のサンプルコードが付属していますが,せっかくなので PowerShell の Cmdlet を作ってみました.当然ながら Vista 以降専用です.

ヘルプをまじめに書いていませんが,以下のように FileInfo (またはその配列) を入力すると,そのファイルをオープンしている Process の配列を返します.

Windows PowerShell
Copyright (C) 2006 Microsoft Corporation. All rights reserved.

PS C:\Windows\System32> Add-PSSnapin Prelude
PS C:\Windows\System32> ls d3d9.dll | Get-FileLockingProcess

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
    205       9   122268      89548   236   590.96   2824 dwm
    398      10    11060      11756    88     2.61   3616 taskeng
   1151      27    57628      68408   254   119.47   5532 explorer

PSSnapin の名前は,特に思いつかなかったので適当に Prelude にしてあります.



まあ現実には Explorer から「右クリック」→「送る」とかで使える GUI ツールの方が利用者を選ばないかもしれませんが……