WSHによるログオン時実行プログラム

ログオン時に実行されるプログラムは2つ。

ひとつは、Sambaの設定と雛形ユーザーの登録その他の設定に述べたlogon.batである。これはドメインログオンのシステムとして、自動的に実行されるようになっている。

もう一つは、システムポリシーで指定して実行するようにしているWSHのスクリプト。この設定についてはシステムポリシー通常のコンピュータのシステムの部分で述べたが内容について、ここで説明したい。

どんなことに使えるか

さまざまなことに使っており、その都度変えているので、現在の姿をそのまま見せても複雑すぎる。まず、どんなことが可能かを概観したい。

ネットワークドライブの接続
logon.bat で接続できるが、失敗することがある(なんとパスワード入力後Enterキーを2回連打すると接続しない)。このため、接続できていない時に接続をするようにしている。
レジストリの設定をする。
windowsアップデート・ネットクローリング禁止、imeユーザー辞書・色深度の設定。dontshowlastuser。(クライアントと雛形ユーザーの設定で忘れたこともこれでできることが多い)
ファイルのコピー
簡単なソフトのインストールや、教材の配布ができる。
デスクトップやスタートメニューの設定
ショートカットをつくることができる。ショートカットはファイルなのでコピー・削除でも操作できる。
起動・設定変更ログ (ローカル/サーバー)
このスクリプトで設定したことを記録する。もともとスクリプトのデバッグのためであったが、クライアントコンピュータだけでなく、サーバーにも保存できるのでコンピュータやユーザーごとの使用履歴が作れる。もちろんLinuxのシステムにもsambaにも使用履歴を取れるログがあるが、独自の内容・書式でほしい場合もある。

解説にあたって

WSHでは、VBscript と JScript の両方の形式でスクリプトを書くことができる。それぞれ Visual BASIC と JavaScript に近いという話なので、JScript を使うことにしたが、Web上の情報は VBscriptの方が多いので、これからはじめるなら VBscriptにするほうが楽かも知れない。

現在250行程度のスクリプトになっている。そのまま出しても読みにくいので、目的別に書きだしてみる。最初にある変数定義や、オブジェクトの生成は実際には冒頭で一度書けばよいのだが、その都度繰り返し書いている。いくつかの機能を組み合わせる時は、重複するものは削除する必要がある。

また、このような書換をしたあと動作テストをしていないので、細かな過不足があって動かないかも知れない。ご容赦願いたい。

msgで始まる変数は、スクリプトの後の方でまとめてログとして書き出すためのもので、本質的には不要なものである。

ネットワークドライブの接続

接続に失敗している時に接続をおこなう。msgdrv は接続の必要がどれぐらいあったか統計を取るために入れている。

var objnet;
var user;
objnet   = WScript.CreateObject("WScript.Network");
//get user name
user = objnet.UserName;

var objFS;
objFS    = WScript.CreateObject("Scripting.FileSystemObject");
//------- if shippai --------------
var msgdrv = '';
if (!objFS.DriveExists('u:')){
  objnet.MapNetworkDrive("u:","\\\\joel\\" + user);
  msgdrv = 'u:';
}
if (!objFS.DriveExists('s:')){
  objnet.MapNetworkDrive("s:","\\\\joel\\share");
  msgdrv = msgdrv+'s:';
}

NetWorkクラスのオブジェクトを作り、objnet という名前を付ける。UserNameというメソッドでログオンしたユーザーのユーザー名を得て user に格納する。

ファイルシステムのクラスのオブジェクトを作り、objFS という名前を付ける。

DriveExists というメソッドで u: ドライブが接続されているかを調べ、なければ、objnet の MapNetworkDrive というメソッドで u: ドライブの接続をする。sambaの仕組みとして \\joel\home というサービスはログオンユーザーごとに \\joel\user名 と読み替えられるのでこのように記述する。\ は エスケープ文字なので \ 自身を表すために \\ とする必要がある。

DriveExists というメソッドで s: ドライブが接続されているかを調べ、なければ、objnet の MapNetworkDrive というメソッドで s: ドライブの接続をする。これは全ユーザー共通で \\joel\share であるのでそのまま書けばよい。

msgdrv = の部分は、あとでログを出すためのもの。ここまでの内容では存在意味はない。

レジストリの設定

レジストリでなければできないものと、WindowsのGUIで変更できるが、PCの数だけ、ユーザーの数だけ設定を繰り返したくない場合に助かるという種類のものがある。後者の場合はPCのセットアップ時や雛形ユーザーの作成時にやっておけば済むことなのだが、あとになってわかることも多い。

ネットクローリングの禁止

「ネットワークのフォルダとプリンタを自動的に検索する」は クライアントと雛形ユーザーの設定 に書いたが、チェックをはずしておくべきだ。しかし、後から設定することもできる。

//------- No Net Crawling   --------------
var valname,idata;
var objShell;
objShell = WScript.CreateObject("WScript.Shell");
valname="HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced\\NoNetCrawling";
idata  ="1";
objShell.RegWrite(valname,idata,"REG_DWORD");

HKEY_CURRENT_USER でわかるようにユーザーごとの設定となる。全ユーザーがこれでログオンすればこの部分は用済みになるが、何度やっても害はないのでいまだに入れてある。ユーザーが変更しても元に戻したい時にも使える手法。

WScript.Shellクラスのオブジェクトを作り、objShell という名前を付ける。RegWriteというメソッドで、valname というキーに idata という値をセットする。キーには値のタイプがあるので合わせて REG_DWORD(32ビットの値)、REG_SZ(文字列)、REG_BINARY(バイナリ値) などを指定する。

ユーザー辞書

IME2000のユーザー辞書はユーザープロファイルの対象ではない。いまどき単語登録はそれほど必要ではないが、変換結果の学習、特に文節の切り分けの学習結果はユーザーごとに記憶してほしい。良く判らないまま独自調査して一応解決したのだが、かなり複雑である。マイクロソフトがとちった部分なのだと思う。

基本的にはユーザー辞書をユーザーごと個別の場所にコピーして、その場所をレジストリに書き込めばよい。

ただしコピーする場所が問題で、ネットワークドライブだとうまくいかない。そこで移動プロファイル項目である Application Data に入れることにした。

レジストリは雛形ユーザーで設定しておいて新しいユーザーを作るときに雛形ユーザーのUSR.DATをコピーするというやり方でだいたい間に合うのだが、ユーザー辞書ではユーザー名が陽に入って、「ユーザーの Application Data 」でなく、「雛形ユーザーの Application Data 」という情報がコピーされうまくいかない。そこでWSHが必要になる。

しかしこのWSHも特別な書き方をしなければならず、ここに解説すると全体の見通しが悪いので、別項目を設けて書くこととし、ここには現状だけ記す。

var objFS;
objFS    = WScript.CreateObject("Scripting.FileSystemObject");
var objShell;
objShell = WScript.CreateObject("WScript.Shell");

//------------------IME 2000----------------------
var ime2kHUk,ime2kval,ime2kHUd,msgime2k;
ime2kHUk="HKEY_USERS\\"+user+"\\Software\\Microsoft\\Windows\\CurrentVersion\\Ime\\Japan\\IMEJP\\Dictionaries";
ime2kval="DIC0";
newdic ="C:\\WINDOWS\\Profiles\\"+user+"\\Application Data\\Microsoft\\ime\\imejpusr.dic,1";
//read key-data before
errocc=false;
try{ ime2kHUd =objShell.RegRead(ime2kHUk+"\\"+ime2kval); 
}catch(e){
  msgime2k =e.description;
  errocc =true;
}
if ( !errocc ) {
   var hexstr="0123456789abcdef";
   objReg   = objFS.CreateTextFile("c:\\imejp.reg");
   objReg.WriteLine("REGEDIT4");
   objReg.WriteLine();
   objReg.WriteLine("["+ime2kHUk+"]");
   objReg.Write("\""+ime2kval+"\"=hex:");
   var dct=3;
   for (i = 0; i < newdic.length ; i++) {
      vat = newdic.charCodeAt(i);
      objReg.Write(hexstr.substr(Math.floor(vat/16),1));
      objReg.Write(hexstr.substr(vat % 16 ,1)+",");
      dct++;
      if (dct==25){
          objReg.WriteLine("\\");
          objReg.Write("  ");
          dct=0;
      }
   }
   objReg.Write("00,");
   dct++;
   if (dct==25){
       objReg.WriteLine("\\");
       objReg.Write("  ");
   }
   objReg.WriteLine("00");
   objReg.Close();
   //join to Registory
   objShell.Run("regedit /s c:\\imejp.reg");
   msgime2k="ime2k-joined";
}

色深度を設定する

移動プロファイルを可能にするコンピュータは学校の教材PCのようにどの機械も同じ環境でなければ使いにくい。例えばある機械でアプリケーションソフトのショートカットを作っても別の機械に同じソフトが入っていなければ使えない。

ではハードウェアではどうか。ある機械ではハードディスクが2台あってD:ドライブとなり、CD-ROMがE:ドライブになっているということは場合によっては困難をもたらす。これは容易に想像できる。

ではディスプレイの解像度はどうか。たとえば、普段1280×1024で使用していたユーザーが1024×768までしか表示できない機械でログオンした場合どうか。

これは問題がない。1024で表示される。機械の違いを吸収するのがOSの役目だ。

ところが、色深度(色数)ではこれがうまくいかない。これには苦労させられた。本校のPCが、2種類あり、一方が24ビットまで、片方が32ビットまでだが、24ビットがない。(するとハードウェアというよりデバイスドライバの問題だが)32ビットで使用していたユーザーが24ビットまでの機械にログオンすると問題が起きる。

解像度の違いを対策して色深度を問題にしなかったのはマイクロソフトが手を抜いた部分だと思う。

ただ、いつもなるわけでなく、色深度32ビットの機械で画面のプロパティを開かなければ問題は起きない。

いまどきのディスプレイアダプタで32ビットが使えないものはないと思うので、きわめて昔話になる。これもここに解説すると全体の見通しが悪いので、別項目を設けて書くこととし、ここには現状だけ記す。

var bppHCU ,bppHU ,bppHLM ,bppHCC ;
var bppHCUd,bppHUd,bppHLMd,bppHCCd;
var msgbpp;
var valname,idata;

var objShell;
objShell = WScript.CreateObject("WScript.Shell");

//set h-keys
bppHCU="HKEY_CURRENT_USER\\Display\\Settings\\BitsPerPixel";
bppHU ="HKEY_USERS\\"+user+"\\Display\\Settings\\BitsPerPixel";
bppHLM="HKEY_LOCAL_MACHINE\\Config\\0001\\Display\\Settings\\BitsPerPixel";
bppHCC="HKEY_CURRENT_CONFIG\\Display\\Settings\\BitsPerPixel";
//read key-data before
var errocc=false;
try{ bppHCUd=objShell.RegRead(bppHCU);}catch(e){ bppHCUd=e.description; }
try{ bppHUd =objShell.RegRead(bppHU); 
}catch(e){
  bppHUd =e.description;
  errocc =true;
}
try{ bppHLMd=objShell.RegRead(bppHLM);}catch(e){ bppHLMd=e.description; }
try{ bppHCCd=objShell.RegRead(bppHCC);}catch(e){ bppHCCd=e.description; }

//if error dont rewrite to 24
idata = 'nop';
if (errocc){
   msgbpp="NOP";
}else {
   objShell.RegWrite(bppHU ,"24","REG_SZ");
   msgbpp="U-rewrited";
}

ただし、ここでレジストリに書いた値はログオン時に一度読まれるだけなので、ログオンし直さなければ有効にならない。

32ビットで使用していたユーザーが24ビットまでの機械にログオンすると、起動は正常だが画面がほとんど見えないという状態になる。画面が見えないままキーボードからログオフの操作をして再ログインすると直る。(ログイン画面は対策をしなくても機械ごとの設定で表示される)

ファイルのコピー

フォルダを作ってコピーすれば使える程度の小さなソフトウェアはログオン時にコピーするようにすれば簡単に全部の機械にセットアップできる。これは機械ごとなので、すでにフォルダがあれば、コピーしないようにスクリプトを書けば無駄がない。何クラスかの授業が終われば全部の機械の設定が終わる。

下の例ではc:ドライブのprogフォルダの下にさらにwrapフォルダを作って、サーバーのprogというサービスの(sambaのサービス)wrapというフォルダから.exeという拡張子をもつもの、.icoと.optという拡張子をもつものをコピーしている。このサービスはsambaでは説明していない。説明した範囲でいえば、"\\\\joel\\share\\wrap\\*.exe" などとなる。

ソフトウェアにより、このようにローカルなドライブにコピーして使ったり、"\\\\joel\\prog\\" に置いたまま実行させたり、ソフトウェアを接続したネットワークドライブにインストールして使ったり、授業用のソフトウェアのインストールにはいろいろな方法から選択することができる。

どちらにしても全部のコンピュータの電源を入れてインストールすることに比べると極楽である。

var msgfcp;
var objFS;
objFS    = WScript.CreateObject("Scripting.FileSystemObject");
//------- file copy etc --------------
msgfcp="";
// copy wrap-*.* 
if (!objFS.FolderExists("c:\\prog")) {
   objFS.CreateFolder("c:\\prog");
   msgfcp=msgfcp+" CreateProg";
}
if (!objFS.FolderExists("c:\\prog\\wrap")) {
   objFS.CreateFolder("c:\\prog\\wrap");
   objFS.CopyFile("\\\\joel\\prog\\wrap\\*.exe","c:\\prog\\wrap\\" ,-1);
   objFS.CopyFile("\\\\joel\\prog\\wrap\\*.ico","c:\\prog\\wrap\\" ,-1);
   objFS.CopyFile("\\\\joel\\prog\\wrap\\*.opt","c:\\prog\\wrap\\" ,-1);
   msgfcp=msgfcp+" CreateWrap&Copy";
}

デスクトップやスタートメニューの設定

デスクトップのショートカットはログオン中は c:\windows\profiles\hoge\デスクトップ にあるファイルである。(デスクトップは半角カナであるかもしれない)この場所はWindowsのバージョンによって、またプロファイルの設定で異なるが、WScript.Shellクラスのオブジェクトを作って、.SpecialFolders("Desktop")メソッドを使うことで知ることができる。

また、.SpecialFolders("AllUsersDesktop")メソッドで、全ユーザーに使わせるショートカットの格納される場所も知ることができる。

この場所が判れば、.CreateShortcutメソッドを使ってショートカットを作ることができる。また、ファイルのコピーと削除を使ってデスクトップやスタートメニューをコントロールできる。

firefoxのデスクトップショートカットを変える

firefoxはオープンソースのブラウザ。現在はs:ドライブにインストールして全員がそれを使うようにショートカットをコピーしている。だから実際に必要なのは(2)だけだ。

しかし、以前個別のPCにインストールしたときにAllUsersDesktopにショートカットが入っている。この場所は一見便利だが、Meの場合は一般ユーザーが削除できるので、他のユーザーに影響が出る。だから(1)でAllUsersDesktopのショートカットを消し、(2)で各ユーザーのデスクトップにショートカットを作っている。

(2)ではショートカットが既にあれば作る必要がないのだが、歴史的な理由でプログラムのインストール場所が変わったため必ず上書きするように変更したままになっている。落ち着いたら、元に戻す。

var objnet;
var user;
objnet   = WScript.CreateObject("WScript.Network");
//get user name
user = objnet.UserName;
var objFS;
objFS    = WScript.CreateObject("Scripting.FileSystemObject");
var objShell;
objShell = WScript.CreateObject("WScript.Shell");
var objShortcut;
var stme,msgfcp;

// firefox(1) delete AllUsers sc
   stme = objShell.SpecialFolders("AllUsersDesktop");
   if (objFS.FileExists(stme + "\\Mozilla Firefox.lnk")){
         objFS.DeleteFile(stme + "\\Mozilla Firefox.lnk");
         msgfcp=msgfcp+" Delfox";
   }
// (2)create fx sc
   stme = objShell.SpecialFolders("Desktop")  + "\\firefox.lnk";
//   if ( !objFS.FileExists( stme ) ){
      objShortcut = objShell.CreateShortcut( stme );
      objShortcut.TargetPath = "S:\\prog\\fx\\firefox.exe";
      objShortcut.WorkingDirectory = "S:\\prog\\fx";
      objShortcut.save();
      msgfcp=msgfcp+" MkfoxS";
// }
// (3)create sc on QuickLaunch
// xx on Me.. stme = objShell.ExpandEnvironmentStrings("%APPDATA%\\Microsoft\\Internet Explorer\\Quick Launch")  + "\\firefox.lnk";
   stme ="C:\\WINDOWS\\Profiles\\"+user+"\\Application Data\\Microsoft\\Internet Explorer\\Quick Launch\\firefox.lnk";
   if ( !objFS.FileExists( stme ) ){
      objShortcut = objShell.CreateShortcut( stme );
      objShortcut.TargetPath = "S:\\prog\\fx\\firefox.exe";
      objShortcut.WorkingDirectory = "S:\\prog\\fx";
      objShortcut.save();
      msgfcp=msgfcp+" MkfoxQ";
   }

(3)ではクイックランチャーにアイコンを置いている。Desktopのようなスペシャルフォルダの位置を取り出すとができなかったので、生で書き込んで作っている。

OpenOffice quickbootの削除

OpenOffice.org をインストールするとスタートアップにショートカットが入れられる。スタートメニューをユーザープロファイルに入れなかったので(入れることをお勧めする)個別インストールをまだしていないユーザーでもログオン時にクイックブートが起動してしまう。

個別インストールが済んでいないと個別インストールのインストーラーが起動してやっかいなことになる。そこでそのショートカットを削除するようにした。

実はクイックブートをインストールしないようにできたのだが、気がつくのが遅かったためのスクリプトという訳である。

var objFS;
objFS    = WScript.CreateObject("Scripting.FileSystemObject");
var objShell;
objShell = WScript.CreateObject("WScript.Shell");
var msgfcp;

// OpenOffice quickboot
   stme = objShell.SpecialFolders("Startup");
   if (objFS.FileExists(stme + "\\OpenOffice.org 1.1.2.lnk")){
      objFS.DeleteFile(stme + "\\OpenOffice.org 1.1.2.lnk");
      msgfcp=msgfcp+" Del112";
   }

起動・設定変更ログ

これまで、独立しても使えるように変数宣言やオブジェクトの生成をそれぞれ重複して記した。(過不足がないかは自信がないけど。)

ここでは、上記のログを作る関係上、変数宣言やオブジェクトの生成は既に済んでいるものとしてスクリプト中に書き加えていない。

クライアント機へ

クライアント機の決められた場所にファイルを作り、ログオンごとに書き足していくスクリプト。上で使っていた msgfcp のような記録のための変数に入れられた文字列を最後に書き出すことにしている。記録日時と機械の名前、ユーザー名も記録する。1回のログインの記録が1行になると後々処理しやすい。

//write to local------------------------------------
// add to logfile
objTS = objFS.OpenTextFile("C:\\etclog.txt",8,-1);
var strDate = new Date();
objTS.Write(strDate+" "+host+" "+user+" ");
objTS.Write(msgime2k+" ");
objTS.WriteLine(msgfcp);
objTS.Close();

サーバーへ

保存場所を接続されたネットワークドライブにすると、サーバーに保存できる。こちらの場合は、クライアント機を起動しなくてもサーバー内で全部の記録を見ることができるので、便利だ。

//write to server------------------------------------
// add to logfile
objTS = objFS.OpenTextFile("u:\\log.txt",8,-1);
objTS.Write(strDate+" "+host+" "+user+" ");
objTS.WriteLine(msgdrv);
objTS.Close();
 ▲ 
聖愛高等学校
http://www.seiai.ed.jp/
Last update: 2006-03-29