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するとか、まぁそんな感じでしょうか。

evalとTextStream.OpenTextFileのナゾ2009年01月27日 04時12分05秒

久々にスクリプトネタ

ずいぶんと古い記事なんだけど、「主に言語とシステム開発に関して」さんからリンクしていただいたので便乗してWSHネタ。

話題としてはJScriptで外部ライブラリをロードする手法に関してで、おっしゃるとおり

WSF形式にする。  ←これがベスト ※後述
なのは確かなんだけども、サンプルであげられてたTextStreamオブジェクトのエラーについてちょっと気になったので。

eval()は確かにダメだけどセキュリティは関係ないっす

「主に言語とシステム開発に関して」さんでは、こんなサンプルコードをあげられていた。

// evalを使う
var str = "var fso = WScript.CreateObject('Scripting.FileSystemObject');" +
    "fso.CreateTextFile('b.txt');" +
    "var txt = fso.OpenTextFile('b.txt',2);" +
    "txt.WriteLine('hoge');" +
    "txt.Close();"
eval( str ); 


で、これ実行すると確かに「エラー:書き込みできません。」とでるんだけども、これ、セキュリティが原因じゃないっす。

ためしにこんな風に変えてみたり、

var str = "var fso = WScript.CreateObject('Scripting.FileSystemObject');" +
    "var txt = fso.CreateTextFile('b.txt');" +
    "txt.WriteLine('hoge');" +
    "txt.Close();"
eval( str ); 

またはこんな風に変えてみたり
var str = "var fso = WScript.CreateObject('Scripting.FileSystemObject');" +
    "fso.CreateTextFile('b.txt').Close();" +
    "var txt = fso.OpenTextFile('b.txt',2);" +
    "txt.WriteLine('hoge');" +
    "txt.Close();"
eval( str ); 

すると、エラーにならず、意図どおりに書き込める。要するに、「主に言語とシステム開発に関して」さんのサンプルのエラーはCreateTextFileで書き込み権付で開いたストリームを閉じていないので排他がかかってるということではないかと。

だもんで、eval()方式だからといって、ファイルI/Oを扱うライブラリが使えないということはないんです。実際、dara-jはそういうライブラリ作って、eval()方式で読み込んでつかってるし。

まぁ、eval()がダメな2番目の理由が本当に致命的で、関数化できるのはファイル読み込みくらいまでだもんだから、素直にWSF使ったほうが精神衛生上よろしいのは確かなんですが。

で、ナゾが残る、と。

と、ここまで書いてハタと気づいたんだけども、さっぱり理屈がわからないのだが、「// evalを使わない」ほうのサンプルでは、同じようにCreateTextFile()で開いたストリーム放置しててもエラーがでないんですよ、これが。なんで???

まぁ、コールスタックの数によってセキュリティ警告がでたりでなかったりという不可思議な現象もあるJScriptの環境だから、「そんなこともあるのかぁ」と納得しておくことにしようか。つか、そんな内部の挙動を調べるスキルなんかないし。

.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コマンドとライブラリ自体の説明はまた後日。

FORコマンドで遊んでみる。2008年08月04日 01時52分38秒

いや、実用性はないんだけども、前のエントリで覚えたばかりのFORコマンドと、ちょっと前に作ったなんちゃってコマンドを組み合わせて遊んでみたり。

chkconfig.jsの--detailを連続で

chkconfig.jsにオマケでつけてた--detailオプション、サービス名を指定しないと使えない仕様なんだけども、これをFORコマンドと組み合わせて使ってみると。

コマンドラインからcscript付きで指定するとちょっと冗長なので、ラッパーのバッチファイルを作っておくことにしよう。こんな感じで、「chkconfig.bat」とか「chkconfig.cmd」とか。

@echo off
cscript //nologo "%~dP0\scripts\chkconfig.js" %*

dara-jは特定のディレクトリにバッチ(というか.cmdファイル。単なる好みだけど)を設置することにしていて、そこにサブディレクトリ「scripts」を切って.jsはそこに置くようにしているので、chkconfig.jsのパス指定で「%~dP0\scripts\」としている。んで、バッチをおいたディレクトリにパスを通している。

で、chkncofig --listでサービスを列挙した後にfindstr Manualで手動起動設定になっているサービスのみに絞り込んで、それらに対してchkconfig --detailを適用してみる。

C:\Documents and Settings\dara-j>for /f "tokens=1" %d in ('"chkconfig --list | f
indstr Manual"') do @chkconfig --detail %d
サービス名 : AppMgmt
表示名     : Application Management
スタートアップの種類 : 手動
状態       : 停止中
説明 :
  割り当て、公開、削除などのソフトウェアのインストール サービスを供給します。

サービス名 : aspnet_state
表示名     : ASP.NET 状態サービス
スタートアップの種類 : 手動
状態       : 停止中
説明 :
  ASP.NET の out-of-process セッション状態のサポートを提供します。このサービスが
中止されると out-of-process 要求は処理されません。このサービスが無効になると、こ
のサービスに明示的に依存しているサービスはすべて開始できなくなります。
 :
(中略)
 :
サービス名 : xmlprov
表示名     : Network Provisioning Service
スタートアップの種類 : 手動
状態       : 停止中
説明 :
  自動ネットワーク提供のための XML 構成ファイルをドメインごとに管理します。


C:\Documents and Settings\dara-j>

結構重たい上なんの役にも立たないんだけども。

FORコマンド中で「tokens=1」と指定しているのは、chkconfig --listの出力結果からサービス名のみを抽出するため。FORコマンドの既定の区切り文字(空白とタブ、らしい)で各行がトークン単位に分割されるのだが、chkconfigの出力の場合は、先頭がサービス名になるのでそのように指定している。

killでメモ帳を全て終了

今度は多少は使い道あるかなぁ。こちらの「ps.jsとps.cmd / kill.cmd」で作ったpsとkillを使って、特定のプロセスをまとめて終了させると。

まず適当にnotepadをいくつか起動する。

C:\Documents and Settings\dara-j>ps | findstr notepad
 1456  5788 dara-j               4708     4708    0:00:00 notepad.exe
 5316  5788 dara-j               4852     4852    0:00:00 notepad.exe
 4228  5788 dara-j               4716     4716    0:00:00 notepad.exe
 4992  5788 dara-j               4720     4720    0:00:00 notepad.exe
 5544  5788 dara-j               4696     4696    0:00:00 notepad.exe

C:\Documents and Settings\dara-j>

こんな感じで5個のメモ帳が動いてるとして、
C:\Documents and Settings\dara-j>for /f "tokens=1" %d in ('"ps | findstr notepad
"') do @kill %d

C:\Documents and Settings\dara-j>

ってな感じでnotepad.exe全殺し。やっぱ役に立たないかな。