ardarimの日記 このページをアンテナに追加 RSSフィード

2010-02-20

[][][] 開いているファイルを強制的にクローズするWSHスクリプトを書いてみた

【Windowsプログラミング】プログラムから他のプロセスが使っているファイルを削除する方法を教えてください。 (方法はなんでもOkですが、Windows上で動作するのが条件です.. - 人力検索はてな
の件に関連して。JScriptで書いてみた。


実際にはHandle.exeラッパーオブジェクトHandleとしての実装。Handle.exeをスクリプトファイルと同じディレクトリに放り込んでおく必要がある。Handle v3.42で動作確認。

Handle.exec(options)
    指定された引数でHandle.exeを実行。戻り値はObjectオブジェクトで、errorLevelにエラーレベル、outputに標準出力への実行結果が入っている。
Handle.enumOpenHandles(fileName)
  指定されたファイル名をオープンしているプロセス一覧を取得する。戻り値はOpenHandleInfoオブジェクトの配列で、processNameにオープンしているプロセス名、pidにオープンしているプロセスID、handleにオープンハンドルが入っている。
Handle.closeOne(pid, handle)
指定されたプロセスIDのオープンハンドルをクローズする。
Handle.closeAll(fileName)
指定されたファイル名のすべてのオープンハンドルをクローズする。


クローズは、実行すると有無を言わさず問答無用で強制クローズするので間違ってもシステムが開いているファイルとかを指定しないように。何が起きても知らないよ。実際のところ、handle.exeはフルパスの一部でも受け入れちゃうので、*.txtとか指定しちゃうとあらゆるオープン中のテキストファイルを強制クローズしちゃったりするので要注意。フルパス推奨。

JavaScriptはまだまだhack力が足りないのでかっこいい書き方じゃないのはご容赦。


テストプログラム
引数で指定したファイルを開いているプロセスを一覧表示する。(cscript xxx.js <ファイル名>で実行)

if(WScript.arguments.length == 0){
    WScript.echo("引数にファイル名(フルパス)を指定してください。");
    WScript.quit(1);
}

fileName = WScript.arguments.item(0);

handle = new Handle();

opener = handle.enumOpenHandles(fileName);
if(opener == null){
    WScript.echo(fileName + "はオープンされていません。")
    WScript.quit(2);
}

for(i = 0; i < opener.length; i++){
    WScript.echo("[" + opener[i].pid + "]" + opener[i].processName + "/" + opener[i].handle);
}

// if(true)にすると強制クローズするサンプル。
if(false){
    if(handle.closeAll(fileName)){
        WScript.echo(fileName + "のすべてのオープンハンドルを強制クローズしました。");
    } else {
        WScript.echo(fileName + "のオープンハンドルの強制クローズに失敗しました。");
        WScript.quit(3);
    }
}

WScript.quit(0);


Handleオブジェクトのソース

function Handle(exePath)
{
    var fso = WScript.createObject("Scripting.FileSystemObject");

    this.handlePath = fso.getParentFolderName(WScript.scriptFullName) +  "\\handle.exe";
    if(exePath){
        this.handlePath = exePath;
    }

    fso = null;

    this.exec = function(options)
    {
        var cmdLine;
        var result = new Object;
        var TemporaryFolder = 2;
        var ForReading = 1;
        var WshShell = WScript.createObject("WScript.Shell");
        var fso = WScript.createObject("Scripting.FileSystemObject");
        var tempFolder = fso.getSpecialFolder(TemporaryFolder);
        var tempFileName = tempFolder.path + "\\" + fso.getTempName();

        // Invoke handle.exe thru the command prompt.
        cmdLine = "%COMSPEC% /c \"" + this.handlePath + "\" " + options + " > " + tempFileName;
        result.errorLevel = WshShell.run(cmdLine, 7, true);
        WshShell = null;

        result.output = new Array;
        file = fso.openTextFile(tempFileName, ForReading, false);

        // Skip header lines
        file.skipLine();
        file.skipLine();                // Version info
        file.skipLine();                // Copyright notice
        file.skipLine();                // URL
        file.skipLine();

        // Read other lines
        while(!file.atEndOfStream){
            result.output.push(file.readLine());
        }

        file.close();
        file = null;

        fso.deleteFile(tempFileName);
        fso = null;

        return result;
    };

    this.enumOpenHandles = function(fileName)
    {
        var file;
        var handleInfo;
        var handleInfoArray;

        result = this.exec(fileName);
        if(result.errorLevel != 0){
            return null;
        }
        if(result.output[0] == "No matching handles found."){
            return null;
        }

        handleInfoArray = new Array;
        while(result.output.length != 0){
            line = result.output.shift();
            handleInfo = new OpenHandleInfo();
            handleInfo.analyze(line);
            handleInfoArray.push(handleInfo);
        }

        return handleInfoArray;
    };

    this.closeOne = function(pid, handle)
    {
        var cmdLine;
        var result;

        cmdLine = "-c " + handle + " -p " + pid + " -y";
        result = this.exec(cmdLine);

        return (result.errorLevel == 0);
    };

    this.closeAll = function(fileName)
    {
        var opener;
        var handleInfo;

        opener = this.enumOpenHandles(fileName);
        if(!opener){
            return false;
        }

        while(opener.length > 0){
            handleInfo = opener.shift();
            if(!this.closeOne(handleInfo.pid, handleInfo.handle)){
                return false;
            }
        }

        return true;
    };

}

function OpenHandleInfo()
{
    this.processName = "";
    this.pid = "";
    this.handle = "";

    this.analyze = function(line)
    {
        var n;

        n = line.indexOf("pid:");
        this.processName = trim(line.slice(0, n));
        line = trim(line.slice(n + 4));

        n = line.indexOf(" ");
        this.pid = line.slice(0, n);
        line = line.slice(n);

        n = line.indexOf(":");
        this.handle = trim(line.slice(0, n));
    };

    function trim(str){
        return str.replace(/^[ ]+|[ ]+$/g, '');
    };

}


所要時間:だいたい3時間

2009-08-28

[][] Autorun.inf問題の対策パッチ

Windows で自動再生機能への更新します。

自動翻訳なんで日本語が変だけど要約すると、USBメモリUSB-HDD、メモリカードなどでは「自動再生」画面を出さなくするパッチ。事実上Autorun.infの完全無効化パッチと言える。CDとDVDだけは従来どおり「自動再生」画面が出る。

パッチ直リンはこちら

ダウンロードの詳細 : Windows XP 用の更新プログラム (KB971029)

ダウンロードの詳細 : Windows Vista 用の更新プログラム (KB971029)

簡単な説明
この更新プログラムをインストールすると、CD ドライブと DVD ドライブでのみ [自動再生] ダイアログの AutoRun エントリが制限されます。

ん…?なんかこの説明もよく読むと日本語でおkって感じだな


Autorun.infの対象をすべてのリムーバブルメディアからROMメディア(CD,DVD)だけに限定したってことかな。

ともあれこれでレジストリいじったりサードパーティツールに頼らずとも、パッチ当てるだけで対策ができるようになる。


サイズが小さく、値段もかなりお手頃間感が出てきてるのでUSBメモリとかSDカード同人ソフトやCG集を頒布するケースが若干増えつつあるけど、このパッチを当てた環境だと自動再生は使えなくなるのでそういうサークルは少しだけ注意が必要かもねー


その他注意事項としてCD見えするUSBメモリは対象外とか書いてあるけどそんな珍妙なデバイス見たこと無いんだぜ。あと無線LANルータとかによく付いてくるUSBメモリを使った自動設定(Wireless Connect Now)も自動設定できなくなるので注意。こっちはけっこういろんな方面に影響あるのかも。
ところでこっちとかでは「Windows Connect Now」になってるね。どっちだ!用語は統一してほしいなあ。

2009-07-02

[] VMMapが使えない

VMMapを使ってみた。


プロセスを選ぶとGPFしちゃう(問題が発生したため、Vmmap - process memory analyzer を終了します。 ご不便をおかけして申し訳ありません。)のでOllyDbgでデバッグに突入。


コールスタックだとこんな感じ。

Call stack of main thread
Address    Stack      Procedure / arguments                 Called from                   Frame
0012F054   00410D41   ???                                   vmmap.00410D3B                0012F0C8
0012F0CC   00410F29   ? vmmap.00410A54                      vmmap.00410F24                0012F0C8
・・・

アドレスが???って?

と思って直前の呼び出し元(vmmap.00410D3B)を覗いてみる。

00410D3B   FF15 E8C34300    CALL DWORD PTR DS:[43C3E8]

で、

DS:[0043C3E8]=00000000

あー、どうみてもぬるぽですほんとうにorz


DS:[0043C3E8]で逆リファしてみたら、上の箇所入れて2箇所しかなくて、もう一箇所は代入。

0042EC04   68 404B4300      PUSH vmmap.00434B40                      ; ASCII "QueryWorkingSetEx"
0042EC09   68 1C4B4300      PUSH vmmap.00434B1C                      ; UNICODE "psapi.dll"
0042EC0E   FF15 9CF24200    CALL DWORD PTR DS:[<&KERNEL32.LoadLibrar>; kernel32.LoadLibraryW
0042EC14   50               PUSH EAX
0042EC15   FF15 BCF24200    CALL DWORD PTR DS:[<&KERNEL32.GetProcAdd>; kernel32.GetProcAddress
0042EC1B   A3 E8C34300      MOV DWORD PTR DS:[43C3E8],EAX

ということらしいので、

どうやらpsapi.dllにQueryWorkingSetExが居ないのが原因らしい。


QueryWorkingSetExの説明を読む限り、

Minimum supported clientは「Windows Vista, Windows XP Professional x64 Edition」とのことなので、ひょっとしたらXP Pro (x86)では動かないのでは?

VMMapの動作環境では「Runs on: Client: Windows XP and higher」になってるけど…。あってるのか?これ


少なくともC:\Windows\system32\psapi.dllにはQueryWorkingSetはいるけど、QueryWorkingSetExはいない。

これが原因ぽいが、QueryWorkingSetEx入りのpsapi.dll(XP Pro x86用)ってどっかから入手できるんだろうか?

入手できないんならXP ProではVMMap動きませんて感じなんだが…

2009-03-17

[][] 印刷ダイアログをスキップする

Windows XPで印刷ダイアログ表示させずに 印刷を実行させる方法をおしえてください。 通常アプリケーションからプリントを実行すると (たとえば IE6で メニュー ファイル .. - 人力検索はてな


Detoursでcomdlg32.dllをフックしてみた。動く動く。

#include <windows.h>
#include <detours.h>

static BOOL (WINAPI *TruePrintDlgA)(LPPRINTDLGA lppd) = PrintDlgA;
static BOOL (WINAPI *TruePrintDlgW)(LPPRINTDLGW lppd) = PrintDlgW;
static HRESULT (WINAPI *TruePrintDlgExA)(LPPRINTDLGEXA lppd) = PrintDlgExA;
static HRESULT (WINAPI *TruePrintDlgExW)(LPPRINTDLGEXW lppd) = PrintDlgExW;

BOOL WINAPI DetourPrintDlgA(LPPRINTDLGA lppd)
{

	if(lppd != NULL){
		lppd->Flags |= PD_RETURNDEFAULT;
		lppd->hDevMode = NULL;
		lppd->hDevNames = NULL;
	}
	return TruePrintDlgA(lppd);
}

BOOL WINAPI DetourPrintDlgW(LPPRINTDLGW lppd)
{

	if(lppd != NULL){
		lppd->Flags |= PD_RETURNDEFAULT;
		lppd->hDevMode = NULL;
		lppd->hDevNames = NULL;
	}
	return TruePrintDlgW(lppd);
}

HRESULT WINAPI DetourPrintDlgExA(LPPRINTDLGEXA lppd)
{
	HRESULT	hres;

	if(lppd != NULL){
		lppd->Flags |= PD_RETURNDEFAULT;
		lppd->hDevMode = NULL;
		lppd->hDevNames = NULL;
	}

	hres = TruePrintDlgExA(lppd);
	if(lppd != NULL) lppd->dwResultAction = PD_RESULT_PRINT;

	return hres;
}

HRESULT WINAPI DetourPrintDlgExW(LPPRINTDLGEXW lppd)
{
	HRESULT	hres;

	if(lppd != NULL){
		lppd->Flags |= PD_RETURNDEFAULT;
		lppd->hDevMode = NULL;
		lppd->hDevNames = NULL;
	}

	hres = TruePrintDlgExW(lppd);
	if(lppd != NULL) lppd->dwResultAction = PD_RESULT_PRINT;

	return hres;
}

static void BeginMyDetours(void)
{

	DetourTransactionBegin();
	DetourUpdateThread(GetCurrentThread());
	DetourAttach((PVOID *)&TruePrintDlgA, DetourPrintDlgA);
	DetourAttach((PVOID *)&TruePrintDlgW, DetourPrintDlgW);
	DetourAttach((PVOID *)&TruePrintDlgExA, DetourPrintDlgExA);
	DetourAttach((PVOID *)&TruePrintDlgExW, DetourPrintDlgExW);
	DetourTransactionCommit();

}

static void EndMyDetours(void)
{

	DetourTransactionBegin();
	DetourUpdateThread(GetCurrentThread());
	DetourDetach((PVOID *)&TruePrintDlgA, DetourPrintDlgA);
	DetourDetach((PVOID *)&TruePrintDlgW, DetourPrintDlgW);
	DetourDetach((PVOID *)&TruePrintDlgExA, DetourPrintDlgExA);
	DetourDetach((PVOID *)&TruePrintDlgExW, DetourPrintDlgExW);
	DetourTransactionCommit();

}


BOOL WINAPI DllMain(HINSTANCE hinst, DWORD dwReason, LPVOID reserved)
{
    if (dwReason == DLL_PROCESS_ATTACH) {
		BeginMyDetours();
    }
    else if (dwReason == DLL_PROCESS_DETACH) {
		EndMyDetours();
    }
    return TRUE;
}

VOID NullExport()
{
}

実行はDetoursのサンプルwithdll.exeでターゲットプロセスに実行時インジェクションする。

仕組みとしては単純明快、一目瞭然。PrintDlg(Ex)をフックして、FlagsにPD_RETURNDEFAULT(印刷ダイアログを表示せずにデフォルトプリンタの設定を構造体に入れるフラグ)を強制的にセットするだけの簡単なもの。PrintDlgExの場合は、dwResultActionをセットしてくれない(0のまんま)みたいなのでPD_RESULT_PRINTを入れてあげないと印刷が始まらないようだ。

2009-02-13

[] ロックされたファイルの対処方法

Windowsだとファイルやフォルダを削除したり、移動したり、リムーバブルメディアを抜こうとした時に

xxxxを削除できません。アクセスできません。
ディスクがいっぱいでないか、書き込み禁止になっていないか、またはファイルが使用中でないか確認してください。

とか

xxxxを削除できません。ほかの人またはプログラムによって使用されています。
ファイルを使用している可能性があるプログラムをすべて閉じてから、やり直してください。

とかメッセージがたまに出たりして困ることがある。

本当にオープン中だったらまだわかるんだけど時々誰も使ってないのにこのエラーが出ると本当にいらいらする。


よく見かける対策としてはUnlockerなんだけど、インストールしなくちゃいけないのが気に食わない。シェル拡張でコンテキストメニューにも食い込んでくるし。

そこでインストール不要でファイルのロック状態の確認/強制解除をする方法を調べてみた。

中級者〜上級者向けなのでハンドルって何?っていう初心者にはお勧めしない。


Handle

はい。やっぱりかゆいところに手の届くSysinternals登場。


ファイルやフォルダを指定してオープンしているプロセスを特定することができる。検索するにはオプションでファイル名やフォルダ名を指定するだけ。

D:\>handle D:\tmp\img

Handle v3.42
Copyright (C) 1997-2008 Mark Russinovich
Sysinternals - www.sysinternals.com

explorer.exe       pid: 592     F88: D:\tmp\img

この場合、explorer.exeが掴んだままになってるのでハンドルを強制的にクローズすればいい。

オプション -c が強制クローズ。-c でファイルハンドル(上の例でF88)、-p でプロセスID(上の例で592)を指定する。

D:\>handle -c F88 -p 592

Handle v3.42
Copyright (C) 1997-2008 Mark Russinovich
Sysinternals - www.sysinternals.com

  F88: File  (RWD)   D:\tmp\img
Close handle F88 in explorer.exe (PID 592)? (y/n) y

Handle closed.

確認のため y を入力すれば完了。(オプション -y で確認を省略可能だけどお勧めはしない)

explorerは「縮小表示」にした時とかたまにファイルを掴みっぱなしになる時がある。

検索は前方一致なので、リムーバブルメディアもドライブ名で検索すればドライブ名以下ロックされているすべてのフォルダ/ファイルが検索対象になるので、何がロックの原因かを特定して、解除できる。まあ、大抵はexplorerだったりするんだが…。


なおハンドルの意味がわかってる人なら、強制クローズしたらどういうことになるのかはご想像の通り。自己責任で。