sudo.cmd / su.cmd2015年10月18日 21時57分13秒

コマンドラインから管理者に昇格したい

たとえば、mklinkでシンボリックリンクを作りたい場合、いちいちcmd.exeを右クリックから「管理者として実行」するのは非常に面倒くさく、いま開いてるプロンプトから権限昇格できると便利だよね、と思って調べると、まぁいろいろな方々がすでにやってたりするわけです。

要は、WSHでShell.Application(エクスプローラを外部から操作するためのActiveXインターフェイス)で実行ファイルに対して「管理者として実行」で起動させる、という仕組みを使う、と。

んで、久々になんちゃってコマンド

んで、まぁ、suとかsudoとかを作ってみようかと。いや、先ほどのGoogle検索の結果に出てくるあちこちの記事でもすでに実現されてるんだけど、なんとなく自分でもやってみるか、という程度で。

suとsudoを別々に実装してもいいんだけど、cmd.exeを「管理者として実行」できればsu、任意のコマンドならsudoになるので、同じスクリプトを別のバッチファイルから呼び出す方式で実装することに。

ダウンロード

こちらからダウンロードをどうぞ。

sudo / suコマンド一式

zipには

  • scripts/sudo.js
  • su.cmd
  • sudo.cmd
の3つのファイルが格納されてるので、この構造を維持したままパスが通ったところに設置するとコマンドラインからいつでも使えるようになります。

ソース - scripts/sudo.js

スクリプトのソースはこんな感じ。

キモは最後のほうにある「new ActiveXObject("Shell.Application").ShellExecute()」のところで、ここで第4引数に"runas"を与えると、エクスプローラのコンテキストメニューから「管理者として実行」を行ったことになる、という感じです。詳しくは以下のリンク先を参照。

Shell.ShellExecute method (Windows)

ソース - sudo.cmd

sudoのバッチはこんな内容です。

バッチに渡された引数をそのままsudo.jsに丸投げしてるだけの簡単なつくりです。

ソース - su.cmd

suのバッチはこんな感じ。

sudo.cmdでは引数をそのまま丸投げしてたところを、明示的に「cmd.exe /k」しています。

あと、スクリプトを実行すると新しいプロンプトが開くので、なにも考えずに使ってるとコマンドプロンプトのウィンドウだらけになってウザいので最後にexitしてますが、これはお好みで削除してもよいでしょう。

あ、それからsudo.js側で小技を入れているので、suした場合は元のディレクトリを引き継ぎます。

使い道とか

suしてmklinkしてシンボリックリンクをバンバン作成するとか、hostsいじるのにsudo notepadするとか、まぁそんな感じでしょうか。

PHPでWake-on-LANしてみたり、wol代替コマンドにしてみたり。2009年07月23日 05時48分39秒

WoL事情を調べてみた。なんとなく。

社内でちょっとした必要があって、Web経由でWake-on-LANによるPCの起動をするツールでも作ろうかと思って調べてみた。

Vineなんかはapt経由でwolコマンド(どうやらこちらのコマンドがオリジナルみたい)をさくっとインストールできたり、CentOSやDebianにもコマンドがあるらしいことがわかった。

これならPHP経由でもコマンド叩くだけで必要な機能は実装できそう(といいつつ、CentOSのethtoolはroot権限必要だけど)なので楽かなーとか思っていたが、Windowsでデバッグするのにちょっと面倒くさそう。

じゃ、PHPだけでやってみるか

PHPだけでなんとかできんものかと思って調べたら、すでにやっておられる方もいるので、自分でもやってみることにした。

WakeOnLan.php

こんな感じでやってみた。仮に「WakeOnLan.php」とでもしておく。

<?php
class WakeOnLan {
    const BROADCAST_MAC_ADDR = 'FF:FF:FF:FF:FF:FF';
    
    const DEFAULT_BROADCAST_IP = '255.255.255.255';
    
    const DEFAULT_PORT = 2304;
    
    public static function macAddrToBytes($mac) {
        $mac = (string)$mac;
        if(! self::isValidMacAddr($mac)) {
            throw new Exception('invalid MAC address');
        }
        
        $buf = array();
        foreach(preg_split('/[:\-]/', $mac) as $one_octet) {
            $buf[] = chr(intval($one_octet, 16));
        }
        return join('', $buf);
    }
    
    public static function isValidMacAddr($mac) {
        return preg_match('/^[\da-zA-Z]{2}([:\-][\da-zA-Z]{2}){5}$/', $mac);
    }
    
    protected $_broadcastIp;
    
    protected $_port;
    
    public function __construct($broadcastIp = null, $port = null) {
        $this->setBroadcastIp($broadcastIp)->setPort($port);
    }
    
    public function getBroadcastIp() {
        return $this->_broadcastIp;
    }
    public function setBroadcastIp($ip) {
        $ip = (string)$ip;
        if(empty($ip)) $ip = self::DEFAULT_BROADCAST_IP;
        $this->_broadcastIp = $ip;
        return $this;
    }
    public function getBroadcastUrl() {
        return 'udp://' . $this->getBroadcastIp();
    }
    
    public function getPort() {
        return $this->_port;
    }
    public function setPort($port) {
        if($port == null) $port = -1;
        $port = (int)$port;
        if($port < 0) $port = self::DEFAULT_PORT;
        $this->_port = $port;
        return $this;
    }
    
    public function sendTo($mac) {
        $data = self::macAddrToBytes(self::BROADCAST_MAC_ADDR);
        $mac_data = self::macAddrToBytes($mac);
        for($i = 0; $i < 16; $i++) $data .= $mac_data;
        
        $fp = @fsockopen($this->getBroadcastUrl(), $this->getPort(), $errno, $errstr);
        if( $fp === false ) {
            throw new Exception($errstr . '(' . $errno . ')');
        }
        fwrite($fp, $data);
        @fclose($fp);
        return $this;
    }
}

ソース中に特にコメント入れてないけど、結構コンパクトなコードなので読むのはそんなに難しくないかと。WoL自体はWikipedia@itの記事を見ればだいたいわかるし。

んで、使い方はかなり単純で、

  • コンストラクタ(またはsetter)でブロードキャストアドレスとポートを設定し、
  • sendToメソッドにターゲットのMACアドレスを渡す
だけ。

たとえばMACアドレスが「00-0D-59-B5-31-08」なんてPCがあって、起動すると192.168.0.0/24なアドレスが割り振られるネットワークにいたとすると、これをWoLで起動する場合は

<php
require_once 'WakeOnLan.php';

$wol = new WakeOnLan('192.168.0.255', 2304);
$wol->sendTo('00-0D-59-B5-31-08');

てな感じで使う。あ、MACアドレスはハイフン区切りでもコロン区切りでも同じ動作をします。

ほんとはもうちょっと簡単

先の例ではご丁寧にブロードキャストアドレス/ポートとも指定したが、WoL発行元と同一セグメント上のPCを起こすなら「255.255.255.255」(=リミテッド・ブロードキャスト・アドレスっていうらしい)で問題ないし、ポートについても気休め程度のものらしいので、コンストラクタ引数を省略して

$wol = new WakeOnlan();
だけでもよい。

で、バッチで包む

例であげたような起動用PHPを作成してもいいんだけど、引数をそのまま流し込めばいいくらいなので、こんなバッチファイルで済ませられる。

@echo off
php -r "require_once 'WakeOnLan.php'; $wol=new WakeOnLan('%2'); $wol->sendTo('%1');"

これを「wol.bat」とか「wol.cmd」とかって名前でパスが通った場所に保存して(WakeOnLan.phpはinclude_pathが設定されているところにおいておけばいいでしょ)、
wol 00:0D:59:B5:31:08
とか、
wol 00:0D:59:B5:31:08 192.168.0.255
なんて感じで叩いたり、1行 - 1MACアドレスなテキストファイル作って、
for %m in ('type macaddr.txt') do @wol %m
みたいな感じで一斉起動とか。

余談

WakeOnLan.phpではMagicPacketを送出するのにfsockopen() → fwrite()だけで済ませてるけど、参考にさせてもらったこちらのコードではsocket_set_option()でブロードキャスト許可設定をしている。fsockopen()の場合にはいらないんだろうか。それともデフォルトでブロードキャスト許可されてるんだろうか。

ZendFrameworkのライブラリやドキュメントをコマンドラインからダウンロードするバッチファイル2009年06月10日 04時18分31秒

なんとなく作ってみました

いやー、我ながら毎度毎度くだらんと思うんだけど、Zend Frameworkのアーカイブやらドキュメントやらをコマンドラインでダウンロードするバッチを作ってみたり。ソースも載せてますが、こちらからダウンロードもできます。

元々は、ローカルのsvnリポジトリに、ZFの各リリース版を上書き→コミットしてバージョン間の差分を見てみようというくだらない思いつきが発端なんだけども、アーカイブページからちくちくとリンククリックしたりするのが面倒になってきたもので。いや、ダウンローダ使えばいいんだけどね。

バッチは難しいや

ソースはこんな感じ。

@echo off
set VER=%1
set EXT=%2
set KIND=%3
if "%EXT%"=="" goto setext else goto setkind
:setext
set EXT=zip
:setkind
if "%KIND%"=="doc" goto setman
goto setlib
:setman
set KIND=-manual-ja
set MSG=マニュアル
goto dl
:setlib
set KIND=
set MSG=ライブラリ
:dl
@echo Zend Framework %VER% の %MSG% をダウンロードします
wget http://framework.zend.com/releases/ZendFramework-%VER%/ZendFramework-%VER%%KIND%.%EXT%

ファイル名は「zfdl.bat」とか「zfdl.cmd」とか、適当に。

しかしバッチ書き慣れないせいか、えらく時間かかったうえにかなりグダグダしたソースだなぁ。難しいや。

あ、これ実行するには拙作のwget.js(+ラッパーのバッチファイル)が必要です。ラッパー書くのめんどくさい場合は最終行の

wget http:.....
cscript //nologo wget.js http://....
のように直接JS起動にしても大丈夫ですが。

使い方

引数を1~3つとります。

最初の引数はダウンロード対象のZFのバージョンを「x.x.x」形式で指定します。たとえば、ZF1.0.0を対象とする場合は、

zfdl 1.0.0
のように指定します。

第二引数はオプションで、ダウンロードするファイルの拡張子を指定します。指定可能な拡張子は「.tar.gz」または「.zip」の2種類のみで、省略時、または他の拡張子などを指定した場合は「.zip」になります。先頭にピリオドが必要な点に注意。

第三引数もオプションで、これに「doc」と指定するとドキュメントをダウンロードします。省略時または「doc」以外のパラメータを指定した場合はライブラリのアーカイブをダウンロードします。

コマンドのサンプル

例1:1.8.1のライブラリを.zipでダウンロード

zfdl 1.8.1

例2:1.6.2のライブラリを.tar.gzでダウンロード

zfdl 1.6.2 .tar.gz

例3:1.7.8のドキュメントを.tar.gzでダウンロード

zfdl 1.7.8 .tar.gz doc

例4:1.0.0のドキュメントを.zipでダウンロード

zfdl 1.0.0 "" doc
第二引数は二重引用符が2つ重なっているだけです。

forコマンドで連続ダウンロード

まぁ、そんな需要はまずないと思うのですが、forコマンドで指定バージョンを自動的に連続ダウンロードすることもできます。

まず落としたいバージョンを列挙した、以下のような感じのテキストファイルを用意しておきます。

1.0.0
1.0.1
1.0.2
1.0.3
1.0.4
1行に1つのバージョンを記述する形式です。

で、これをforコマンドのタネにします。たとえば上記のファイルを「ver.txt」として、.zipでマニュアルをダウンロードするには

for /f %v in ('type ver.txt') do @zfdl %v .tar.gz doc
のようにします。

バッチファイルそのものにもう少し手を入れれば、ダウンロード済みのファイルは落とさないようにしておいて、勝手に最新版をダウンロードするようにもできるような気もしますが、まぁそんな必要はないか。

追記

あれ?拡張子の先頭のピリオド、いらないじゃん

さらに追記

えと、元々wget.jsの作りからして、ダウンロードする内容をいったんすべてメモリに蓄えるので、ドキュメントのダウンロードに使い方絞ったほうが安全かもしれません(dara-jの環境では1.8.1をzip形式でDLできましたが)。

ついでにいうと、圧縮率考えると、zipよりもtar.gzのほうがお勧めです。

.wmiコマンドの説明2008年10月21日 04時42分51秒

昨日の続きで、.wmiコマンドの説明でもしようかと。

起動

jsi.js環境でWQLを直接実行するための追加ドットコマンドで、jsi.jsのプロンプトから「.wmi」と入力することで起動します。

昨日の記事でも例示していますが、.wmiを起動すると、モードが通常のJSモードからWQLモードに切り替わり、プロンプト表示も変化します。

Microsoft (R) Windows Script Host Version 5.6
Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.

jsi > .wmi

WMI Console for jsi.js
server: localhost, root: root/cimv2

wmi@localhost >

.wmiコマンドには、接続パラメータを指定することもできます。指定したパラメータはWbemScripting.SWbemLocatorオブジェクトのConnectServerメソッドの第一~第四引数にそのままそのまま渡されます。具体的には以下の通り。

  1. strServer
    → ログインするコンピュータの名前を指定します。省略時は「.」、すなわちローカルホストになります。
  2. strNamespace
    → ログインする名前空間を指定します。本来は省略も可能ですが、パラメータ展開の都合で第三パラメータ以降の指定が必要な場合は省略できません。その場合はシステム標準の名前空間「root/cimv2」を指定してください。
  3. strUser
    → strServer, strNamespaceで指定されたコンピュータ・名前空間へログインするためのユーザ名を指定します。省略時は現在のセキュリティコンテキストで使用されているログイン名が使用されます。
  4. strPassword
    → strServer, strNamespaceで指定されたコンピュータ・名前空間へログインするためのパスワードを指定します。省略時は現在のセキュリティコンテキストで使用されているログインパスワードが使用されます。

リモートコンピュータ「myserver」の名前空間「root/cimv2」へアカウント「username:password」で接続するには以下のように指定します。

jsi > .wmi myserver root/cimv2 username password

WMI Console for jsi.js
server: myserver, root: root/cimv2

wmi@myserver >

WQLの実行

WQLモードでは、後述のドットコマンドを除いて、WQL以外のコマンドは実行できません。

入力方法はJSモードと同様セミコロンが出現するまでは入力モードです。末尾にセミコロンを入力することで初めてWQLによる検索を実行します。

wmi@localhost > select name, creationdate from win32_process;
// 検索結果がダンプされる
出力はデフォルトではJSON形式ですが、後述のドットコマンド「.format」でカンマ区切りまたはタブ区切りのテキストへ変更することもできます。

また、標準ではコンソールへのみ出力されますが、後述のドットコマンド「.output」でファイルへの出力も指定できます。

WQLモードを終了する場合はドットコマンド「.quit」を入力します。.quitを実行するとJSモードへ戻ります。

wmi@localhost > .quit

jsi >

ドットコマンド「.format」

ドットコマンド「.format」で出力フォーマットの確認・変更が行えます。

パラメータを指定せずに.formatを実行した場合は現在の出力フォーマットが表示されます。

wmi@localhost > .format
 - json-format

wmi@localhost >
パラメータ指定を行う場合は".format"に続けて演算子"="と有効なフォーマット名を指定します。
wmi@localhost > .format = json
 - json-format

wmi@localhost > .format = csv
 - csv-format

wmi@localhost >

.formatで指定可能なフォーマット名は以下の3種類です。

  1. "json"
    → JSON形式で出力します。ただし、1行分のデータのみが正しいJSON形式で、複数データがヒットした場合でも各データ間はカンマで区切られないので注意してください(表示のみなら特に問題なし)。
  2. "csv"
    → CSV(カンマ区切りテキスト)形式で出力します。先頭行はカラムヘッダ、それ以降の行に結果がカンマ区切りで出力されます。
  3. "tsv"
    → TSV(タブ区切りテキスト)形式で出力します。CSVフォーマット同様、先頭行がカラムヘッダ、それ以降が結果データになります。
また、.format =の後に何も入力せずに確定させると、デフォルトのJSONフォーマットに変更されます。

ドットコマンド「.output」

ドットコマンド「.output」を使用することで、検索結果が出力されるストリームを確認・構成することができます。

現在の出力ストリーム構成を確認するには、パラメータ指定なしで「.out」または「.output」と入力します。

wmi@localhost > .out
 - [0]: (standard output stream)

wmi@localhost >

ストリームの追加

デフォルトの状態では標準出力のみがストリームとして登録された状態です。以下のように、「+=」を使用してファイルストリームを追加することができます。

wmi@localhost > .out += log.txt
 - [0]: (standard output stream)
 - [1]: file stream:[log.txt]

wmi@localhost >
ファイルのパスはjsi.jsのインストールディレクトリからの相対パスまたは絶対パスで指定できます。絶対パス指定する場合は、パスを二重引用符で括らないでください

上記の構成では、標準出力および.jsi.jsのインストールディレクトリ直下のlog.txtに結果が出力されます。

ストリームの削除

追加したストリームを構成から削除するには「-=」に続けてファイル名/パスを入力してください。

wmi@localhost > .out
 - [0]: (standard output stream)
 - [1]: file stream:[log.txt]

wmi@localhost > .out -= log.txt
 - [0]: (standard output stream)

wmi@localhost >

現在のバージョンではまれにストリームが削除できなくなる場合があります。いずれ修正予定ですが、このような状態になったらいったんjsi.jsを終了してください。

まとめ

というように、WQLを実行して結果を表示するだけのコマンドで、検索結果に対してなにか処理できるわけでもないので実用性はあまりありません。

ま、リモート接続ができるので、たとえばCSVフォーマットでリモートコンピュータにインストールされているプリンタを列挙したりとか、多少は役に立つかもしれませんが、WMIのクラスリファレンス見ながら「へぇ、こんなクラスあるのかー。どんな情報取れるんだろ」を試すくらいしか使い道ないかもしれません。

またしても長めの記事になっちゃったので、ライブラリ自体の説明はまた後日。

jsi.jsに手を加えてみたり。2008年10月20日 04時33分42秒

最近なんだかネタになるようなスクリプトとか書いてる時間がぜんぜんなくてネタにこまったのでjsi.jsに手を入れてみた。

といっても、コードの修正とかは結構前にやっていたもので、どちらかというと蔵出し的な感じ。ほんとにネタに困ってるんだってば。

手を入れたところは細かいバグ修正とライブラリの追加。zipは固めなおしてリビジョンあげたので、以下からダウンロードしてください。

サンプルのライブラリ設定ファイルとして、付属ライブラリをすべてロードする設定で「libsettings.sample」を添付しているので、これを「libsettings」とリネーム、適当に編集して使ってください。

jsi.jsの変更内容

こんな感じ。

  • ドットコマンドの引数展開バグの修正
  • ライブラリロード中の例外発生時に、ロード中ライブラリファイル名も表示するよう改善
  • ドットコマンド「.wmi」を追加
    • → 後述の追加ライブラリ「wmi_console.js」を必要とします。注意。

追加ライブラリ・vbscript.js

かなり前のネタになるが、VBScriptラッパーをライブラリに追加。細かい使い方はリンク先を見てもらうとして、いくつか組み込みメソッドを提供しているのでちょろっと使えるかも。ま、こっちの記事ですでに紹介してるやつなんですが。

getTypeNameメソッド

VBSのTypeName関数のラッパーです。引数に渡したオブジェクトのCOMオブジェクト型を返します。こんな感じ。

jsi > var vb = new VBScript;
--> (undefined)

jsi > vb.getTypeName(new ActiveXObject("Scripting.FileSystemObject"));
--> FileSystemObject

jsi >

promptメソッド

VBSのInputBox関数のラッパー。ブラウザのprompt()感覚で使える。かな。

引数は2つ渡すことが可能で、第一引数にはダイアログメッセージ、第二引数にダイアログタイトルを指定できます。んで、入力された文字列か、キャンセルされた場合はnull(正確にはundefined)が返ります。

jsi > vb.prompt(); // ここでInputBoxが表示される。キャンセルしたとすると...
--> (undefined)

jsi > vb.prompt(); // こんどは'hoge'とでも入れてみると...
--> hoge

jsi >

yesNoメソッド

VBSのMsgBoxラッパーで、vbYesNo定数を渡して[はい][いいえ]の2択処理をします。引数はpromptメソッドと同じくダイアログメッセージとタイトルです。戻り値はvbYes定数(=6)かvbNo定数(=7)が返ります。もう一層ラッパーかぶせてconfirmっぽくしてもよいかも。

jsi > vb.yesNo(); // [はい]クリック
--> 6

jsi > vb.yesNo(); // こんどは[いいえ]クリック
--> 7

jsi >

getNothingとgetEmpty

まず役立つ局面はそうはないだろうけど、VBSでのNothingを取得するgetNothingメソッドもつけてます。JS上ではnullとして扱われますが、getTypeName()を通すとNothingであることが確認できます。それだけですが...

jsi > vb.getNothing();
--> (null)

jsi > vb.getTypeName(vb.getNothing());
--> Nothing

jsi > vb.getTypeName(null);
--> Null // JSのnullはNull

あと、ほとんど意味ないんですが、getEmpty()もつけてみました。VBSのEmptyを取得します。が、これ、JSのundefinedと同じなのよね。サンプル省略。

追加ライブラリ・wmi_console.js

WMIへアクセスするためのライブラリ+WQLを実行するコンソール機能を提供します。詳しい解説はそのうち書きますが、とりあえず追加のドットコマンド「.wmi」を実行してみてください。

jsi > .wmi

WMI Console for jsi.js
server: localhost, root: root/cimv2

wmi@localhost >

プロンプトがこのように変わります。この状態でWQLを入力してみます。
wmi@localhost > select name, creationdate from win32_process;
{
        "CreationDate" : null,
        "Handle" : "0",
        "Name" : "System Idle Process"
}
 :
 :
{
        "CreationDate" : "2008/10/20 03:33:09",
        "Handle" : "10008",
        "Name" : "sakura.exe"
}
{
        "CreationDate" : "2008/10/20 04:28:48",
        "Handle" : "9224",
        "Name" : "wmiprvse.exe"
}
wmi@localhost >

こんな感じでWQLの実行結果が表示されます。元のJSシェルに戻るには.quitしてください。

思ったより記事が長くなっちゃったので、この.wmiコマンドとライブラリ自体の説明はまた後日。