わさっきhb

大学(教育研究)とか ,親馬鹿とか,和歌山とか,とか,とか.

JScriptでhostsを書き換え

はじめに

無線LANルータを買い換えました.BUFFALO Air Station NFINITI HighPower Giga 11n/g/b対応 無線LANアクセスポイント WZR-HP-G300NHです.
つなぎ替えて設定して*1,LANも無線も快適になった…
と思いきやトラブル発生.ノートPC (Windows Vista)から,LAN内のサーバに接続できません.
それまでは,自宅からでも外からでも,DynDNSで設定しているドメイン名で,アクセスできたのですが.
主な用途はSSHSubversionでアップデートもコミットもできなくなってしまいました.シェルでsshコマンドを実行するのも,しばらく待たされたあと,「Connection timed out」と言ってきます.
外には出られるので,名前解決ができればよさそうです.しかし,くだんのルータには,ホスト名とIPアドレスの登録ができそうになく,かといって,ノートPCで自宅使用時のDNSサーバをLAN内のにするのも手間です.sshコマンドだけなら,~/.ssh/configに書き加えることで対処できますが,Subversionsvn+ssh:// のあとのホスト名を,ローカルなIPアドレスに変更するというのは,やりたくありません.
無線LANルータのこの問題,軽く調査すると,やむにやまれず : Air Stationに変えたらLAN側自社サービスにアクセスできなくなったを発見しました.2年前の記事ですが,今も変更なしの可能性があります.

解決策は3つくらいありそう。

  1. 上のリンクにも書かれているけど、WAN側にあるHTTP Proxyを使う
  2. hostsを書く
  3. LAN側に上記hosts相当のDNSを立てる
やむにやまれず : Air Stationに変えたらLAN側自社サービスにアクセスできなくなった

これを読んで,そうだWindowsにもたしかhostsファイルがあるはずだから,それを書き換えることにしよう,と決めました.

Windowsのhostsはどこ?

まずはWindows環境でのhostsファイルの所在を調査.ほどなく,見つかりました.

hostsファイルが存在する位置はOSによって違います。興味がある方はエクスプローラを使って探してみて下さい。
ただし、システムファイルなのでむやみやたらにいじっては駄目です。

Windows XPの場合→「C:\WINDOWS\system32\drivers\etc」
Windows 2000の場合→「C:\WINNT\system32\drivers\etc」
Windows 95/98/MEの場合→「C:\Windows

場合によってはhostsのあとに拡張子が付いたもの、例えば「hosts.ini」などが見つかる場合がありますが、ここで言うhostsファイルは拡張子が無いものです。

higaitaisaku.com

Windows VistaもXPと同じ…まあ英字の大小はMeadowで開いたときのものに変更しておきますか…「c:\Windows\System32\drivers\etc\hosts」でした.
こういうのもありました.

2) レジストリの、 次の値はどうなっていますか。
キー: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters
名前: DataBasePath
実際に参照されるのは、 ここで指定されたパスにある hosts ファイルです。
標準では、 「%SystemRoot%\System32\drivers\etc」 という REG_EXPAND_SZ 値になっています。
あまり書き替えの必要が生じない値ですので、 書き替えられているようであれば、 ウィルス感染も視野に入れて疑ったほうが良いのかもしれません。

http://sakaguch.com/pastbbs/0034/B0017364.html#No17392

まあ今は,レジストリまで見に行かなくてもいいでしょう.
もう一つ.

有効・無効が切り替えられ*2,hostsファイルの変更監視にも対応.
対応OSにVistaや7の名前が入っていないのが気がかりです.デスクトップのVistaマシンにインストールしてみたところ,問題なく動作しました.起動直後は真っ白ですが,メニューの「ファイル(F)>現在のhostsファイルを開く(C)」を選べば,表示されました.
ともあれこれはこれとして,自分の勉強のため,プログラムを作りましょう.

JScriptを採用,そして苦労

ではプログラミング.ファイルの書き換えならRubyといきたいところですが,CygwinRubyを入れていないようなノートPCでも実行できるようにしようと検討し,JScriptを採用しました.
JavaScriptと同じ感覚で書け,Windows Script Host (WSH)の機能を使えば,ローカルファイルの読み書きや,ダイアログボックスの表示も可能です*3
実はもう一つ,JScriptを採用した理由があります.ファイルを書き換えるスクリプトファイルを,見つけたのでした.

そのプログラムをコピーさせてもらい,インデントとスペーシングは自分の好みに変更した上で,作りたい機能になるよう,コードに手を加えました.
考えたことを箇条書きで:

  • IPアドレスとホスト名の対応”を「追加するjsファイル」と「削除するjsファイル」を作る.
    • 「追加するjsファイル」を実行したとき
      • hostsファイルに“IPアドレスとホスト名の対応”がなければ,ファイル末尾に追加する.
      • hostsファイルに“IPアドレスとホスト名の対応”が(コメントでなく)あれば,ファイルは書き換えない.
      • hostsファイルに“IPアドレスとホスト名の対応”が行頭コメントになっていれば,コメントを取り除く.
    • 「追加するjsファイル」を実行したとき
      • hostsファイルに“IPアドレスとホスト名の対応”がなければ,ファイルは書き換えない.
      • hostsファイルに“IPアドレスとホスト名の対応”が(コメントでなく)あれば,行頭にコメントを付ける.
      • hostsファイルに“IPアドレスとホスト名の対応”が行頭コメントになっていれば,ファイルは書き換えない.
  • hostsファイルに複数の“IPアドレスとホスト名の対応”はないものとする.
  • hostsファイルのそれ以外の(挿入・削除したいホスト名文字列を含まない)行は,そのままとする.
  • hostsファイルは小さいこともあり,処理の簡単化のため2パスで行う.すなわちまずhostsファイルを読み出して,出力する内容を文字列として保持し,ファイルを閉じる.書き換える必要があれば,hostsファイルを書き込み用に開き,流し込む.

それでコーディングですが,“IPアドレスとホスト名の対応”があるとかないとか,コメント付きだとかを,検出する方法のところで,苦労しました.しばらくInStrというのを試していたのですが,常にエラーです.どうやらこれはJScriptではなくVBScriptの関数なのだと気づいて,正規表現マッチングに切り替えると,まあできるようになりました.
しかし考えてみれば,探す対象(ホスト名)は固定文字列なので,正規表現に頼ることなく,もっと手軽な方法があるだろうと再度探すと,文字列に対するindexOfメソッドを見つけました.RubyのString#indexとほぼ同じですが,文字列が見つからなかったときの戻り値が-1である(Rubynil)点だけ要注意です.

完成したスクリプトファイル

/* -*- coding: shift_jis-dos -*- */
/* addhost.js : IPアドレスとホスト名の規則を追加 */

// 書き変えたい内容
//var file_hosts = "hosts";
//var file_to = "hosts_to";
var file_hosts = "c:\\Windows\\System32\\drivers\\etc\\hosts";
var file_to = file_hosts;
var str_host = "sokonoke.sokonoke.asokonoke";
var str_ipaddr = "192.168.aaa.iii";
var str_line = str_ipaddr + " " + str_host;

// ファイル存在チェック
var fso = WScript.CreateObject("Scripting.FileSystemObject");
if (!fso.FileExists(file_hosts)) {
    WScript.Echo("ファイルが存在しません。");
    WScript.Quit();
}

// 読み込み
var content = "";
var found = 0; // 0:存在しない,1:存在する,2:コメントされていた
var txt_r = fso.OpenTextFile(file_hosts, 1);
while (!txt_r.AtEndOfStream) {
    var str = txt_r.ReadLine();
    if (str.indexOf(str_host) >= 0) {
	if (str.indexOf("#") == 0) {
	    WScript.Echo(str_host + ":\r\nコメントされていました。\r\nコメントを取り除きます。");
	    content += str_line + "\r\n";
	    found = 2;
	} else {
	    WScript.Echo(str_host + ":\r\n見つかりました。\r\n修正しません。");
	    content += str + "\r\n";
	    found = 1;
	}
    } else {
	content += str + "\r\n";
    }
}
txt_r.Close();

if (found == 0) {
    WScript.Echo(str_host + ":\r\n見つかりませんでした。\r\n規則を追加します。");
    content += str_line + "\r\n";
}

// 書き出し
if (found != 1 || file_from != file_to) {
    var txt_w = fso.CreateTextFile(file_to);
    txt_w.Write(content);
    txt_w.Close();
    WScript.Echo(file_to + ":\r\n置換終了");
}
/* -*- coding: shift_jis-dos -*- */
/* delhost.js : IPアドレスとホスト名の規則を削除 */

// 書き変えたい内容
//var file_hosts = "hosts";
//var file_to = "hosts_to";
var file_hosts = "c:\\Windows\\System32\\drivers\\etc\\hosts";
var file_to = file_hosts;
var str_host = "sokonoke.sokonoke.asokonoke";
var str_ipaddr = "192.168.aaa.iii";
var str_line = str_ipaddr + " " + str_host;

// ファイル存在チェック
var fso = WScript.CreateObject("Scripting.FileSystemObject");
if (!fso.FileExists(file_hosts)) {
    WScript.Echo("ファイルが存在しません。");
    WScript.Quit();
}

// 読み込み
var content = "";
var found = 0; // 0:存在しない,1:存在する,2:コメントされていた
var txt_r = fso.OpenTextFile(file_hosts, 1);
while (!txt_r.AtEndOfStream) {
    var str = txt_r.ReadLine();
    if (str.indexOf(str_host) >= 0) {
	if (str.indexOf("#") == 0) {
	    WScript.Echo(str_host + ":\r\nコメントされていました。\r\n修正しません。");
	    content += str + "\r\n";
	    found = 2;
	} else {
	    WScript.Echo(str_host + ":\r\n見つかりました。コメントにします。");
	    content += "#" + str + "\r\n";
	    found = 1;
	}
    } else {
	content += str + "\r\n";
    }
}
txt_w.Close();

if (found == 0) {
    WScript.Echo(str_host + ":\r\n見つかりませんでした。\r\n修正しません。");
}

// 書き出し
if (found == 1 || file_hosts != file_to) {
    var txt_w = fso.CreateTextFile(file_to);
    txt_w.Write(content);
    txt_r.Close();
    WScript.Echo(file_to + "\r\n置換終了");
}

結果

ノートPCにコピーして,変数str_host,str_ipaddrの値をまともなものにし,addhost.jsをダブルクリックすれば*4,hostsが書き換えられ,sshで無事に入れるようになりました.delhost.jsをダブルクリックすれば,sshで入れなくなりました.
また,書き換える必要がないときには,たしかに書き込みを行っていないことも,確認しました.

*1:小さなトラブルを解決したのでメモ:サーバ設定ページには認証がかかっていて,出荷時設定のユーザ名は(内緒),パスワードは空っぽなのですが,Fireboxで入力する打ち込む際には,(内緒),Tab,Tab,Enterではだめで,(内緒)のあと即Enterとすれば,入ることができました.

*2:無効でも規則が削除されるわけではないのですね.

*3:JScriptWSH他との関係を手早く知るには,http://www.atmarkit.co.jp/fwin2k/tutor/cformwsh01/cformwsh01_01.html

*4:なお,書き換え対象のhostsファイルは,システムファイルなので,管理者の権限が必要です.