JScript.NETコンパイラ(jsc.exe)で「error JS1135: 変数 'WScript' が宣言されていません」のエラーが出たので WScript を自作した

まず、この自作したWScriptを使った、WSH JScriptファイルのコンパイル方法を説明する。
下記の wscr.js (自作版WScript) と sample.js (コンパイル対象のWSHスクリプト) を同一フォルダに格納する。
ソースファイルを保存する際、エンコードは「Unicode(UTF16) BOMなし」「UTF7 BOMなし」を避けること。
格納後、次のコマンドを実行。

"C:\Windows\Microsoft.NET\Framework64\v4.0.30319\jsc.exe" /out:sample.exe wscr.js sample.js

wscr.js (自作版WScript)

//wscr.js
import System;
import System.IO;
import System.Threading;
import System.Diagnostics;
import System.Windows.Forms;
import Microsoft.JScript;

function MsgBox(value) {
  MessageBox.Show(value, 'Windows Script Host', MessageBoxButtons.OK, MessageBoxIcon.None);
}

class WScriptBaseClass {
  protected const _args = Environment.GetCommandLineArgs();
  protected const _argsObj = new WshArguments();
  protected var _getObj = GetObject;

  public class WshArguments {
    public function Item(i : int) {
      return _args[i + 1];
    }
    public function Count() {
      return _args.length - 1;
    }
    public function get length() {
      return _args.length - 1;
    }
  }

  public function Arguments(i : int) {
    return _args[i];
  }
}

class WScriptClass extends WScriptBaseClass {

  public function get Arguments() { return _argsObj; }

  //コマンドラインから起動したかどうかをメインウィンドウの有無で判定
  private const runFromCommandLine = (Process.GetCurrentProcess().MainWindowHandle === IntPtr.Zero);

  private const _wshExeName = runFromCommandLine ? 'cscript.exe' : 'wscript.exe';

  public const Path = Environment.GetFolderPath(Environment.SpecialFolder.System);
  public const FullName = System.IO.Path.Combine(Path, _wshExeName);
  public const Name = 'Windows Script Host';
  public const ScriptFullName = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AppDomain.CurrentDomain.FriendlyName);
  public const ScriptName = AppDomain.CurrentDomain.FriendlyName;
  public const Version = '5.812';

  public function Echo(value) {
    if(runFromCommandLine) {
      print(value);
    } else {
      MsgBox(value);
    }
  }

  public function Quit(errorCode){
    Environment.Exit(errorCode);
  }

  public function CreateObject(strProgID : String) {
    return new ActiveXObject(strProgID);
  }

  public function GetObject(strPathname : String, strProgID : String, strPrefix : String) {
    if (strPathname === '' && strProgID !== '') {
      return new ActiveXObject(strProgID);
    } else {
      if (strProgID === void 0) {
        return _getObj(strPathname);
      } else {
        return _getObj(strPathname, strProgID);
      }
    }
  }

  public function Sleep(intTime : int) {
    System.Threading.Thread.Sleep(intTime);
  }

  private var _fso;
  private function get fso() {
    _fso = _fso || new ActiveXObject('Scripting.FileSystemObject');
    return _fso;
  }

  private var _stdIn;
  public function get StdIn() {
    _stdIn = _stdIn || fso.GetStandardStream(0);
    return _stdIn;
  }

  private var _stdOut;
  public function get StdOut() {
    _stdOut = _stdOut || fso.GetStandardStream(1);
    return _stdOut;
  }

  private var _stdErr;
  public function get StdErr() {
    _stdErr = _stdErr || fso.GetStandardStream(2);
    return _stdErr;
  }
}

var WScript = new WScriptClass();

sample.js (コンパイル対象のWSHスクリプト)

// sample.js
// (WSHでの実行方法)コマンドラインで以下を実行
// cscript sample.js aaaa bbbb cccc

var puts = function (s) {
  WScript.StdOut.WriteLine(s);
}

puts('typeof WScript.Arguments='+(typeof WScript.Arguments));
puts('WScript.Arguments.length = ' + WScript.Arguments.length);
var i;
for(i = 0; i < WScript.Arguments.length; i++) {
  puts('WScript.Arguments.Item(' + i + ') = ' + WScript.Arguments.Item(i));
  puts('WScript.Arguments(' + i + ') = ' + WScript.Arguments(i));
}

puts('WScript.FullName = ' + WScript.FullName);
puts('WScript.Name = ' + WScript.Name);
puts('WScript.Path = ' + WScript.Path);
puts('WScript.ScriptFullName = ' + WScript.ScriptFullName);
puts('WScript.ScriptName = ' + WScript.ScriptName);
puts('WScript.Version = ' + WScript.Version);

var msec = 1 * 1000;
puts('WScript.Sleep(' + msec + ') START : ' + dateToString(new Date()) );
WScript.Sleep(msec);
puts('WScript.Sleep(' + msec + ') END   : ' + dateToString(new Date()) );

WScript.StdOut.WriteLine('WScript.StdOut.WriteLine');
WScript.StdErr.WriteLine('WScript.StdErr.WriteLine');
/*
WScript.StdErr.Write('Enter Text>');
var text = WScript.StdIn.ReadLine();
WScript.StdErr.WriteLine('Entered : "' + text + '"');
*/

WScript.Echo('WScript.Echo');

var errorCode = 3;
puts('WScript.Quit(' + errorCode + ')');
puts('after this process done, enter \'echo \%errorlevel\%\'');
WScript.Quit(errorCode);

function dateToString(dt) {
  var s = '';
  s += dt.getFullYear();
  s += '/' + ('00' + (dt.getMonth() + 1)).slice(-2);
  s += '/' + ('00' + dt.getDate()).slice(-2);
  s += ' ' + ('00' + dt.getHours()).slice(-2);
  s += ':' + ('00' + dt.getMinutes()).slice(-2);
  s += ':' + ('00' + dt.getSeconds()).slice(-2);
  s += '.' + ('000' + dt.getMilliseconds()).slice(-3);
  return s;
}


上記の自作版WScriptには下記の制限がある。

  • 自作版WScript のメソッド名、プロパティ名は Case-Sensitive である。大文字小文字を wscr.js で定義されている通りに書かなければならない。
  • 自作版WScript の GetObject メソッドには、引数 strPathname と strProgID を同時に指定しない。3番目の引数 strPrefix は指定できない。


さて、実装では以下の点が問題となった。

(1) デフォルトメソッドを呼び出すとき、オブジェクトに対して関数呼び出しの書き方ができる。

  • 関数呼び出しの記述
    WScript.Arguments(2)
  • 通常のオブジェクト参照
    WScript.Arguments.length
    WScript.Arguments.Item(2)

これらの書き方を両立させるのが難しい

(2) JScriptにおいてもWScriptのメソッド名はCase-Insensitiveである。

(3) WScript.GetObject と JScript.NET の組み込み関数 GetObject の動作が異なる。
WScript.GetObject(strPathname [,strProgID], [strPrefix]) の引数 strPrefix が、JScript.NET の GetObject にはない。
strPathname,strProgID を同時に与えたときの動作が異なる。


(2), (3) については対処できなかった。

ちなみに条件コンパイルを使えば、WScriptの実装とWSH用のスクリプトは下記のように同一ファイルにまとめることができる。
このソースはそのままで、WSHスクリプトとして動かせるし、JScript.NETファイルとしてコンパイルできる。

//sample2.js
/*@cc_on
@if (@_jscript_version >= 7)
import System;
import System.IO;
import System.Threading;
import System.Diagnostics;
import System.Windows.Forms;
import Microsoft.JScript;

function MsgBox(value) {
  MessageBox.Show(value, 'Windows Script Host', MessageBoxButtons.OK, MessageBoxIcon.None);
}

class WScriptBaseClass {
  protected const _args = Environment.GetCommandLineArgs();
  protected const _argsObj = new WshArguments();
  protected var _getObj = GetObject;

  public class WshArguments {
    public function Item(i : int) {
      return _args[i + 1];
    }
    public function Count() {
      return _args.length - 1;
    }
    public function get length() {
      return _args.length - 1;
    }
  }

  public function Arguments(i : int) {
    return _args[i];
  }
}

class WScriptClass extends WScriptBaseClass {

  public function get Arguments() { return _argsObj; }

  //コマンドラインから起動したかどうかをメインウィンドウの有無で判定
  private const runFromCommandLine = (Process.GetCurrentProcess().MainWindowHandle === IntPtr.Zero);

  private const _wshExeName = runFromCommandLine ? 'cscript.exe' : 'wscript.exe';

  public const Path = Environment.GetFolderPath(Environment.SpecialFolder.System);
  public const FullName = System.IO.Path.Combine(Path, _wshExeName);
  public const Name = 'Windows Script Host';
  public const ScriptFullName = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AppDomain.CurrentDomain.FriendlyName);
  public const ScriptName = AppDomain.CurrentDomain.FriendlyName;
  public const Version = '5.812';

  public function Echo(value) {
    if(runFromCommandLine) {
      print(value);
    } else {
      MsgBox(value);
    }
  }

  public function Quit(errorCode){
    Environment.Exit(errorCode);
  }

  public function CreateObject(strProgID : String) {
    return new ActiveXObject(strProgID);
  }

  public function GetObject(strPathname : String, strProgID : String, strPrefix : String) {
    if (strPathname === '' && strProgID !== '') {
      return new ActiveXObject(strProgID);
    } else {
      if (strProgID === void 0) {
        return _getObj(strPathname);
      } else {
        return _getObj(strPathname, strProgID);
      }
    }
  }

  public function Sleep(intTime : int) {
    System.Threading.Thread.Sleep(intTime);
  }

  private var _fso;
  private function get fso() {
    _fso = _fso || new ActiveXObject('Scripting.FileSystemObject');
    return _fso;
  }

  private var _stdIn;
  public function get StdIn() {
    _stdIn = _stdIn || fso.GetStandardStream(0);
    return _stdIn;
  }

  private var _stdOut;
  public function get StdOut() {
    _stdOut = _stdOut || fso.GetStandardStream(1);
    return _stdOut;
  }

  private var _stdErr;
  public function get StdErr() {
    _stdErr = _stdErr || fso.GetStandardStream(2);
    return _stdErr;
  }
}

var WScript = new WScriptClass();
@end @*/

//-------------------------------
//ここから下にWSHスクリプトを書く
var puts = function (s) {
  WScript.StdOut.WriteLine(s);
}

puts('typeof WScript.Arguments='+(typeof WScript.Arguments));
puts('WScript.Arguments.length = ' + WScript.Arguments.length);
var i;
for(i = 0; i < WScript.Arguments.length; i++) {
  puts('WScript.Arguments.Item(' + i + ') = ' + WScript.Arguments.Item(i));
  puts('WScript.Arguments(' + i + ') = ' + WScript.Arguments(i));
}

puts('WScript.FullName = ' + WScript.FullName);
puts('WScript.Name = ' + WScript.Name);
puts('WScript.Path = ' + WScript.Path);
puts('WScript.ScriptFullName = ' + WScript.ScriptFullName);
puts('WScript.ScriptName = ' + WScript.ScriptName);
puts('WScript.Version = ' + WScript.Version);

var msec = 1 * 1000;
puts('WScript.Sleep(' + msec + ') START : ' + dateToString(new Date()) );
WScript.Sleep(msec);
puts('WScript.Sleep(' + msec + ') END   : ' + dateToString(new Date()) );

WScript.StdOut.WriteLine('WScript.StdOut.WriteLine');
WScript.StdErr.WriteLine('WScript.StdErr.WriteLine');
/*
WScript.StdErr.Write('Enter Text>');
var text = WScript.StdIn.ReadLine();
WScript.StdErr.WriteLine('Entered : "' + text + '"');
*/

WScript.Echo('WScript.Echo');

var errorCode = 3;
puts('WScript.Quit(' + errorCode + ')');
puts('after this process done, enter \'echo \%errorlevel\%\'');
WScript.Quit(errorCode);

function dateToString(dt) {
  var s = '';
  s += dt.getFullYear();
  s += '/' + ('00' + (dt.getMonth() + 1)).slice(-2);
  s += '/' + ('00' + dt.getDate()).slice(-2);
  s += ' ' + ('00' + dt.getHours()).slice(-2);
  s += ':' + ('00' + dt.getMinutes()).slice(-2);
  s += ':' + ('00' + dt.getSeconds()).slice(-2);
  s += '.' + ('000' + dt.getMilliseconds()).slice(-3);
  return s;
}