WMIコネタ2008年06月18日 03時12分05秒

chkconfigモドキとかpsモドキとかで久々にWMIに触ったらなんだか面白かったのでちょろちょろといじくっているのだが、そのときに見つけたりしたコネタをば。正確には前も調べたけどすっかり忘れてたってとこなんだが。

まずはEnumeratorを拡張するが吉

WQLで問い合わせた結果はいわゆる「コレクション」が返ってくる。VBSの場合は何も考えずFor Eachを使えるのだが、JScriptの場合はEnumeratorでくるんでやる必要がある。まぁWMIに限らずWSH/HTAでCOMを触る場合はそうなるのだが。

んで好みの問題だと思うけど、WMIみたいなメタデータ中心のオブジェクトを扱う場合はコレクションのループ処理を多用するハメになるので、Enumeratorでeachなんかが使えるように拡張しといたほうが便利かな、と。

最低限のサンプル。

Enumerator.prototype = function(iterator) {
	var i = 0;
	for(this.moveFirst(); ! this.atEnd(); this.moveNext()) {
		iterator( this.item(), i++ );
	}
};

このままだとprototype.jsみたいにcontinue/breakの制御ができないけど、まぁコレクションを一通りまわすことはできるのでこれだけでも結構快適になる。

後で触れるけど、WMIのデータそのままだとちょっと具合が悪い型もあるので、これにmapとtoArrayでも加えればいいんじゃないかと。

Enumerator.prototype.map = function(iterator) {
	var results = [];
	this.each( function(item, index) {
		results[ results.length ] = iterator( item, index );
	} );
	return results;
};
Enumerator.prototype.toArray = function() {
	return this.map( function(item) { return item; } );
};

datetime型

例えばWin32_ProcessクラスのCreationDateプロパティなんかがこの「datetime」型なのだが、JScriptで取り回すと次のような文字列に暗黙で変換される。

var wmi = new ActiveXObject("WbemScripting.SWbemLocator").ConnectServer();
var query = "SELECT * FROM Win32_Process WHERE Name = 'firefox.exe'";

var firefox = new Enumerator(wmi.ExecQuery(query)).toArray()[0];
if( firefox ) {
  WSH.Echo( firefox.CreationDate ); // '20080618004435.514465+540'
}

要するに「年月日時分秒.マイクロ秒GMT時差」という形式。なので、こんな感じでDateにすり合わせてやるとよいかと。
var reg = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\.(\d+)(.\d+)$/;
var parts = reg.exec( firefox.CreationDate );
WSH.Echo( new Date(
	parts[1], parts[2], parts[3],
	parts[4], parts[5], parts[6],
	parseInt( parts[7] / 1000 )
) );
// → 'Fri Jul 18 00:44:35 UTC+0900 2008'
こんなゴミゴミした正規表現じゃなくてsubstrとかでもいいんだけども。

値が配列なプロパティの扱い方

例えばWin32_NetworkAdapterConfigurationクラスのDefaultIPGatewayプロパティやIPAddressプロパティなんかがそうなのだが、値が配列の場合がある。

こういったWMIの配列は、そのままJScriptのArrayに変換されないらしい。

query = "SELECT IPAddress FROM Win32_NetworkAdapterConfiguration";
var nics = new Enumerator(wmi.ExecQuery(query)).toArray();
// 先頭のNICにIPアドレスが割りあたってるとして、
WSH.Echo( typeof(nics[0].IPAddress) ); // → unknown

しかもこの unknownオブジェクト、Enumeratorに食わせることもできない
new Enumerator( nics[0].IPAddress );
// → 'オブジェクトがコレクションではありません'例外

こういう場合はVBArrayを使うんだそうな。

VBArrayってのはEnumerator同様JScript独特のオブジェクトで、

Visual Basic のセーフ配列へアクセスする方法を提供します。
ってなオブジェクトだそうな。幸いなことに(というか、目的考えると当たり前か)toArray()でJScriptのArrayに変換することができる。

ってわけで、

WSH.Echo( new VBArray( nics[0].IPAddress ).toArray().join("\n") );
// → IPアドレスがバインドされてる数だけ出力される

VBArrayをEnumerableにするってのもよいかも。今度やってみよう。

ちなみにProperty_で動的列挙をする場合はIsArrayプロパティがtrueになっているので、それで判別できるかと。

query = "SELECT * FROM Win32_NetworkAdapterConfiguration";
nic = new Enumerator(wmi.ExecQuery(query)).toArray()[0];

new Enumerator(nic.Properties_).each( function(prop) {
	WSH.Echo( [
		"プロパティ名:", prop.Name, "\t",
		"データ型:", prop.CIMType, "\t",
		"配列?:", prop.IsArray
	].join( "" ) );
	// 例(一部)
	// プロパティ名:MACAddress  データ型:8  配列?:false
	// プロパティ名:IPAddress   データ型:8 配列?:true
} );

Win32_Processのメモ2008年06月17日 03時48分07秒

前のエントリで書いた「ps.js」は、基本的にWMIでWin32_Processを列挙してそこから得られる情報を出力しているのだが、CPU時間とオーナーはちとてこずったのでその辺のメモをば。

まず、Win32_Processオブジェクトのメンバーについては、以下を参照。

CPU時間

Win32_Processのプロパティの中で、名前に「Time」がついているのは「KernelModeTime」と「UserModeTime」の2つ。そんで、どちらのプロパティも解説をみると

  • 64ビット 符号なし整数
  • 100ナノ秒単位
とある。

例えば「vmware-authd.exe」というプロセスが、CPU時間「0:13:36」という表示になっているとして、このプロセスをWQLで検索して、KernelModeTime/UserModeTimeの各プロパティ値を1000万で割ってみると、

プロパティ 値 / 1000万
KernelModeTime 5390451088 539.0451088
UserModeTime 2814146544 281.4146544
のようになった。ふむ、どうやらタスクマネージャのCPU時間はカーネルモード+ユーザモードらしい。これで時間の値は取得できたのであとは表示。

最初は「ミリ秒単位にしてDateに食わせりゃあんまり計算しなくていいかも」とか不精なことを考えていたのだが、GMT時差補正や24時間を越えた場合の展開、getMinutes()やgetSeconds()などをちくちく呼び出すことを考えたら、素直に計算したほうが早いことに気づいたのでベタで計算することにした。

ちなみにuint64にそのまま合わせるデータ型がないためか、JSでKernelModeTimeなどを取得した場合、返ってきたデータの型はstringだったので、そこらへんも注意。

オーナー情報の取得

プロパティをざっと眺めたところ、それっぽいやつはなかったのだが、メソッドのほうには「GetOwner」「GetOwnerSid」とそれっぽいやつがあった。

「GetOwnerSid」はSIDを取得するのだろうから、今回は用なしとした。そんな値みてもすぐにはピンと来ないし、第一長すぎるし。

なので、「GetOwner」メソッドの詳細を見てみたところ、

uint32 GetOwner(
 [out] string User,
 [out] string Domain
);
なんて定義になっていた。outパラメータで値を取得し、メソッドの戻り値は成否を示すらしい。

って、JSでoutパラメータなんでどうやって取り回すんだ??

ためしに

// proc には Win32_Processのインスタンスが入ってるとして、
var user, domain;
var result = proc.GetOwner( user, domain );
if( result == 0 ) { // success
	WSH.Echo( "user = " + user + "\ndomain = " + domain );
}
とかやってみたが、result == 0 の場合でもuser/domainとも値が入っていない。まぁ予想はしてたけど。

この説明ページにあるVBSのサンプルコードは

Return = objProcess.GetOwner(strNameOfUser)
とか普通にやってるのだがJSのサンプルないし。さて、困った。

んで「Win32_Process getowner jscript」なんてベタキーワードでググってみたら見つかりましたよ、こんなのが。

var q = "SELECT * FROM Win32_Process";
var e = New Enumerator( GetObject( "winmgmts:{impersonationLevel=impersonate}!//./root/cimv2" ).ExecQuery( q ) );

WScript.Echo("User\tPID");

for ( ; e.atEnd(); e.moveNext() )
{
   var x = e.item();
   var ometh = x.Methods_.Item("GetOwner");
   var xout = x.ExecMethod_(ometh.Name, null);
   var user = xout.User;
   var domain = xout.Domain;

   WScript.Echo(user + "\t" + x.ProcessId );
} 

なるほど、ExecMethod_なんてリフレクションメソッドを使うと、outパラメータの値も戻り値に含まれるのね。ちなみにGetOwner()を普通に呼び出したときの戻り値は上記「xout」の「ReturnValue」プロパティに入るらしい。

で、これを参考に

var info = proc.ExecMethod_( proc.Methods_.Item("GetOwner").Name, null );

なんてコードを書いたんだけど、よくよく考えてみると「proc.Methods_.Item("GetOwner").Name」って普通に"GetOwner"を返すので、
var info = proc.ExecMethod_( "GetOwner", null );
でぜんぜん問題なかったんだけどね(あ、第二引数もいらないかも)。

オマケ・現在のログオンユーザ情報の取得

ps.jsでは、-aオプションなしの時はオーナーが現在のログオンユーザのプロセスに限定するようにしたんだけども、その「現在のログオンユーザ」の取得はWMIとはまったく別口で取得。

こっちはきわめて簡単で、WScript.NetworkオブジェクトのUserDomain/UserNameプロパティでOK。オマケにComputerNameでNetBIOS名も取れる。

var net = new ActiveXObject("WScript.Network");
WSH.Echo( [
	"user = " + net.UserName,
	"domain = " + net.UserDomain,
	"machine = " + net.ComputerName
].join("\r\n") );

ps.jsとps.cmd / kill.cmd2008年06月17日 00時54分14秒

なんちゃってコマンド 第四弾

我ながらしつこいなと思いつつ「なんちゃってコマンド」スクリプト。今度はps作ってみました。

ま、WMIでWin32_Process列挙できればそんなに難しくないので、似たようなことやってる人はいるだろなぁ。

例によってソース

ちょっと長いけど例によってのっけときます。NYSLです。

var fso = new ActiveXObject("Scripting.FileSystemObject");
var shell = new ActiveXObject("WScript.Shell");

// コマンドライン引数の取得
var args = new function() {
  var args = WSH.Arguments, result = [];
  for(var col = new Enumerator( args ); ! col.atEnd(); col.moveNext()) {
    result[ result.length ] = String( col.item() );
  }
  var named = {};
  for(var col = new Enumerator( args.Named ); ! col.atEnd(); col.moveNext()) {
    named[ String( col.item() ) ] = String( args.Named.Item( col.item() ) );
  }
  var unnamed = [];
  for(var col = new Enumerator( args.Unnamed ); ! col.atEnd(); col.moveNext()) {
    unnamed[ unnamed.length ] = String( col.item() );
  }
  result.named = named;
  result.unnamed = unnamed;
  
  result.toArray = function() {
    var result = [];
    for(var i = 0, l = this.length; i < l; i++) {
      result[ i ] = /((^".*"$)|(^'.*'$))/.test( this[i] ) ?
        this[i] : [ '"', this[i], '"' ].join("");
    }
    return result;
  };
  result.toString = function() {
    return this.toArray().join(" ");
  };
  return result;
}();

// cscriptで強制起動
if( /wscript\.exe$/i.test( WSH.FullName ) ) {
  shell.Run( [
    "cscript",
    /((^".*"$)|(^'.*'$))/.test( WSH.ScriptFullName ) ? WSH.ScriptFullName : [ '"', WSH.ScriptFullName, '"' ].join(""),
    args
  ].join(" ") );
  WSH.Quit();
}

// ユーティリティ関数定義
var echo = function(s) {
  print( [ s, "\n" ].join("") );
}
var print = function(s) {
  WSH.StdOut.Write( s || "" );
}
var input = function() {
  if( arguments[0] ) print( arguments[0] );
  print( ">" );
  return WSH.StdIn.ReadLine();
}

Error.prototype.toString = function() {
  return this.description || this.message || this.number || this;
};

var $break = {};
var $continue = {};
Enumerator.prototype.each = function(iterator) {
  try {
    var i = 0;
    for(this.moveFirst(); ! this.atEnd(); this.moveNext()) {
      try {
        iterator( this.item(), i++ );
      } catch(e) {
        if( e != $continue ) throw e;
      }
    }
  } catch(e) {
    if( e != $break ) throw e;
  }
};

String.prototype.repeat = function(count) {
  var buf = [];
  for(var i = 0; i < count; i++) buf[buf.length] = this;
  return buf.join("");
};
String.prototype.align = function(align, size) {
  if( ! /^((left)|(right)|(center))$/i.test( align ) ) align = "left";
  if( isNaN(size) || size < 1 ) size = this.length;
  switch( align ) {
  case "left":
    return [ this, " ".repeat(size) ].join("").substr(0, size);
  case "right":
    return [ " ".repeat(size), this ].join("").slice(-1 * size);
  default:
    var pad = " ".repeat( size );
    var s = [ pad, this, pad ].join("");
    return s.substr( parseInt(s.length / 2) - parseInt(size / 2), size );
  }
};
Number.prototype.toTime = function() {
  var sec = this % 60;
  var min = Math.floor( this / 60 );
  var hour = Math.floor( min / 60 );
  min = hour ? min % 60 : min;
  return [
    hour,
    ( "00" + min ).slice(-2),
    ( "00" + sec ).slice(-2)
  ].join(":");
};

var ProcMgr = function() {
  this.wmi = new ActiveXObject("WbemScripting.SWbemLocator").ConnectServer();
  var wshNet = new ActiveXObject("WScript.Network");
  this.currentUser = [ wshNet.UserDomain, wshNet.UserName ].join("\\");
};
ProcMgr.prototype = {
  list : function(id) {
    var query = "SELECT * FROM Win32_Process";
    query += id != null ? [ " WHERE ProcessId = ", id ].join("") : "";
    return new Enumerator( this.wmi.ExecQuery(query) );
  },
  kill : function(id) {
    this.list(id).each( function(proc) {
      proc.Terminate();
      throw $break;
    } );
  },
  exec : function(params) {
    if( params[0] && params[0].toLowerCase() == "--kill" && params[1] ) {
      // --kill
      this.kill( params[1] );
    } else if( params[0] && params[0].toLowerCase() == "--list" ) {
      // --list
      
      // -a オプション。全ユーザのプロセスを列挙
      var allUser = ( params[1] != null && /^-.*A/i.test( params[1] ) );
      // -d オプション。コマンドラインの情報を付加
      var detail = ( params[1] != null && /^-.*d/i.test( params[1] ) );
      
      var inited = false;
      var _self = this;
      this.list().each( function(proc) {
        if( ! inited ) {
          inited = true;
          echo( [
            "PID".align( "right", 5 ),
            "PPID".align( "right", 5 ),
            "OWNER".align( "left", 16 ),
            "WK-SIZE".align( "right", 8 ),
            "PEAK".align( "right", 8 ),
            "TIME".align( "right", 10 ),
            "CMD".align( "left", 16 )
          ].join(" ") );
        }
        var cmd = proc.CommandLine == null ? "(null)" : String( proc.CommandLine );
        var time = parseInt( ( Number(proc.KernelModeTime) + Number(proc.UserModeTime) ) / 10000 / 1000 );
        var info = proc.ExecMethod_( proc.Methods_.Item("GetOwner").Name, null );
        
        // プロセスオーナーのチェック
        if( ! allUser && [ info.Domain, info.User ].join("\\").toLowerCase() != _self.currentUser.toLowerCase() ) throw $continue;
        
        echo( [
          String(proc.ProcessId).align( "right", 5 ),
          String(proc.ParentProcessId).align( "right", 5 ),
          String(info.User || "(null)" ).align( "left", 16 ),
          String(Number(proc.WorkingSetSize) / 1024).align( "right", 8 ),
          String(Number(proc.PeakWorkingSetSize) / 1024).align( "right", 8 ),
          String( time.toTime() ).align( "right", 10 ),
          [
            String(proc.Name),
            detail ? [ "(", cmd, ")" ].join("") : ""
          ].join(" ")
        ].join(" ") );
      } );
    } else {
      // no parameter -> --list
      this.exec( [ "--list" ] );
    }
  }
};

new ProcMgr().exec( args );

今回のダウンロードはzip形式にしてます。こちらからどうぞ。

使い方

今回は不精して、1つのスクリプトで2つのコマンドを兼用しています。おかげでjsファイルとしての呼び出しはちと不自然な感じなんですが。

プロセスの列挙 - psコマンド風味

何も引数をつけずに起動すると psコマンドっぽく(?)稼動中のプロセスを列挙します。ただし、標準入力での入力待ちを行わないので、明示的にcscriptで起動したほうがよいでしょう。

C:\ps_command\scripts>cscript ps.js
Microsoft (R) Windows Script Host Version 5.6
Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.

  PID  PPID OWNER             WK-SIZE     PEAK       TIME CMD
  472  1532 dara-j               2072     4912    0:01:01 tp4serv.exe
 2172  2112 dara-j              29992    95644    0:42:34 explorer.exe
 2272  2172 dara-j               4384     6808    0:00:30 TSVNCache.exe
    :
  (中略)
    :
 1640  2172 dara-j               2840     2884    0:00:00 cmd.exe
 4920  1640 dara-j               6612     6612    0:00:00 cscript.exe

C:\ps_command\scripts>

列の意味は、左から
  • PID - プロセスID
  • PPID - 親プロセスID
  • OWNER - プロセスのオーナー
  • WK-SIZE - ワーキングセット サイズ
  • PEAK - ピークメモリサイズ
  • TIME - CPU時間(カーネル+ユーザ)
  • CMD - コマンドイメージ名
といった具合です。まぁ「列の選択」で選べば普通にタスクマネージャで確認できる内容ばかりなんですが。

また、第一引数に「--list」をつけた場合も上と同じ動作になります。

C:\ps_command\scripts>cscript ps.js --list
Microsoft (R) Windows Script Host Version 5.6
Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.

  PID  PPID OWNER             WK-SIZE     PEAK       TIME CMD
  472  1532 dara-j               2072     4912    0:01:01 tp4serv.exe
 2172  2112 dara-j              29928    95644    0:42:39 explorer.exe
 2272  2172 dara-j               4384     6808    0:00:30 TSVNCache.exe
    :
  (中略)
    :
 1640  2172 dara-j               2840     2884    0:00:00 cmd.exe
 5864  2172 dara-j               1888     6716    0:00:01 taskmgr.exe
 5212  1640 dara-j               6604     6604    0:00:00 cscript.exe

C:\ps_command\scripts>

--listで起動した場合はさらに第二引数にオプションをつけることができます。

「-a」オプションをつけると、他のオーナーが所有するプロセスも列挙します。といってもだいたい「SYSTEM」や「NETWORK SERVICE」くらいでしょうが。

C:\ps_command\scripts>cscript ps.js --list -a
Microsoft (R) Windows Script Host Version 5.6
Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.

  PID  PPID OWNER             WK-SIZE     PEAK       TIME CMD
    0     0 (null)                 16        0   73:50:31 System Idle Process
    4     0 SYSTEM                 36     2040    0:05:51 System
  836     4 SYSTEM                 44      480    0:00:00 smss.exe
    :
  (中略)
    :
 1932  2172 dara-j               2848     2864    0:00:00 cmd.exe
 4696  1932 dara-j               6624     6624    0:00:00 cscript.exe
 3356  1168 NETWORK SERVICE      5728     5728    0:00:00 wmiprvse.exe

C:\ps_command\scripts>

これは個人的な趣味でつけたのですが、「-d」オプションをつけると、起動時のコマンドライン(引数まで込み)を出力します。

C:\ps_command\scripts>cscript ps.js --list -d
Microsoft (R) Windows Script Host Version 5.6
Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.

  PID  PPID OWNER             WK-SIZE     PEAK       TIME CMD
  472  1532 dara-j               2072     4912    0:01:02 tp4serv.exe ("C:\Progr
am Files\Lenovo\TrackPoint\tp4serv.exe")
 2172  2112 dara-j              30244    95644    0:43:04 explorer.exe (C:\WINDO
WS\Explorer.EXE)
 2272  2172 dara-j               4408     6808    0:00:30 TSVNCache.exe ("C:\Pro
gram Files\TortoiseSVN\bin\TSVNCache.exe")
    :
  (中略)
    :
 5864  2172 dara-j               2632     6716    0:00:10 taskmgr.exe (C:\WINDOW
S\system32\taskmgr.exe)
 1932  2172 dara-j               5516     5592    0:00:00 cmd.exe ("cmd.exe" /K
cd C:\ps_command)
 2260  1932 dara-j               6624     6624    0:00:00 cscript.exe (cscript p
s.js --list -d)

C:\ps_command\scripts>

複数インスタンス起動できるエディタなんかの、あるインスタンスを特定する場合なんかに便利かなとか思ってつけたのですが、正直表示が見づらいです。まぁオマケ機能くらいの感じで。

実行例は示しませんが、「-a」と「-d」は「-ad」といった具合で同時に指定できます。

プロセスの強制終了 - killコマンド風味

第一引数に「--kill」をつけるとkillモードで起動します。この呼び出し方法は第二引数にプロセスIDを必ず指定する必要があります

以下はメモ帳(notepad.exe)を起動し、プロセスIDを確認後にkillモードで終了させる例です。

C:\ps_command\scripts>notepad

C:\ps_command\scripts>cscript ps.js | findstr notepad
 5728  1932 dara-j               4888     4888    0:00:00 notepad.exe

C:\ps_command\scripts>cscript ps.js --kill 5728
Microsoft (R) Windows Script Host Version 5.6
Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.


C:\ps_command\scripts>cscript ps.js | findstr notepad

C:\ps_command\scripts>

バッチファイル

見てきたように2種類の動作を1つのスクリプトに集約したため、ちょっと引数が気持ち悪いのですが、バッチファイルを添付してあるので勘弁してください(ダウンロードファイルをzipにしたのはこのため)。

ダウンロードファイルを解凍すると、以下のような構造になっています。

  • ps_command/
    • scripts/
      • ps.js - スクリプト本体
    • kill.cmd - killモード起動用のバッチファイル
    • ps.cmd - psモード起動用のバッチファイル

バッチファイルはスクリプトパスを「"%~dP0\scripts\ps.js"」としているため、「ps_command」フォルダごとリロケータブルになっています。「%~dp0」がなんなのかはこちらのサイトなんかが参考になるのでは。

ps.cmdは見れば即わかるくらいシンプルですが、kill.cmdは引数なしの時にusageを表示する分岐処理を入れてます。まぁぜんぜん難しいことしてるわけではないのですが。

その他

なんというか、できそうだからやってみました、以上のことがないようなネタですが。本当はvmstatとかfreeとかもやろうかと思ったけど、まんまlinuxと同じ情報があるわけでなし、なにを出力するか考えるのも面倒になってきたのでなんちゃってシリーズはこれでおしまいかな。

ま、そうはいっても今回はCPU時間とかオーナー情報の取得でちとてこずったので、そこらへんを別記事にまとめる予定。

chkconfig.js2008年06月15日 06時19分04秒

またくだらんモノ作ったなぁ

くだらんと思いつつも、chkconfig風にWindowsサービスの起動方法を確認したり変更したりするユーティリティスクリプト作ってみました。

いや、前回のservice.cmdの引数に渡す「サービス名」を確認するのが主な目的だったんだけど、起動方法の変更とかもなんとなくできそうだったのでやってみたらできたってことで。

ソース

こんな感じ。例によってNYSLで。ダウンロードはこちらから。

var fso = new ActiveXObject("Scripting.FileSystemObject");
var shell = new ActiveXObject("WScript.Shell");

// コマンドライン引数の取得
var args = new function() {
  var args = WSH.Arguments, result = [];
  for(var col = new Enumerator( args ); ! col.atEnd(); col.moveNext()) {
    result[ result.length ] = String( col.item() );
  }
  var named = {};
  for(var col = new Enumerator( args.Named ); ! col.atEnd(); col.moveNext()) {
    named[ String( col.item() ) ] = String( args.Named.Item( col.item() ) );
  }
  var unnamed = [];
  for(var col = new Enumerator( args.Unnamed ); ! col.atEnd(); col.moveNext()) {
    unnamed[ unnamed.length ] = String( col.item() );
  }
  result.named = named;
  result.unnamed = unnamed;
  
  result.toArray = function() {
    var result = [];
    for(var i = 0, l = this.length; i < l; i++) {
      result[ i ] = /((^".*"$)|(^'.*'$))/.test( this[i] ) ?
        this[i] : [ '"', this[i], '"' ].join("");
    }
    return result;
  };
  result.toString = function() {
    return this.toArray().join(" ");
  };
  return result;
}();

// cscriptで強制起動
if( /wscript\.exe$/i.test( WSH.FullName ) ) {
  shell.Run( [
    "cscript",
    /((^".*"$)|(^'.*'$))/.test( WSH.ScriptFullName ) ? WSH.ScriptFullName : [ '"', WSH.ScriptFullName, '"' ].join(""),
    args
  ].join(" ") );
  WSH.Quit();
}

// ユーティリティ関数定義
var echo = function(s) {
  print( [ s, "\n" ].join("") );
}
var print = function(s) {
  WSH.StdOut.Write( s || "" );
}
var input = function() {
  if( arguments[0] ) print( arguments[0] );
  print( ">" );
  return WSH.StdIn.ReadLine();
}

Error.prototype.toString = function() {
  return this.description || this.message || this.number || this;
};

var $break = {};
var $continue = {};
Enumerator.prototype.each = function(iterator) {
  try {
    var i = 0;
    for(this.moveFirst(); ! this.atEnd(); this.moveNext()) {
      try {
        iterator( this.item(), i++ );
      } catch(e) {
        if( e != $continue ) throw e;
      }
    }
  } catch(e) {
    if( e != $break ) throw e;
  }
};

String.prototype.repeat = function(count) {
  var buf = [];
  for(var i = 0; i < count; i++) buf[buf.length] = this;
  return buf.join("");
};
var SvcMgr = function() {
  this.wmi = new ActiveXObject("WbemScripting.SWbemLocator").ConnectServer();
};
SvcMgr.prototype = {
  list : function(name) {
    var query = "SELECT Name, DisplayName, Description, StartMode, State FROM Win32_Service";
    if( name ) query += " WHERE Name = '" + name + "'";
    return new Enumerator( this.wmi.ExecQuery( query ) );
  },
  change : function(name, value) {
    this.list(name).each( function(service) {
      service.ChangeStartMode( value );
      throw $break;
    } );
  },
  exec : function(params) {
    if( params[0] && params[0].toLowerCase() == "--list" ) {
      // --list
      var max = 39;
      this.list( params[1] || false ).each( function(service) {
        echo( [
          service.Name.length < max ?
            ( service.Name + " ".repeat(max) ).substr(0, max) : service.Name,
          service.StartMode
        ].join(" ") );
      } );
    } else if( params[0] && params[0].toLowerCase() == "--detail" && params[1] ) {
      // --detail
      this.list( params[1] ).each( function(service) {
        echo( "サービス名 : " + service.Name );
        echo( "表示名     : " + service.DisplayName );
        echo( "スタートアップの種類 : " + ( {
          "Boot" : "ブート",
          "System" : "システム",
          "Auto" : "自動",
          "Manual" : "手動",
          "Disabled" : "無効"
        }[ service.StartMode ] || service.StartMode ) );
        echo( "状態       : " + ( {
          "Running" : "実行中",
          "Stopped" : "停止中"
        }[ service.State ] || service.State ) );
        echo( "説明 : " );
        echo( "  " + ( service.Description || "(no description)" ) );
        echo( "" );
        throw $break;
      } );
    } else if( params[0] && ! /^--/.test(params[0]) && params[1] ) {
      // change startmode
      var mode = {
        "auto" : "automatic",
        "sys" : "system",
        "man" : "manual",
        "dis" : "disabled"
      }[ params[1] ] || params[1];
      this.change(params[0], mode);
      this.exec( [ "--list", params[0] ] );
    } else {
      echo( "使い方 : [cscript | wscript] chkconfig.js --list [サービス名]" );
      echo( "         [cscript | wscript] chkconfig.js --detail サービス名" );
      echo( "         [cscript | wscript] chkconfig.js サービス名 [ Boot |" );
      echo( "                                                       System |" );
      echo( "                                                       Automatic |" );
      echo( "                                                       Manual |" );
      echo( "                                                       Disabled ]" );
      echo();
    }
  }
};

new SvcMgr().exec( args.unnamed );

これをcscript経由で起動するバッチファイルを%SystemRoot%にでも入れておけばよいかと。

使い方

そのまま起動すると使い方は表示されるのでだいたいわかると思いますが、以下の3種類の引数を受け付けます。

  1. --list [サービス名]
  2. <サービス名> <起動方法>
  3. --detail <サービス名>

--list [サービス名]

「--list」はサービスの起動方法を確認します。こんな感じ。

C:\Documents and Settings\dara-j>cscript chkconfig.js --list
AcPrfMgrSvc                             Auto
AcSvc                                   Disabled
Alerter                                 Disabled
ALG                                     Disabled
AppMgmt                                 Manual
aspnet_state                            Manual
AudioSrv                                Auto
  :
 (中略)
  :
WudfSvc                                 Manual
WZCSVC                                  Auto
xmlprov                                 Manual

C:\Documents and Settings\dara-j>

各行はサービス名と起動方法が出力されます。

ここに表示されるサービス名は管理ツールの「サービス」で表示される「表示名」ではなく、内部のサービス名で、前回のservice.cmdに(というか、net startやnet stopに)渡すサービス名はこれになります。

--listの後ろにサービス名をつけると、そのサービスの情報のみが表示されます。

C:\Documents and Settings\dara-j>cscript chkconfig.js --list iisadmin
IISADMIN                                Auto

C:\Documents and Settings\dara-j>

また、パイプでfindstrにつないでもいいかもしれません。
C:\Documents and Settings\dara-j>cscript chkconfig.js --list | findstr -i vnc
winvnc                                  Auto

C:\Documents and Settings\dara-j>

findstrは-i(Ignore Case)がよいでしょう。サービス名うろ覚え時にぜひ。

<サービス名> <起動方法>

サービスの起動方法を変更します。Linuxのようなランレベルの概念はないため、--level <levels>のようなパラメータは必要なく、on/off/resetの代わりに「Boot」「System」「Automatic」「Manual」「Disabled」の5種類の指定が可能です(が、「Boot」と「System」はよくわかりません。指定しても反映されてるやらされてないやら)。

実行すると、設定反映後の状態を出力します。

C:\Documents and Settings\dara-j>cscript chkconfig.js w3svc disabled
W3SVC                                   Disabled

C:\Documents and Settings\dara-j>cscript chkconfig.js w3svc automatic
W3SVC                                   Auto

C:\Documents and Settings\dara-j>

ちなみに大文字小文字は区別されません。

--detail <サービス名>

これは本家chkconfigには相当する機能はないのですが、オマケで実装してみました。指定サービスの詳細を表示します。

C:\Documents and Settings\dara-j>cscript chkconfig.js --detail iisadmin
サービス名 : IISADMIN
表示名     : IIS Admin
スタートアップの種類 : 自動
状態       : 実行中
説明 :
  インターネット インフォメーション サービスのスナップインを使用した Web と FTP
サービスの管理を提供します。

Enter キーで終了します>

C:\Documents and Settings\dara-j>

ちょっとした解説

たいしたことはしてないのですが、WMIでWin32_Serviceクラスを扱っています。サービスの列挙は

SELECT * FROM Win32_Service
ってのが基本で(実際は取得するプロパティを限定してますが)、WHERE句でサービスを限定したり、返ってきたインスタンスに対してChangeStartModeメソッドを実行して起動方法を変えたりしています。

参考リンク

wget.jsを修正しました2008年06月13日 21時12分46秒

保存先に同名ファイルがあった場合に上書き保存に失敗するというなさけないバグがあったので、あわてて修正しました。元記事のソース・ダウンロード用ソースとも修正ずみです。