コマンドプロンプト(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)