Windows Scripting Host 5.6
Written 11/25/01
1. WshScriptExec
スクリプトからコマンドを実行するには以前は Run メソッドを使っていましたが、 これには色々とよろしくないところがありました。
これからは Exec メソッドを使うとよいことが多くなると思います。
    oExec = WShell.Exec(strCommand)
 
WShellRun メソッドと同じく WshShell オブジェクトです。 返り値が WshScriptExec オブジェクトです。
Run メソッドのときと同様の例を挙げてみましょう。
  scripts/startpage3.js (ファイルに保存にしてください)
  ----------------------------------------------------------------
  var WShell = WScript.CreateObject("WScript.Shell");
  var oExec = WShell.Exec("start http://member.nifty.ne.jp/aya/");
 
Run メソッドのときとどう違うかというと、 これを cscript で実行したときに、新たに DOS プロンプトが立ち上がりません。 すなわち、cscript を実行した DOS プロンプト内で実行されます。 このほうが気分がいいでしょう?
あと、Run メソッドのときはなぜか実行できなかった lha32 もうまくいくようになりました。
  scripts/lha.js (ファイルに保存にしてください)
  ----------------------------------------------------------------
  var WShell = WScript.CreateObject("WScript.Shell");
  WShell.Exec("lha32 a js.lzh *.js");
 
でも、標準出力とかは表示されないんですね。
Status
WshScriptExec オブジェクトは実行したプログラムに対して いくつかのアクセス方法を与えてくれます。
Status プロパティは実行したプログラムが実行中どうかを 示します。
    0 : 実行中
    1 : 終了
 
プログラムは基本的に非同期で実行されるようですが、 プログラムの実行終了を判定するには次のようにします。
  scripts/calc.js (ファイルに保存にしてください)
  ---------------------------------------------------
  var WShell = WScript.CreateObject("WScript.Shell");
  var oExec = WShell.Exec("calc");
  
  //実行したcalcが終わるまで待つ
  while(oExec.Status == 0)
      WScript.Sleep(100);
  
  WScript.Echo("calcが終わったぴょ〜ん!");
 
Terminate
Terminate メソッドで実行したプログラムを終了させることができます。
  scripts/calc2.js (ファイルに保存にしてください)
  ---------------------------------------------------
  var WShell = WScript.CreateObject("WScript.Shell");
  var oExec = WShell.Exec("calc");
  
  WScript.Sleep(10000);
  oExec.Terminate();
 
正確にはプロセスに WM_CLOSE メッセージを送るらしい。
StdOut
StdOut プロパティを使うと、 実行しているプログラムの標準出力を得ることができます。

Perl を使う人ならよく分かると思うのですが、 例えば ls -lR の結果を取り込むときに次のようにするのと同じようなことです。

  open FILES, "ls -lR |";
  while(<FILES>) {
      ...
  }
  close FILES;
 
これと同じようなことが WSH でもできるようになりました。
  scripts/dir2.js (ファイルに保存にしてください)
  ----------------------------------------------------------------------------
  var WShell = WScript.CreateObject("WScript.Shell");
  var oExec = WShell.Exec("command.com /c dir /s");     //NT系はcmd.exeだっけ?
  
  var files = oExec.StdOut;
  var buff;
  var quit = false;
  while(true) {
      while(!files.AtEndOfStream) {
          buff = files.ReadAll();       //出力された分を読んで
          files.Write(buff);            //そのまま出力する
      }
      if(quit)
          break;
      quit = (oExec.Status == 1);
      WScript.Sleep(100);
  }
 
Perl にくらべて複雑ですね。 これは、MSDNを参考にして書いたのですが、 けっこう微妙です。ちょっとコードが変わると動かなくなったりします。
色々試してみたのですが、実行したプログラムは出力がある場合は、 AtEndOfStreamtrue にならない限り、Status は1にならないようです。
とりあえず、これだけ気をつけておきましょう。 気をつけてもうまくいかないこともあるのですが。

上の例はあまり意味がないので、もうちょっとましな例を考えてみましょう。 StdOut を使って何が嬉しいのかというと、 テンポラリなファイルを作らなくていいということです。 以前に seek2.js という例を出しましたが、 これをテンポラリなファイルを作らなくてもよいようにします。

  scripts/seek3.js (ファイルに保存にしてください)
  ----------------------------------------------------------------------------
  var WShell = WScript.CreateObject("WScript.Shell");
  var fs = WScript.CreateObject("Scripting.FileSystemObject");
  
  var objArgs = WScript.Arguments;
  if(objArgs.length == 0) {
      WScript.Echo("usage : cscript seek.js ファイル名");
      WScript.Quit();
  }
  
  var oExec = WShell.Exec("command.com /c dir /s " + objArgs(0));
  var stream = oExec.StdOut;
  
  //とにかく全部読む
  var status = 0;
  var str = "";
  var quit = false;
  while(true) {
      while(!stream.AtEndOfStream)
          str += stream.ReadAll();
      if(quit)
          break;
      quit = (oExec.Status == 1);
      WScript.Sleep(100);
  }
  
  var lines = str.split("\r\n");  //1行ずつの配列にする
  var arr;
  while(lines.length > 0) {
      str = lines.shift();        //前から取り出す
      arr = str.split(/ /);
      if(status == 0) {           //ヘッダ部分
          if(arr[0] == "ディレクトリは") {
              cfolder = arr[1];
              if(cfolder.length == 3)
                  cfolder = cfolder.substring(0, 2);
              lines.shift();
              status = 1;
          }
      }
      else {                      //リスト部分
          if(arr.length == 4)     //次のフォルダへ
              status = 0;
          else if(str.search(/<DIR>/) == -1)      //ファイル
              WScript.Echo(cfolder +
                      "\\" + str.slice(str.indexOf(":") + 4));
      }
  }
 
読むところ、
      while(!stream.AtEndOfStream)
          str += stream.ReadAll();
 
ですが、ここを
      while(!stream.AtEndOfStream) {
          str = stream.ReadLine();
          //処理する
      }
 
とする、すなわち一行ずつ読むとうまくいかないんですね、なぜか。
あと、StdErr も同様に使えると思います。
StdIn
StdIn プロパティを使うと、 実行したプログラムに入力を与えることができます。
例えば、次のスクリプトは式を入力すると答えが出てくるものです。
  scripts/calc3.js (ファイルに保存にしてください)
  ----------------------------------------------------------------------------
    while(true) {
        WScript.StdOut.Write("?");
        var buff = "";
        var c;
        while(true) {
            c = WScript.StdIn.Read(1);
            if(c == "\n") {
                if(buff.length == 1)
                    WScript.Quit();
                WScript.StdOut.WriteLine(eval(buff));
                break;
            }
            else
                buff += c;
        }
    }
 
単に Enter で返すとプログラムが終了します。
これをスクリプトで制御します。
  scripts/calc4.js (ファイルに保存にしてください)
  ----------------------------------------------------------------------------
    var qExp = [ "1+2", "Math.sin(Math.PI/6)" ];
    
    var WShell = WScript.CreateObject("WScript.Shell");
    
    var oExec = WShell.Exec("cscript //nologo calc3.js");
    var istream = oExec.StdIn;
    var ostream = oExec.StdOut;
    var cout = WScript.StdOut;
    
    var str;
    while(!ostream.AtEndOfStream) {
        str = ostream.Read(1);
        cout.Write(str);
        if(str == "?") {
            if(qExp.length > 0)
                expression = qExp.shift();
            else
                expression = "";
            istream.WriteLine(expression);  //式を入力
            cout.WriteLine(expression);     //エコーする
        }
        WScript.Sleep(50);
    }
 
"?" が出力されると、qExp から1つずつ式を実行しているプログラムに入力します。 qExp が空になったら改行を入力して終わります。
  str = ostream.Read(1);
 
のところは、なぜか ReadAll だとうまくいきません。
Back