コマンドプロンプト(CMD.EXE)の操作や出力ログを全部取りたい、と思っては見たものの、Windows標準の機能だけでは完全なログを取ることができません。リダイレクトしても入力したログが記録されないし、何より、コンソール画面に出力されないので入力しつつ、出力も表示しながら、かつすべてのログをとる、ということが標準でできません。
確かUnix系のOSなら、teeコマンドというものでできるらしいのですが・・・Windowsにはありません。そういう需要がWindowsには少ないのか、ネットで探すと、VBSスクリプトで実装しているもの(Tee.vbs)もありますが・・・。
これが Cygwin であれば、TeratermやPedorosaのようなターミナルエミュレータでCygwinに接続することですべてのログを取ることが可能ですが、残念ながらこれらのターミナルからCMD.EXEには接続できません。
というわけで、C++で強引に作ってみよう、というわけで、とりあえず動作チェック・テスト用コードを書いてみました。
仕組み自体は、CreateProcessで標準入出力をリダイレクトしたCMD.EXEのプロセスを作って、匿名パイプで出力を取得してファイルと画面に出力する、という至って普通のやり方です。
なんとか強引にコーディングして、標準出力と標準エラー出力はファイルに落とすことはできましたが、まだ入力のログがとれてません。また来週にでも完全版を書きたいと思ってます。。。
/****************************************************************
■CMD.EXE のログをとる不完全版テストコード
(入力がログに落ちません)
****************************************************************/
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#define CLOSEHANDLE(X) {if(X)CloseHandle(X);}
#define SAFEDELETE(X) {if(X)delete (X);}
#define BUFFER_SIZE 1024
#define SPIN_COUNT 4000
#define LOGFILE_PATH TEXT("cmdlog.log")
//匿名パイプをラップしたもの
struct PipeHandle
{
HANDLE hRead;
HANDLE hWrite;
SECURITY_ATTRIBUTES sa;
PipeHandle() : hRead(NULL),hWrite(NULL)
{
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
::CreatePipe(&hRead,&hWrite,&sa,0);
}
~PipeHandle()
{
CLOSEHANDLE(hWrite);
CLOSEHANDLE(hRead);
}
};
//スレッド関数に各種変数を渡すために使用するコンテキスト
struct Context
{
PipeHandle *lpStdHandle;
HANDLE hFile;
HANDLE hStdio;
LPCRITICAL_SECTION lpCriticalSection;
PBOOL pThreadMustTerminate;
Context(LPCRITICAL_SECTION lpcs,PBOOL ptht)
: lpCriticalSection(lpcs),pThreadMustTerminate(ptht)
{}
};
//パイプから出力を読み取るスレッド関数
DWORD WINAPI ReadOutputProc(LPVOID lpParameter)
{
Context *context = (Context*)lpParameter;
TCHAR pBuffer[BUFFER_SIZE] = {0};
DWORD dwRead = 0,dwWrite = 0;
while(!(*context->pThreadMustTerminate))
{
BOOL b = FALSE;
DWORD dwReadAvail = 0;
do
{
b = ReadFile(context->lpStdHandle->hRead,(PVOID)pBuffer,BUFFER_SIZE,&dwRead,NULL);
if(dwRead > 0)
{
EnterCriticalSection(context->lpCriticalSection);
WriteFile(context->hFile,(PVOID)pBuffer,dwRead,&dwWrite,NULL);
WriteFile(context->hStdio,(PVOID)pBuffer,dwRead,&dwWrite,NULL);
LeaveCriticalSection(context->lpCriticalSection);
}
if(*context->pThreadMustTerminate == TRUE)
break;
}
while(b == TRUE && dwRead > 0);
}
return 0;
}
//スタートアップ
int _tmain(int argc,_TCHAR **argv)
{
//CMD.EXEのパスを格納するバッファ
TCHAR szCmdPath[MAX_PATH+1] = {0};
//ログを書き込むファイル
HANDLE hFile = NULL;
//CreateProcessに必要な構造体
STARTUPINFO si;
PROCESS_INFORMATION pi;
//パイプ
PipeHandle *ppStdOut = new PipeHandle();
PipeHandle *ppStdErr = new PipeHandle();
//スレッドの同期処理に必要
CRITICAL_SECTION cs;
BOOL bThreadMustTerminate = FALSE;
//標準入力コンソールハンドル
HANDLE hStdInput = GetStdHandle(STD_INPUT_HANDLE);
//スレッドに渡すコンテキスト
Context ctxOut(&cs,&bThreadMustTerminate);
Context ctxErr(&cs,&bThreadMustTerminate);
//クリティカルセクションオブジェクト初期化
InitializeCriticalSectionAndSpinCount(&cs,SPIN_COUNT);
//CMD.EXEのパスを環境(システム)変数から得る。
if(0 == GetEnvironmentVariable(TEXT("COMSPEC"),szCmdPath,MAX_PATH+1))
{
_tprintf(TEXT("環境変数 COMSPEC 取得失敗\n"));
return 0;
}
//ログファイルをオープン
if((hFile = CreateFile(LOGFILE_PATH,
GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL)) == INVALID_HANDLE_VALUE)
{
_tprintf(TEXT("CreateFile が失敗しました"));
goto cleanup;
}
// CMD.EXEのプロセスを生成
ZeroMemory((LPVOID)&si,sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdInput = hStdInput;
si.hStdOutput = ppStdOut->hWrite; //パイプにリダイレクト
si.hStdError = ppStdErr->hWrite; //パイプにリダイレクト
if(FALSE == CreateProcess(NULL,
szCmdPath,
NULL,
NULL,
TRUE,
NORMAL_PRIORITY_CLASS,
NULL,
NULL,
&si,
&pi))
{
_tprintf(TEXT("CreateProcess 失敗 : %d"),GetLastError());
goto cleanup;
}
//スレッドに渡すコンテキストのメンバをセット
ctxOut.lpStdHandle = ppStdOut;
ctxOut.hFile = hFile;
ctxOut.hStdio = GetStdHandle(STD_OUTPUT_HANDLE);
ctxErr.lpStdHandle = ppStdErr;
ctxErr.hFile = hFile;
ctxErr.hStdio = GetStdHandle(STD_ERROR_HANDLE);
/* スレッド作成 */
HANDLE hThread1 = CreateThread(NULL,0,ReadOutputProc,&ctxOut,0,NULL);
HANDLE hThread2 = CreateThread(NULL,0,ReadOutputProc,&ctxErr,0,NULL);
//CMDプロセス終了まで待機
WaitForSingleObject(pi.hProcess,INFINITE);
//スレッド終了通知
bThreadMustTerminate = TRUE;
//パイプの削除
SAFEDELETE(ppStdOut);
SAFEDELETE(ppStdErr);
//クリティカルセクションオブジェクトを削除
DeleteCriticalSection(&cs);
cleanup:
//ハンドルを閉じる
CLOSEHANDLE(hFile);
CLOSEHANDLE(pi.hThread);
CLOSEHANDLE(pi.hProcess);
CLOSEHANDLE(hThread1);
CLOSEHANDLE(hThread2);
return 0;
}
なんか・・・汚いコードだな、相変わらず・・・。
これだけなら、
CMD.EXE 2>&1 | xxxxxx
と変わらないなぁ・・・。
コマンドプロンプトのログを取る(完結)
コマンドプロンプトのログを取る(その3)
コマンドプロンプトのログを取る (その2)