気難しいExplorerを手なずけるAutoItのプラクティス

 Windowsでファイルのコピーを自動化したい、と思ったとき、様々な方法が思い浮かぶが、一番手っ取り早いのは

COPY SourceFile DestinationFile

とだけ書かれたファイルを「〜.bat」というファイル名で保存することだろう。しかし、もしユーザの行なうファイルコピー操作をシミュレートしたい、という条件を加えたとしたら、この方法はあまりよろしくはない。DOSコマンドを愛して、画面右端に常にDOS窓が開いているヒネくれものなんてそうそういないからだ。(オレは人生で一人しかあったことが無いよ。) そんな超マイノリティのことは無視するとして、私たちのような「極一般的」ユーザが行なうようなExplorer経由でのファイルコピー操作を自動化してみたい。おなじみのAutoItを使えば、一見これは簡単そうだ。(さて、AutoItがおなじみな一般人はマイノリティじゃあないのか?という議論は一旦置いておこう)
 シミュレートを行なうに当たっては、あらかじめどのような操作を行なうのかを明らかにしておく必要がある。

  1. Explorerを開く
  2. Explorerから対象のファイルを選択する
  3. コピーを行なう
  4. Explorerでコピー先のフォルダを開く
  5. 貼り付けを行なう

OK、ExplorerをRun Asから起動しちゃう極一般的な君は、Explorerの「/select,」引数でファイル選択状態で開くことで、1と2をいっぺんに実現できることを想像する。素晴らしい、大正解だ! それじゃあ操作元のファイルを仮に「c:\temp\test.txt」操作先のフォルダを「c:\temp\dst\」とするものとして…、とりあえずコピーを行なうところまでスクリプトを書いてみよう。

Run(@ComSpec & " /c " & 'explorer /select, c:\temp\test.txt', "", @SW_HIDE)
Send("{CTRLDOWN}c{CTRLUP}")

このスクリプトが、何となく嫌なにほいを漂わせていることはお気づきだろう。そう、このスクリプトにはExplorerが立ち上がる前にコピーをしようとしてしまう可能性がある。Explorerが立ち上がるのを見届けてからCtrl+cしなければ、駄目なんだ。考えてもみてよ、君の親父さんが自宅で使っているPCのOSは何だい? 一度試してみるといい、Explorerが立ち上がるまでに、左手の爪を切り終わることだって夢じゃないはずだぜ。

RunWait(@ComSpec & " /c " & 'explorer /select, c:\temp\test.txt', "", @SW_HIDE)
Send("{CTRLDOWN}c{CTRLUP}")

ハハハーッ!引っかかった! これこそまさにExplorerを「気まぐれ」、ってオレが書いた所以だ。一見大丈夫そうなこのスクリプトを、是非 君の親父さんのPCで試してみてくれたまえ。5回目かそこらでScriptがフリーズする謎の問題にぶち当たるはずだ。(…といってもこれは本当はAutoItの問題なのかもしれないけど。) とにかくRunWait()とExplorerの相性は最悪なんだ。

Opt("WinTitleMatchMode", -2)
$src = 'c:\temp\test.txt'
$srcTitle=StringRegExpReplace($src, '\\[^\\]*$','')
$srcTitle=StringRegExpReplace($srcTitle, '.*\\','')
Run(@ComSpec & " /c " & 'explorer /select, '&$src, "", @SW_HIDE)
WinWaitActive($srcTitle)
Send("{CTRLDOWN}c{CTRLUP}")

あー、すごくイロイロ頑張ってくれて、申し訳ないんだけど、実は WinWaitActive()もRunWait()と同じでExplorerと混ぜて使うと「スクリプトがフリーズする」。
もはやこうなると、こんな風にスクリプトを書きたくなってくるかもしれない。

Run(@ComSpec & " /c " & 'explorer /select, c:\temp\test.txt', "", @SW_HIDE)
Sleep(1000)
Send("{CTRLDOWN}c{CTRLUP}")

ああ、でもこれは完全に負けだよ。それじゃ何だい、君のExplorer以上に気難しいAntiVirus君が突如アップデートを始めたら? 1秒以内にExplorerは立ち上がるだろうか。 10MBにも肥大化したスプレッドシートの自動保存が走ったとしたら?(これは5秒待ったって怪しいものだ。)
 段々イライラしてきた? OKそれじゃそろそろ正解を言おう。ちゃんと正解はあるんだ。

Opt("WinTitleMatchMode", -2)
Local $src = 'c:\temp\dst\test.txt'
$srcTilte=StringRegExpReplace($src, '\\[^\\]*$','')
$srcTilte=StringRegExpReplace($srcTilte, '.*\\','')
Run(@ComSpec & " /c " & 'explorer /select, c:\temp\test.txt', "", @SW_HIDE)
While Not ControlFocus($srcTilte, "", "[CLASS:SysListView32]")
	Sleep(100)
WEnd
Send("{CTRLDOWN}c{CTRLUP}")

キーとなるのはControlFocus()。この関数は対象のコントロールに対しフォーカスをあわせようとする。で、あわせられれなかったら0を返すわけだ。[CLASS:SysListView32]はExplorerのファイルがリストアップされている個所を指す。従って、Explorerのウィンドウが立ち上がってるんだか立ち上がってないんだかよく判らない状態だと、当然フォーカスを合わせるのにも失敗する。逆に、フォーカスがあわせられるってことは、「コピー」の操作も受け付けるってこと。Explorerのタイトルはそのファイルの入っているディレクトリ名が「標準設定では」表示される。そのため3行目から4行目でファイルパスの切り出しを行なっている。でもヒネくれたヤツだとウィンドウタイトルにファイルのフルパスを表示しているかもしれない。Opt("WinTitleMatchMode", -2)は大文字小文字を区別しないで、かつ、部分一致でウィンドウタイトルの比較を行なうように動作を変更するので、これでひとまず安心だ。ちなみにもし test.txt が隠しファイルだったら? 心配御無用。Explorer /select,は隠しファイルだろうが何だろうが「選択した状態で」Explorerを開く。従ってCtrl+cを送ってやれば画面上何も表示されてなくても、コピーは行なえる。
 ふぅ。とりあずこれでコピーまで行なえた。面倒になってきたので貼り付けについは次回に回そう(多分無いけど)。とりえあず貼り付け時の注意点を書いておく。

  • コピー先には既にファイルがあるかもしれない。ファイルがあった場合、上書きを「する」動作を行なうこと。
  • 上書き確認のダイアログはWindows XP以前のバージョンとWindows Vistaで異なる。
  • Windows Vistaでファイルを含むフォルダを、同名のファイルを含むフォルダへ上書きした際、やはりXPと動作が異なる。(そしてtest.txtという「フォルダ」をつくることは許されている。ああ、オレはtest.txtがテキストファイルだなんて一言も言ってないよ)