ps.jsとps.cmd / kill.cmd ― 2008年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モード起動用のバッチファイル
- scripts/
バッチファイルはスクリプトパスを「"%~dP0\scripts\ps.js"」としているため、「ps_command」フォルダごとリロケータブルになっています。「%~dp0」がなんなのかはこちらのサイトなんかが参考になるのでは。
ps.cmdは見れば即わかるくらいシンプルですが、kill.cmdは引数なしの時にusageを表示する分岐処理を入れてます。まぁぜんぜん難しいことしてるわけではないのですが。
その他
なんというか、できそうだからやってみました、以上のことがないようなネタですが。本当はvmstatとかfreeとかもやろうかと思ったけど、まんまlinuxと同じ情報があるわけでなし、なにを出力するか考えるのも面倒になってきたのでなんちゃってシリーズはこれでおしまいかな。
ま、そうはいっても今回はCPU時間とかオーナー情報の取得でちとてこずったので、そこらへんを別記事にまとめる予定。
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 |
最初は「ミリ秒単位にしてDateに食わせりゃあんまり計算しなくていいかも」とか不精なことを考えていたのだが、GMT時差補正や24時間を越えた場合の展開、getMinutes()やgetSeconds()などをちくちく呼び出すことを考えたら、素直に計算したほうが早いことに気づいたのでベタで計算することにした。
ちなみにuint64にそのまま合わせるデータ型がないためか、JSでKernelModeTimeなどを取得した場合、返ってきたデータの型はstringだったので、そこらへんも注意。
オーナー情報の取得
プロパティをざっと眺めたところ、それっぽいやつはなかったのだが、メソッドのほうには「GetOwner」「GetOwnerSid」とそれっぽいやつがあった。
「GetOwnerSid」はSIDを取得するのだろうから、今回は用なしとした。そんな値みてもすぐにはピンと来ないし、第一長すぎるし。
なので、「GetOwner」メソッドの詳細を見てみたところ、
なんて定義になっていた。outパラメータで値を取得し、メソッドの戻り値は成否を示すらしい。uint32 GetOwner( [out] string User, [out] string Domain );
って、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」なんてベタキーワードでググってみたら見つかりましたよ、こんなのが。
なるほど、ExecMethod_なんてリフレクションメソッドを使うと、outパラメータの値も戻り値に含まれるのね。ちなみにGetOwner()を普通に呼び出したときの戻り値は上記「xout」の「ReturnValue」プロパティに入るらしい。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 ); }
で、これを参考に
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") );
最近のコメント