ふにゃるんv2

もとは、http://d.hatena.ne.jp/Wacky/

コマンドプロンプトの出力を、ファイルに保存する(その2)

むか〜し、コマンドプロンプトの出力を、ファイルに保存するってネタを書いた事がありました。


先日、標準入出力をリダイレクトするやり方を、もう一度おさらいしていたら、MSKBのコードを一部いじるだけで、所望の機能を実現できる事に気づきました。

あちゃ〜、何で今まで気づかなかったんだろう?ってな感じです。
欲しいモノは、実は目の前にあったとは。(これも 青い鳥 の一種かな)


という訳で、MSKBのコードを改造して、標準入出力をファイルに保存できるようにしたコードを、以下に貼り付けておきます。

/*
以下の改造版。標準入出力を指定したログファイル(argv[1])に保存します

コンソール プロセスを生成して標準ハンドルをリダイレクトする方法
http://support.microsoft.com/kb/190351/ja
*/
/*++

Copyright (c) 1998  Microsoft Corporation

Module Name:

Redirect.c

Description:
This sample illustrates how to spawn a child console based
application with redirected standard handles.

The following import libraries are required:
user32.lib

Dave McPherson (davemm)   11-March-98

--*/ 

#include<windows.h>
#include<stdio.h>
#pragma comment(lib, "User32.lib")
void DisplayError(char *pszAPI);
void ReadAndHandleOutput(HANDLE hPipeRead);
void PrepAndLaunchRedirectedChild(HANDLE hChildStdOut,
								  HANDLE hChildStdIn,
								  HANDLE hChildStdErr);
DWORD WINAPI GetAndSendInputThread(LPVOID lpvThreadParam);

HANDLE hChildProcess = NULL;
HANDLE hStdIn = NULL; // Handle to parents std input.
BOOL bRunThread = TRUE;
HANDLE	g_hLogFile = NULL;


void main (int argc, char* argv[])
{
	HANDLE hOutputReadTmp,hOutputRead,hOutputWrite;
	HANDLE hInputWriteTmp,hInputRead,hInputWrite;
	HANDLE hErrorWrite;
	HANDLE hThread;
	DWORD ThreadId;
	SECURITY_ATTRIBUTES sa;


	// Set up the security attributes struct.
	sa.nLength= sizeof(SECURITY_ATTRIBUTES);
	sa.lpSecurityDescriptor = NULL;
	sa.bInheritHandle = TRUE;


	// Create the child output pipe.
	if (!CreatePipe(&hOutputReadTmp,&hOutputWrite,&sa,0))
		DisplayError("CreatePipe");


	// Create a duplicate of the output write handle for the std error
	// write handle. This is necessary in case the child application
	// closes one of its std output handles.
	if (!DuplicateHandle(GetCurrentProcess(),hOutputWrite,
		GetCurrentProcess(),&hErrorWrite,0,
		TRUE,DUPLICATE_SAME_ACCESS))
		DisplayError("DuplicateHandle");


	// Create the child input pipe.
	if (!CreatePipe(&hInputRead,&hInputWriteTmp,&sa,0))
		DisplayError("CreatePipe");


	// Create new output read handle and the input write handles. Set
	// the Properties to FALSE. Otherwise, the child inherits the
	// properties and, as a result, non-closeable handles to the pipes
	// are created.
	if (!DuplicateHandle(GetCurrentProcess(),hOutputReadTmp,
		GetCurrentProcess(),
		&hOutputRead, // Address of new handle.
		0,FALSE, // Make it uninheritable.
		DUPLICATE_SAME_ACCESS))
		DisplayError("DupliateHandle");

	if (!DuplicateHandle(GetCurrentProcess(),hInputWriteTmp,
		GetCurrentProcess(),
		&hInputWrite, // Address of new handle.
		0,FALSE, // Make it uninheritable.
		DUPLICATE_SAME_ACCESS))
		DisplayError("DupliateHandle");


	// Close inheritable copies of the handles you do not want to be
	// inherited.
	if (!CloseHandle(hOutputReadTmp)) DisplayError("CloseHandle");
	if (!CloseHandle(hInputWriteTmp)) DisplayError("CloseHandle");


	// Get std input handle so you can close it and force the ReadFile to
	// fail when you want the input thread to exit.
	if ( (hStdIn = GetStdHandle(STD_INPUT_HANDLE)) ==
		INVALID_HANDLE_VALUE )
		DisplayError("GetStdHandle");

	// ログファイルを作成する
	fprintf(stderr, "$$$ 標準入出力のフックを開始します(exitで終了)\n");
	if(argc > 1){
		const char*	log_file = argv[1];
		printf("arg[1] = %s\n", log_file);
		g_hLogFile = ::CreateFile(log_file, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
		if(g_hLogFile == INVALID_HANDLE_VALUE) DisplayError("fail log file");
		fprintf(stderr, "$$$ 標準入出力を%sに保存開始しました\n", log_file);
	}

	PrepAndLaunchRedirectedChild(hOutputWrite,hInputRead,hErrorWrite);


	// Close pipe handles (do not continue to modify the parent).
	// You need to make sure that no handles to the write end of the
	// output pipe are maintained in this process or else the pipe will
	// not close when the child process exits and the ReadFile will hang.
	if (!CloseHandle(hOutputWrite)) DisplayError("CloseHandle");
	if (!CloseHandle(hInputRead )) DisplayError("CloseHandle");
	if (!CloseHandle(hErrorWrite)) DisplayError("CloseHandle");


	// Launch the thread that gets the input and sends it to the child.
	hThread = CreateThread(NULL,0,GetAndSendInputThread,
		(LPVOID)hInputWrite,0,&ThreadId);
	if (hThread == NULL) DisplayError("CreateThread");


	// Read the child's output.
	ReadAndHandleOutput(hOutputRead);
	// Redirection is complete


	// Force the read on the input to return by closing the stdin handle.
	if (!CloseHandle(hStdIn)) DisplayError("CloseHandle");


	// Tell the thread to exit and wait for thread to die.
	bRunThread = FALSE;

	if (WaitForSingleObject(hThread,INFINITE) == WAIT_FAILED)
		DisplayError("WaitForSingleObject");

	if (!CloseHandle(hOutputRead)) DisplayError("CloseHandle");
	if (!CloseHandle(hInputWrite)) DisplayError("CloseHandle");
	if(g_hLogFile != NULL){
		if (!CloseHandle(g_hLogFile)) DisplayError("CloseHandle");
		fprintf(stderr, "$$$ 標準入出力のログファイル保存が終了しました\n");
	}
	fprintf(stderr, "$$$ bye,bye\n");
}


/////////////////////////////////////////////////////////////////////// 
// PrepAndLaunchRedirectedChild
// Sets up STARTUPINFO structure, and launches redirected child.
/////////////////////////////////////////////////////////////////////// 
void PrepAndLaunchRedirectedChild(HANDLE hChildStdOut,
								  HANDLE hChildStdIn,
								  HANDLE hChildStdErr)
{
	PROCESS_INFORMATION pi;
	STARTUPINFO si;

	// Set up the start up info struct.
	ZeroMemory(&si,sizeof(STARTUPINFO));
	si.cb = sizeof(STARTUPINFO);
	si.dwFlags = STARTF_USESTDHANDLES;
	si.hStdOutput = hChildStdOut;
	si.hStdInput  = hChildStdIn;
	si.hStdError  = hChildStdErr;
	// Use this if you want to hide the child:
	//     si.wShowWindow = SW_HIDE;
	// Note that dwFlags must include STARTF_USESHOWWINDOW if you want to
	// use the wShowWindow flags.


	// Launch the process that you want to redirect (in this case,
	// Child.exe). Make sure Child.exe is in the same directory as
	// redirect.c launch redirect from a command line to prevent location
	// confusion.
	char	cmd_buff[1024] = "child.exe";
	if(!GetEnvironmentVariable("COMSPEC", cmd_buff, 32768)) strcpy_s(cmd_buff, sizeof(cmd_buff) / sizeof(cmd_buff[0]), "cmd.exe");
	if (!CreateProcess(NULL,cmd_buff,NULL,NULL,TRUE,
		CREATE_NO_WINDOW,NULL,NULL,&si,&pi))
		DisplayError("CreateProcess");


	// Set global child process handle to cause threads to exit.
	hChildProcess = pi.hProcess;


	// Close any unnecessary handles.
	if (!CloseHandle(pi.hThread)) DisplayError("CloseHandle");
}


/////////////////////////////////////////////////////////////////////// 
// ReadAndHandleOutput
// Monitors handle for input. Exits when child exits or pipe breaks.
/////////////////////////////////////////////////////////////////////// 
void ReadAndHandleOutput(HANDLE hPipeRead)
{
	CHAR lpBuffer[256];
	DWORD nBytesRead;
	DWORD nCharsWritten;

	while(TRUE)
	{
		if (!ReadFile(hPipeRead,lpBuffer,sizeof(lpBuffer),
			&nBytesRead,NULL) || !nBytesRead)
		{
			if (GetLastError() == ERROR_BROKEN_PIPE)
				break; // pipe done - normal exit path.
			else
				DisplayError("ReadFile"); // Something bad happened.
		}

		// Display the character read on the screen.
		if (!WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE),lpBuffer,
			nBytesRead,&nCharsWritten,NULL))
			DisplayError("WriteConsole");

		// save log
		if(g_hLogFile != NULL){
			::WriteFile(g_hLogFile, lpBuffer, nBytesRead, &nCharsWritten, NULL);
		}
	}
}


/////////////////////////////////////////////////////////////////////// 
// GetAndSendInputThread
// Thread procedure that monitors the console for input and sends input
// to the child process through the input pipe.
// This thread ends when the child application exits.
/////////////////////////////////////////////////////////////////////// 
DWORD WINAPI GetAndSendInputThread(LPVOID lpvThreadParam)
{
	CHAR read_buff[256];
	DWORD nBytesRead,nBytesWrote;
	HANDLE hPipeWrite = (HANDLE)lpvThreadParam;

	// Get input from our console and send it to child through the pipe.
	while (bRunThread)
	{
		if(!ReadConsole(hStdIn,read_buff,1,&nBytesRead,NULL))
			DisplayError("ReadConsole");

		read_buff[nBytesRead] = '\0'; // Follow input with a NULL.

		if (!WriteFile(hPipeWrite,read_buff,nBytesRead,&nBytesWrote,NULL))
		{
			if (GetLastError() == ERROR_NO_DATA)
				break; // Pipe was closed (normal exit path).
			else
				DisplayError("WriteFile");
		}
	}

	return 1;
}


/////////////////////////////////////////////////////////////////////// 
// DisplayError
// Displays the error number and corresponding message.
/////////////////////////////////////////////////////////////////////// 
void DisplayError(char *pszAPI)
{
	LPVOID lpvMessageBuffer;
	CHAR szPrintBuffer[512];
	DWORD nCharsWritten;

	FormatMessage(
		FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
		NULL, GetLastError(),
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		(LPTSTR)&lpvMessageBuffer, 0, NULL);

	wsprintf(szPrintBuffer,
		"ERROR: API    = %s.\n   error code = %d.\n   message    = %s.\n",
		pszAPI, GetLastError(), (char *)lpvMessageBuffer);

	WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE),szPrintBuffer,
		lstrlen(szPrintBuffer),&nCharsWritten,NULL);

	LocalFree(lpvMessageBuffer);
	ExitProcess(GetLastError());
}


ちなみに、以下のやり方で改造しました。

  1. VC2008を起動
  2. 新しいプロジェクトの追加>Visual C++>Win32コンソールアプリケーションを選択
  3. アプリケーションの設定では、
     コンソールアプリケーション
     空のプロジェクト
    を選択
  4. プロジェクトが出来たら、CPPのファイルを新規に作成
  5. MSKBのコードを貼り付ける
  6. CTRL+Aでコード全体を選択
  7. 編集>詳細>選択範囲のフォーマットを選択し、ソースコードを整頓
  8. プロジェクトの設定で、構成プロパティ>全般>文字セットを「マルチバイト文字セットを使用する」に変更
  9. ビルド出来る事を確認
  10. 後は、改造しまくる

一昔前は、テキストエディタでごしごしやっていたんですが、最近IDEのコード補完を使っている自分に、ちょっと苦笑。
昔、使わなかったのは、IDEが遅い、スケルトンから作成に入らないとコード作成できない、等の制約があったからなんですが、最近のIDEは少々の無理を聞いてくれるようになったのが嬉しいですね。