コマンドプロンプトのログを取る (その2)

コンソール(CMD.EXE)のログを取る (不完全版) の続きです。

標準出力と標準エラー出力をファイルに書き出せたところまではできてタイムアップ。標準入力が書き出せていませんでした。今回は、入力バッファをファイルに書き出してから、リダイレクトしたパイプハンドルに書き出せば事は足ります。

で、あまりにもソースが汚くなってきたので整理して、テストコードが完成。パイプハンドルをラップした構造体とスレッドに渡すコンテキストを別ファイルにして整理。

/*******************************************************************

 ■コマンドプロンプトのすべての操作をテキストファイルへ書き出します。

  ※ほとんどエラーチェック・処理をしていないテストコード
  ※書き出すファイルは、#define LOGFILE TEXT("~") で指定。

********************************************************************/

#include <windows.h>

//Cランタイム
#include <tchar.h>
#include <stdio.h>

//パイプ、スレッドに渡すパラメータをラップした構造体を定義したヘッダー
#include "pipe.h"
#include "context.h"

#define CLOSEHANDLE(X) {if(X)CloseHandle(X);}
#define BUFFER_SIZE 1024
#define SPIN_COUNT 4000

//書き出すファイル名の指定
#define LOGFILE TEXT("cmdlog.txt")

//標準入出力・ファイルハンドルへの読取と書込スレッド
DWORD WINAPI ReadAndWriteProc(LPVOID lpParameter)
{
  Context *context = (Context*)lpParameter;

  TCHAR pBuffer[BUFFER_SIZE] = {0};
  DWORD dwRead = 0,dwWrite = 0;
  BOOL bRet = FALSE;
  while(!(context->bThreadMustTerminate))
    {
      do
        {
          bRet = ReadFile(context->hRead,(PVOID)pBuffer,BUFFER_SIZE,&dwRead,NULL);
          if(dwRead > 0)
            {
              EnterCriticalSection(&(context->CriticalSection));
              if(context->hFile)
                WriteFile(context->hFile,(PVOID)pBuffer,dwRead,&dwWrite,NULL);
              if(context->hWrite)
                WriteFile(context->hWrite,(PVOID)pBuffer,dwRead,&dwWrite,NULL);
              LeaveCriticalSection(&(context->CriticalSection));
            }
        }
      while(bRet == TRUE && dwRead > 0);
    }

  return 0;
}

//ファイルを書込用にオープン(同期)
HANDLE OpenLogFile(PCTSTR szLogName)
{
  return CreateFile(szLogName,
                    GENERIC_WRITE,
                    0,
                    NULL,
                    CREATE_ALWAYS,
                    FILE_ATTRIBUTE_NORMAL,
                    NULL);
}

//環境変数COMSPECからコマンドプロンプトのパスを得て起動
BOOL CreateCommandPrompt(STARTUPINFO& si,PROCESS_INFORMATION& pi)
{
  BOOL bRetVal = FALSE;

  //CMD.EXEのパスを格納するバッファ
  TCHAR szCmdPath[MAX_PATH+1] = {0};

  //CMD.EXEのパスを環境(システム)変数から得る。
  if(0 == GetEnvironmentVariable(TEXT("COMSPEC"),szCmdPath,MAX_PATH+1))
    goto cleanup;
  
  bRetVal = CreateProcess(NULL,
                          szCmdPath,
                          NULL,
                          NULL,
                          TRUE,
                          NORMAL_PRIORITY_CLASS,
                          NULL,
                          NULL,
                          &si,
                          &pi);

cleanup:
  return bRetVal;
}

//スタートアップ
int _tmain(int argc,_TCHAR **argv)
{
  UNREFERENCED_PARAMETER(argc);
  UNREFERENCED_PARAMETER(argv);
  
  /*------------------------------------------------------------
    スレッドハンドル、パイプ、スレッドに渡すコンテキスト
    配列はそれぞれ、0:標準入力用 1:標準出力用 2:標準エラー出力用
  --------------------------------------------------------------*/
  HANDLE hThreads[3] = {NULL};
  Pipe Std[3] = {PIPE_READ_INHERIT,PIPE_WRITE_INHERIT,PIPE_WRITE_INHERIT};
  Context ctx[3];

  //ログを書き込むファイル
  HANDLE hFile = NULL;
  
  //CreateProcessに必要な構造体
  STARTUPINFO si = {sizeof(STARTUPINFO)};
  PROCESS_INFORMATION pi = {NULL};

  //クリティカルセクション初期化
  InitializeCriticalSectionAndSpinCount(&Context::CriticalSection,SPIN_COUNT);

  if((hFile = OpenLogFile(LOGFILE)) == INVALID_HANDLE_VALUE)
    {
      _tprintf(TEXT("ログファイルのオープンが失敗しました"));
      goto cleanup;
    }

  //入出力ハンドルをリダイレクトしたコマンドプロンプトを生成
  si.dwFlags    = STARTF_USESTDHANDLES;
  si.hStdInput  = Std[0].hRead;   //パイプにリダイレクト
  si.hStdOutput = Std[1].hWrite; //パイプにリダイレクト
  si.hStdError  = Std[2].hWrite; //パイプにリダイレクト

  if(FALSE == CreateCommandPrompt(si,pi))
    {	
      _tprintf(TEXT("CreateProcess 失敗 : %d"),GetLastError());
      goto cleanup;
    }

  //コンテキスト構造体のメンバをセット
  ctx[0].Set(GetStdHandle(STD_INPUT_HANDLE),Std[0].hWrite);
  ctx[1].Set(Std[1].hRead,GetStdHandle(STD_OUTPUT_HANDLE),hFile);
  ctx[2].Set(Std[2].hRead,GetStdHandle(STD_ERROR_HANDLE),hFile);
  
  //スレッド作成
  for(int i=0;i<3;i++)
    hThreads[i] = CreateThread(NULL,0,ReadAndWriteProc,&ctx[i],0,NULL);
  
  //新しいコマンドプロンプトが終了するまで待機
  WaitForSingleObject(pi.hProcess,INFINITE);
  
  //スレッド終了通知をセット
  Context::bThreadMustTerminate = TRUE;

  //パイプハンドルのクローズ
  for(int i=0;i<3;i++)
    Std[i].Close();

  CloseHandle(GetStdHandle(STD_INPUT_HANDLE));

  //起動したスレッドが終了するまで待機
  WaitForMultipleObjects(3,hThreads,TRUE,INFINITE);

  //クリティカルセクションオブジェクトを削除
  DeleteCriticalSection(&Context::CriticalSection);

cleanup:
  //ハンドルを閉じる
  CLOSEHANDLE(hFile);
  CLOSEHANDLE(pi.hThread);
  
  for(int i=0;i<3;i++)
    CLOSEHANDLE(hThreads[i]);
  
  CLOSEHANDLE(pi.hProcess);

  return 0;
}

すべてのコードを固めたものはこちら。
nmakeでcmdlog.exeが生成されます。

ただ、これは CTRL-Cとかすると・・・コマンドプロンプト自体がガサッと終了してしまいますし、入力した内容がエコーされるし(ファイルには出力されないので別に良いのですが・・・)、まだまだ不完全。
書き出すファイルパスはハードコードしとるし、エラー処理していないし、ファイルの書き出しは同期処理なので、遅いデバイスを指定すると(今更ないと思うけどフロッピーとか)書出処理から制御が戻ってこないので非常にマズイことになってます・・・。

また暇なときに完成度を高めたい・・・といっててそのままでずっと使うことが多いんですが(ーー;


コンソール(CMD.EXE)のログを取る (不完全版)
コマンドプロンプトのログを取る(その2)
コマンドプロンプトのログを取る(その3)
コマンドプロンプトのログを取る(完結)

まだやってんのか???

土曜日にケーブルTVの宅内点検とかいうのが来る、と親が言ってた。

宅内点検??? JCOMはまだそんな「点検商法」をやってんのか?

あいにく土曜は一日出かける予定があるので・・・親に、何を言われても、契約はするな、と釘を刺したのだが・・・なんか不安。前に一度、おいらがいないときに点検が来て、ネットから電話からすべて契約させられたそうになったので・・・。

ケーブル引いてる方はご用心を!宅内点検なんて基本拒否で問題無し。
 

iPod Touchに対応したradiko公式アプリがやっと出た。GPSがないTouchでもPCと同様に無線LAN経由でラジオが聴けるようになりました! 野球中継聴きながらWeb巡回できます(^^

iPod と言えば、今日発売ですね、iPad。会社さぼって行列に並んだ人多いんじゃないかな?
夕方のローカルニュースでどの局も報道してました。でもやっぱりiPadでかい・・・。iPhone二つぶんくらいの大きさだったら絶対買ってだろうなぁ~。通勤途中に電車の中で使ったら注目度ナンバーワンです。いろんな意味で(笑)

着いた! 早!

朝会社に行くと、机に箱が置いてあって、何かな? と開けてみると・・・先日注文したIllustorator CS5でした。。。Illustrator CS5、28日発売だったはずなのだが・・・?う~ん・・・。お金下ろしてこないと・・・。

いつも思うんだけど、パッケージソフトウェアの外箱って必要ないんじゃないかなぁ~・・・。薄型のCD・DVDケースとかでも十分だと思うんだけど・・・。

Illustrator CS5のパッケージ

アップグレード版のインストーラーって、なんで前のバージョンを削除してくんないでしょうねぇ・・・?CS5をインストールした後、CS2をアンインストールしたらCS5の関連付けが解除されて・・・またCS5をインストールし直し。なんとかならんの・・・?

というわけで、

Illustrator CS5


Win7+CS2より、かなり使いやすくなりました(^^;
 

Adobeといえば・・・

Appleと仲が良い会社というイメージが未だにあるんだけど、ちょっと前のアドビ vs アップル のFlashに関する応酬はで、実はそうじゃないんだな、と思いました。コカコーラvsペプシみたいな日本では到底起こりえない、アメリカの典型的な企業間闘争でしょうか(笑) これに、Googleが絡んできているので、もうメチャクチャ。な感じです。

最近、やたらとHTML5というキーワードが情報系のニュース記事で頻繁に見かけるようになってきました。HTMLは、HTML4以降主に二つの系列に分かれてきています。
一つは、XHTML。XHTMLは、HTMLをXMLのサブセットとするような感じです。もう一つは、このHTML5です。HTML5はよりWebアプリのため、と言ってもいいようなぐらいですが、まだ標準化作業のまっただ中です。

なぜこれほどまでに、HTML5が重要なんだろうか? と考えたとき、行き着く先は「金になる」からなんだと思います。

iPhoneアプリにしろ、アンドロイドアプリにしろ、ほとんどのアプリは、ネット環境前提のWebアプリで、その実態はHTML文とJavascriptのようなスクリプトで駆動するタイプかなと思います。

パソコンにインストールしてあるブラウザでウェブサイトを見る、というスタイルは、この先主流ではなくなる、これは間違いないでしょう。ウェブサイトを見るためだけにパソコンを立ち上げるなんて、ナンセンスですよ、今の時代。僕自身、家の無線LAN経由でiPod Touchを使って見ることの方が圧倒的に多いです。iPadを代表とする、5~10インチ端末の出現で、この流れは加速するはずです。

携帯端末に自由にHTMLとJavascriptを転送できて、それらをその携帯端末で見ることが出来れば便利なのにな・・・と常々思ってました。そこんところを制限して金儲けにしているのが、現在のアプリストア。そのアプリは、端末に搭載されているブラウザで駆動するHTML5とスクリプト。

つまり、極端なこというと、HTML5とJavascriptで組んだ、単なる写真のスライドショーであっても、もし売れればお金になる、ということ。売れるか売れないかは別にして、もっと極端なことを言うと「文字を表示するだけ」のHTMLファイルでもWebアプリだ!と言い張っても通るわけ。

以前ならHTMLとスクリプトでは1円にもならなかったものが、携帯端末から自由を奪う(制限をかける)ことで、コンテンツの保護と金儲けを実現させた、このモデルを考え出して実現した人たちって、頭がいいんでしょうねぇ。
(学歴とか偏差値とかいう意味じゃないですよ、もちろんビジネス的な頭の良さ。)

まぁ、商売人にとってHTML5は「金儲け」のツールでしかないんでしょうねぇ。

コンソール(CMD.EXE)のログを取る (不完全版)

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

雨ですねぇ

GW中に出かけたきり、週末はほとんど部屋にヒキコモリです。
仕事で使うスクリプトやら個人的に使うプログラムやら、会社で仕事中にできなかったコーディングをシコシコ夜遅くまで書いて、気づいたら午前3時過ぎとか・・・。

PC用のスピーカー、右から音が出なくなってしまいました。友人からもらったもので、もうかれこれ十年ぐらい使っているのですが、そろそろ買い替えたいなぁ~、と思ってた矢先だったんで、家電量販店に買いに、今日行ってきました。外は暴風雨です(笑)

で・・・とりあえず値段は高くても良いものを買っておこう・・・と思い見て回ってましたら・・・

BOSE M2

に行き着きました(^^;;; 小さくて音も良さそうです。
しかし・・・値段が・・・約4万・・・・。うーーーーーーん・・・・。
IllustratorCS5のアップグレード版を会社で注文しちゃったしなぁ・・・とりあえず・・・ネットでよく調べてから、と思い、衝動買いは阻止(^^;;;

さて・・・どうしましょうか・・・。

このままだと音楽聴けないので、音が鳴らないスピーカーを分解すると、接点が錆び付いて断線しかかっていた模様。ハンダごてとかないので、適当に線を巻き付けてテープで固定・・・。取り敢えず復旧。

今は・・・M2を、買うべきか、今のままで我慢するか・・・悩んでます。
悩んでる内容が小さいですな~(ーー;;; つくづく小さい人間ですな・・・・。