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時間とかオーナー情報の取得でちとてこずったので、そこらへんを別記事にまとめる予定。

コメント

コメントをどうぞ

※メールアドレスとURLの入力は必須ではありません。 入力されたメールアドレスは記事に反映されず、ブログの管理者のみが参照できます。

※なお、送られたコメントはブログの管理者が確認するまで公開されません。

名前:
メールアドレス:
URL:
コメント:

トラックバック

このエントリのトラックバックURL: http://dara-j.asablo.jp/blog/2008/06/17/3581400/tb

※なお、送られたトラックバックはブログの管理者が確認するまで公開されません。