スポンサーリンク

コマンドプロンプトから,Win32 APIや任意のDLLを呼び出して実行しよう (コマンドプロンプトから画面キャプチャする方法の仕組みを理解)


Windowsのコマンドプロンプト上で,Win32 APIなどのDLLを呼び出して実行する。

例えば,コマンドプロンプトだけで,任意のキー操作イベントをエミュレートできる。


サンプルコマンド:

コマンドプロンプトから,画面をキャプチャする。(PrintScreenキーの押下をエミュレート)

mshta vbscript:execute("Set a=CreateObject(""Excel.Application""):a.ExecuteExcel4Macro(""CALL(""""user32"""",""""keybd_event"""",""""JJJJJ"""",44,0,1,0)""):a.ExecuteExcel4Macro(""CALL(""""user32"""",""""keybd_event"""",""""JJJJJ"""",44,0,2,0)""):close()")

これを実行すると,クリップボードに画面のハードコピーが格納される。*1
(ペイントなどに貼り付けして確認してみるとよい)

もちろん,batファイルから実行する事も可能。


「コマンドプロンプトやBATから,DLLを呼び出せる」というのは,非常に面白い。

以下では,このコマンドの動作原理を説明。


(1)VBAからDLLを呼び出す:事前の宣言有りの場合。Declareする

以下のエントリでは,WSH+VBAのそれぞれできちんとしたコードを組む事によって,「コマンドラインで画面キャプチャ」を実現していた。

画面のスクリーンショットを,Excelブック内に自動的に保存するバッチ
http://language-and-engineering.hatenablog.jp/entry/20100425/p1

VBAからは,user32.dllを呼び出して利用していた。(抜粋)

Private Declare Sub keybd_event Lib "user32" (ByVal bVk As Byte, _
    ByVal bScan As Byte, ByVal dwFlags As Long, ByVal dwExtraInfo As Long)

Private Const KEYEVENTF_EXTENDEDKEY As Long = &H1
Private Const KEYEVENTF_KEYUP As Long = &H2
Private Const fKEYDOWN = KEYEVENTF_EXTENDEDKEY
Private Const fKEYUP = KEYEVENTF_EXTENDEDKEY Or KEYEVENTF_KEYUP

....

    keybd_event vbKeySnapshot, 0&, fKEYDOWN, 0&
    keybd_event vbKeySnapshot, 0&, fKEYUP, 0&

user32.dllのkeybd_event関数は,WIN32 APIの一部で,キー押下イベントを発生させる関数。

引数として渡すキーコードを変えれば,OS内の色々な低レベル操作を実行できる。

例えば,「音量の上げ下げ」も実現できる。

keybd_event
http://yokohama.cool.ne.jp/chokuto/ur...

  • キーストロークを合成します。合成されたキーストロークから、 WM_KEYUP や WM_KEYDOWN メッセージを生成します。


API commands to turn volume control on
http://www.pcreview.co.uk/forums/show...

  • 音量の上げ下げをVBAで


なお,こういったdll呼び出しは,VBAだけでなく,本家VBなどの言語でも可能。

キーストロークをシミュレート(押下げ・制御)する(VB6.0)
http://hanatyan.sakura.ne.jp/vbhlp/ke...

しかし,プログラムのソースコードの冒頭に「Declare」でDLL利用宣言をするのは面倒。

ワンライナーが書けるぐらいの手軽さはない。どうしたらよいか。


(2)VBAからDLLを呼び出す:事前の宣言なしの場合。CALL関数を使う

VBAで,事前の宣言なしにDLLを呼び出す方法を考える。

CALL関数」を使えばよい。


CALL関数は,VBAのCALLステートメントとは違う。

  • CALLステートメント:プロシージャを呼び出すための,VBAマクロのステートメント
  • CALL関数:DLL内のプロシージャを呼び出すための,特殊なワークシート関数

Callステートメント
http://officetanaka.net/excel/vba/sta...

  • プロシージャ名の前につければプロシージャを呼び出せる。
  • 省略可能。


CALL 関数 (Microsoftのページ)
http://office.microsoft.com/ja-jp/exc...

  • ワークシート関数だがマクロからしか呼べない,という不思議な関数


Excel97 CALL問題
http://www.trusnet.com/secinfo/docs/s...

  • ExcelのCALL関数はユーザのマシン上で任意のプログラムを起動させることが潜在的に可能
  • CALL関数自体は単なるワークシート関数でありマクロではない
  • セキュリティ上の問題から,ワークシート上では利用不能に修正された


Add-in and Automation functions:CALL function
http://www.likeoffice.com/28057/Excel...


このCALL関数,ワークシート上で利用できないし,マクロでも Application.WorksheetFunction.CALL としても使えないのだが,COM経由でExecuteExcel4macroを実行している時にだけは使える,という何とも不思議な関数。



呼び出し形式は

' 単独使用 (Windows 版 Excel)

CALL(モジュール名,プロシージャ名,タイプ,引数 1,...)
  • モジュール名:目的のプロシージャを含む DLL の名前を、半角の二重引用符 (") で囲んだ文字列として指定
  • プロシージャ名:DLL 内の関数の名前を半角の二重引用符 (") で囲んだ文字列として指定します。名前の代わりにモジュール定義関数ファイル (拡張子は .DEF) 内の EXPORTS ステートメントに記述されている関数の番号を指定することもできます。


CALL関数の「タイプ」という引数について。

CALL 関数と REGISTER 関数の使い方
http://office.microsoft.com/ja-jp/exc...

  • タイプの先頭の文字では、戻り値のデータ型を指定します。残りの文字では、すべての引数のデータ型を指定
  • データ型はコード文字を並べた文字列で表現。例えば「J」は,符号付き 4 バイト整数 (long int)
  • したがって,「"JJJJJ"」の意味は・・・
    • 戻り値のデータ型が符号付き 4 バイト整数であり,
    • 引数が4つあって,それらの型は全部,符号付き 4 バイト整数。

こんなわけで,事前の宣言なしでDLLを呼びたい場合,COM経由であればCALL関数を利用可能。

ただし,利用時にはWin32 APIの各関数の引数や返り値がどんなデータ型であるかを調査し,適切なデータ型文字列を記述する必要がある。


(3)VBAのCALL関数を,WSHやコマンドプロンプトから呼び出す

(2)の処理をWSHから呼び出す事が可能。

Set Excel=CreateObject("Excel.Application")
Excel.ExecuteExcel4Macro "CALL(""user32"",""keybd_event"",""JJJJJ"",44,0,1,0)"
Excel.ExecuteExcel4Macro "CALL(""user32"",""keybd_event"",""JJJJJ"",44,0,3,0)"

画面全体やアクティブウィンドウのスナップショットを取得する。
http://scripting.cocolog-nifty.com/bl...

Excelには、DLL関数を呼び出すCALLマクロと、
マクロを呼び出すExecuteExcel4Macroメソッドがあります。
これを利用して、スクリプトからWin32APIのkeybd_event()を呼び出します。


WSHから利用可能なので,コマンドプロンプトでWSHのワンライナーを書けば,コマンドプロンプトからも呼び出せる。

職場が少し楽しくなるメモ - 07/11/19
http://web.archive.org/web/2008052815...

Excelがインストールされている環境なら、 ExecuteExcel4MacroとCALLを使ってVBスクリプトからWin32APIが利用できる。

    Set Excel=CreateObject("Excel.Application")
    Excel.ExecuteExcel4Macro("CALL(""user32"",""MessageBoxA"",""JJCCJ"","& 0 &",""I'm Script!"",""Hello, API"","& &H40 &")")

CALLの第3引数は変数の型を指定している。詳細は CALL 関数と REGISTER 関数の使い方 を参照のこと。
コマンドプロンプトからAPIを使うこともできるようだ。実に無意味。感動した。

mshta vbscript:execute("Set a=CreateObject(""Excel.Application""):a.ExecuteExcel4Macro(""CALL(""""user32"""",""""MessageBoxA"""",""""JJCCJ"""",0,""""Hello, World"""",""""TEST"""",0)""):close()")


これを使えば・・・

コマンドラインだけでキャプチャ

mshta vbscript:execute("Set a=CreateObject(""Excel.Application""):a.ExecuteExcel4Macro(""CALL(""""user32"""",""""keybd_event"""",""""JJJJJ"""",44,0,1,0)""):a.ExecuteExcel4Macro(""CALL(""""user32"""",""""keybd_event"""",""""JJJJJ"""",44,0,2,0)""):close()")


コマンドプロンプトにしゃべらせる

mshta.exe vbscript:Execute("CreateObject(""SAPI.SpVoice"").Speak ""Hello"":Close")

前述の「音量の上げ下げ」をやってみよう。

下記のキーコード表で調べる。

仮想キーコード
http://yokohama.cool.ne.jp/chokuto/ur...

  • Print Screen (VK_SNAPSHOT) のキーコードは8進数で2Cなので,十進数では44。
  • 音声のミュートの切り替え(VK_VOLUME_MUTE)はADなので,十進数では173。

キーコードを173に書き換えればよいということがわかる。


コマンドラインだけで,ボリュームコントロールのミュートON/OFFを切り替え:

mshta vbscript:execute("Set a=CreateObject(""Excel.Application""):a.ExecuteExcel4Macro(""CALL(""""user32"""",""""keybd_event"""",""""JJJJJ"""",44,0,1,0)""):a.ExecuteExcel4Macro(""CALL(""""user32"""",""""keybd_event"""",""""JJJJJ"""",44,0,2,0)""):close()")


このコマンドは,以下のエントリの補足としても役立つ。

音声を再生するときだけミュートを自動的にCUIで解除できるので。

バッチで,wavなどの音声を再生しよう (コマンドプロンプトから音を鳴らす方法)
http://language-and-engineering.hatenablog.jp/entry/20100729/p1

補足

以前,コマンドプロンプトからWin32 APIや任意のDLLを呼び出すために,わざわざ自作DLLを経由するという方法を執筆した。

コマンドラインからマウスを操作する方法 (rundll32.exeで動くDLLの作成法)
http://language-and-engineering.hatenablog.jp/entry/20081117/1226943698

しかし今回の方法であれば,自作DLLは不要。Excelがインストールされている必要があるが。



関連する記事:

バッチで,画像を生成・加工・一括処理しよう (WSH/JScriptでImageMagickを呼び出す方法)
http://language-and-engineering.hatenablog.jp/entry/20111019/p1


バッチで,レジストリの値の読み取り・書き込み・存在判定をしよう(WSH/JScriptでレジストリ操作のサンプルコード)
http://language-and-engineering.hatenablog.jp/entry/20100908/p1


メモリの中身を読んでみよう (プロセスをダンプ+解析する方法)
http://language-and-engineering.hatenablog.jp/entry/20081019/1224341559

  

*1:Excelがインストールされている必要がある。