【VBA】外部プログラムの実行 標準入出力あり(WScript.Shell / Exec / StdIn / StdOut)
やりたいこと
VBAから外部プログラムを呼び出して、その出力をVBAで利用したい。
ただし、その外部プログラムは対話型※ なので、標準入力からいろいろ入力したい。
※対話型:プログラムがユーザーに質問をする。ユーザーが答えると処理を継続する。
例えばこういう状況
実行しているコンピュータのメモリ容量を知りたい としよう。
コマンドプロンプト(cmd.exe)を起動して「SYSTEMINFO」と入力すればメモリ容量がわかる。
システム ディレクトリ: C:\WINDOWS\system32
起動デバイス: \Device\HarddiskVolume1
システム ロケール: ja;日本語
入力ロケール: ja;日本語
タイム ゾーン: (UTC+09:00) 大阪、札幌、東京
物理メモリの合計: 15,789 MB
利用できる物理メモリ: 8,896 MB
仮想メモリ: 最大サイズ: 18,221 MB
仮想メモリ: 利用可能: 9,311 MB
仮想メモリ: 使用中: 8,910 MB
つまり、以下の処理を行えばよい。
- cmd.exe を起動する
- 「SYSTEMINFO」と標準入力から打ち込む
- システム情報が表示されるまでちょっと待つ
- 「EXIT」を入力して終了する
- 得られた標準出力から、メモリ容量のところを抜き出す
- ダイアログボックスに表示する
これをVBAで実現してみよう。
コード
Option Explicit '外部プログラムを実行する Dim oExec With CreateObject("WScript.Shell") Set oExec = .Exec("cmd.exe") End With '標準入力に以下の入力を積んでおく oExec.StdIn.WriteLine("SYSTEMINFO") oExec.StdIn.WriteLine("EXIT") 'cmd.exe はこれがないと終了しない '標準出力をすべて取得し、改行で区切る Dim sOut Do Until oExec.StdOut.AtEndOfStream sOut = sOut & oExec.StdOut.ReadLine() & vbCrLf Loop '物理メモリの合計が書かれている行を探す Dim sEach Dim sMemory For Each sEach In Split(sOut, vbCrLf) If InStr(sEach, "物理メモリの合計:") = 1 Then sMemory = sEach End If Next 'ダイアログボックスに物理メモリの合計を表示 MsgBox sMemory 'ついでに標準出力をファイルに書き込む 'テキストファイルを開く … モード=書き込み、ファイル形式=規定値 With CreateObject("Scripting.FileSystemObject").OpenTextFile("StdOut.txt", 2, -2) '書いて閉じる .Write(sOut) .Close End With
解説
Dim oExec With CreateObject("WScript.Shell") Set oExec = .Exec("cmd.exe") End With
外部プログラムとして、コマンドプロンプト(cmd.exe)を実行する。
.Exec では非同期実行をするので、cmd.exe の終了を待たずに次の行を実行する。
oExec.StdIn.WriteLine("SYSTEMINFO") oExec.StdIn.WriteLine("EXIT")
oExec.StdIn.WriteLine を実行すると、標準入力に文字列を流し込める。
コマンドプロンプトで、「SYSTEMINFO(Enter)」と「EXIT(Enter)」をキー入力したのと同じ効果が得られる。
Dim sOut Do Until oExec.StdOut.AtEndOfStream sOut = sOut & oExec.StdOut.ReadLine & vbCrLf Loop
プログラムが終了すると、oExec.StdOut.AtEndOfStream は True になる。
標準出力は、oExec.StdOut.ReadLine で1行ずつ取得することができる。
もし、コンソールが入力待ちになっている時に AtEndOfStream や ReadLine を実行すると、そこから先は処理がブロックされて停止してしまう。(今回の罠)
したがって、入力待ちにならないように、外部プログラム(cmd.exe)が終了するまで oExec.StdIn.WriteLine で標準入力に流し込んでやる必要がある。