RCIE-ジャンクのコード屋

主に自分のためにコーディングのTIPSを蓄積しています。

【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 で標準入力に流し込んでやる必要がある。