移動ユーザープロファイルで画面崩壊トラブル

目次

公開日 2023-11-27 更新日 -

先に結論

Windows9xのレジストリは4つの部分に分かれていて、2つが機器に属するもの、2つがユーザーに属するものです。ユーザーがBitsPerPixel(色深度)の設定をするとユーザーの部分にも記録されます。この設定は移動ユーザープロファイルにより他の機器でも継承されますが、同じ指定ができないPCで画面表示が正常にできなくなる場合があります。

この問題を Windows Script(ing) Host(以降WSH)を使って JScript を動かし、レジストリの値を変更することで回避する方法をとりました。起動時に実行されるスクリプトで次回の起動のときに使われるレジストリを変更するというトリッキーな方法でした。

解像度や色数の設定を一度もしていないユーザーは、問題が発生しないようですが、移動ユーザープロファイルの不具合と言えるでしょう。

この問題とは

別の機会に解説することとしていた、移動プロファイルの問題です。ここからは原因を探り、解決策を導き出した過程を説明します。

この問題はWindowsMe導入時にWindows98とMeの間の移動プロファイルで発覚したのですが、原因はOSの違いではありません。新規で入ったPCとのBitsPerPixel(色深度)をの違いを移動プロファイルが正しく扱えなかったのが原因です。

古いPCでテストをしていたときには意図せずにいろいろな機器の組み合わせが生じましたが、組み合わせによっては画面がおかしくなることがあり、解像度の違いかと思っていたことがありました。

解像度なら合わせればよいですが、色深度の場合は合わせられないことがある。これがこのときの問題でした。

この現象の画面は次のようなものでした。

問題が起こる条件

2000年に導入した実習機は Intel 810 チップセットの内蔵グラフィックを使用するタイプ。地元のショップブランド製です。これを i810機としておきます。

2001年に追加で導入したものは EPSON のTypeSAで、Silicon Integrated Systems社製の SiS630 チップセットの内蔵グラフィックを使用するものでした。これをSiS630機としておきます。

問題が起こるのは、SiS630機でログインしたユーザーが、i810機にログインしたときです。画面は乱れていますが、起動はできていて、初期のデスクトップの画面です。マウスも動きますが、画面のアイコンとは別の座標系になっていて、目的のアイコンをダブルクリックするのは試行錯誤で探らなければなりません。キーボード・ショートカットは使えますが、文字は見えません。

なんとかレジストリエディタを起動してファイルに値を書き出すと次のような値が見つかります。

SiS630機でログイン後、i810機にログインした場合。

[HKEY_USERS\adachi\Display\Settings]
"BitsPerPixel"="32"
"DPILogicalX"="96"
"DPILogicalY"="96"
"Resolution"="1024,768"
"AttachToDesktop"="1"
"DesktopPos"="192,144"

i810機でログイン後、再びi810機にログインした場合。

[HKEY_USERS\adachi\Display\Settings]
"BitsPerPixel"="24"
"DPILogicalX"="96"
"DPILogicalY"="96"
"Resolution"="1024,768"
"AttachToDesktop"="1"
"DesktopPos"="192,144"

この"BitsPerPixel"は1ピクセルに何ビットを割り当てるかということです。RGBそれぞれ8ビットで計24ビットを使い(28)3=16777216色を表現するフルカラーという色数が一般的でした。この24ビットを扱うのに32ビットという単位で扱う方式が出てきました。32ビットCPUになり、32ビットずつの処理が効率が良かったのでしょう。メモリの使い方は非効率になりますが、処理速度を優先したのだと思います。また、将来の色数の増加や透明度の追加のようなことを見据えたのかもしれません。

こういう事情ですから、24ビットは使用できるけど32ビットは使えない(i810がこれ)とか、32ビットの場合はメモリを食う分解像度が低くなるとか、24ビットモードは省略して32ビットしか使えない(かなり大胆だと思うがSiS630がこれ)などいろいろなタイプのものがありました。

32ビットで使えれば、24ビットでも使える(メモリが余るだけ)と思うのが普通ですが、SiS630 はそうではなかった。これが第一の問題でした。

移動プロファイルの仕様も問題です。

当時のディスプレイはまたCRTで、上限はあるものの、好みの解像度で使うことができました。文字が小さくても広く使いたいとか、逆に狭くてもいいから文字が大きいほうが見やすいなどです。ですから、ユーザープロファイルにこれを記録しておくのも自然なことです。"Resolution"="1024,768" というのがそれです。この上に、"1280,1024" とか "1600,1200" もありました。とうぜん移動プロファイルで低解像度のコンピュータに移ったときには、そこに合わせた解像度にしてくれることが期待されます。残念ながらやってみたことはありません。

15インチのディスプレイでは1024×768が適切なので、両方ともこれで使うことにすれば問題ありません。

BitsPerPixelについても、両方で対応できる16ビットにしておくと問題ありません。しかし、16ビットでは65536色になってしまうので不満です。

問題をまとめると次のとおりです。

ユーザーの移動の向き移動先OSが伝えられる値移動先OSが起動時に採用する値成功/失敗
i810機→SiS630機"BitsPerPixel"="24"SiSに24がないので、32で起動するうまくいく
SiS630機→i810機"BitsPerPixel"="32"i810に32がないにもかかわらず、32で起動してしまう画面が乱れる

起動後WSHでの変更ではだめ

乱れた画面から、マウスで画面のプロパティを出して、その後見えないながらキーボード操作で「設定」の色数や解像度を変更すれば回復します。これを起動直後に自動でやるようにすればいいのですが、GUIの操作をコマンドで実行する方法はわからないので、使い慣れたWSHでレジストリを操作することにしました。

結果を先にいうとこれではうまく行きません。

i810ならレジストリを24に書き換える(名はimecrt01.js)
//使用変数宣言
var objnet;
var objShell;
var host,user;
var bppHCU ,bppHU ,bppHLM ,bppHCC ;
var bppHCUd,bppHUd,bppHLMd,bppHCCd;
var i810;
objnet   = WScript.CreateObject("WScript.Network");
objShell = WScript.CreateObject("WScript.Shell");
//ホスト名をもとめる。
host  =objnet.ComputerName;
//ユーザー名をもとめる
user  =objnet.UserName;
//レジストリキーの名前を変数に格納しておく
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";
//レジストリキーの内容を読み出す。
bppHCUd=objShell.RegRead(bppHCU);
bppHUd =objShell.RegRead(bppHU);
bppHLMd=objShell.RegRead(bppHLM);
bppHCCd=objShell.RegRead(bppHCC);
//810機かどうかホスト名から判断する。将来増えても対応できるように
i810=false;
if (host.indexOf("SHEEP",0)>=0) i810=true;
//810ならレジストリを24に書き換える
if (i810){
   objShell.RegWrite(bppHCU,"24","REG_SZ");
   objShell.RegWrite(bppHU ,"24","REG_SZ");
   objShell.RegWrite(bppHLM,"24","REG_SZ");
   objShell.RegWrite(bppHCC,"24","REG_SZ");
   statusbpp="rewrited";
}else {
   statusbpp="ok";
}
//先に読んでいた内容と書き換えたかどうかを報告する。
WScript.Echo(host,user,bppHCUd,bppHUd,bppHLMd,bppHCCd,statusbpp);
//見えない時もあるのでファイルに記録する。
var objFS,objTS;
objFS = WScript.CreateObject("Scripting.FileSystemObject");
//追記の形でオープンする。新規なら objFS.CreateTextFile(ファイル名);
objTS = objFS.OpenTextFile("C:\\bpplog.txt",8,-1);
//c:\\をつけないとルートになるときとwindows内になるときがある。
//日付も入れる
var strDate = new Date();
strDate = strDate.toLocaleString();
objTS.WriteLine(strDate);
objTS.WriteLine(host+" "+user);
objTS.WriteLine(bppHCUd+" "+bppHUd+" "+bppHLMd+" "+bppHCCd);
objTS.WriteLine(statusbpp);
objTS.Close();

BitsPerPixel があるのはレジストリ内で4箇所です。一部を別名で呼び出す形になっていて、実質は2箇所ですが、うまくいかないので全部を調べて全部に書くようにしてあります。

SiS630機→i810機のときに上記プログラム imecrt01.js を実行したときの出力。i810機なのでレジストリ内容を読んだ後に書き換えた(rewrited)といっています。

04/27/2001 21:49:14
SHEEP33 lamb
32 32 24 24 (CU U LM CC)
rewrited

前回使用した機器がSiS630機なので、CU,Uが32になっています。LMとCCは24ですが、これではだめなようです。rewritedとなっているので、予定通りレジストリを書き換えているのですが、これは効果がなく、画面が乱れています。

32という値は、ユーザープロファイルとしてUSER.DATに書き込まれていて、ログイン時にこれを読んで採用しているらしい。ログイン後に24に変更しても間に合わないようです。

24に変更後に、このままログオフし、次にログオンしたときには、全部が24になっていてうまくいきます。その時の出力です。

04/27/2001 21:50:59
SHEEP33 lamb
24 24 24 24
rewrited

つまり、rewiteした24はログオフ時にUSER.DATに書き込まれて次のログイン時に使用されるということらしい。

USER.DATに32とある時のi810機
レジストリ値が32なので32で起動
WSHでレジストリに24を書く(現状32はそのまま)
起動後の画面は崩壊
ログアウトすればレジストリ値24はUSER.DATに書き込まれる

だったら逆にSiS630機のときに24に書換

SiS630機にログインすると32に設定されます。レジストリの値を24に変更しても反映されませんが、ログオフすればUSER.DATには24が記録されます。この状態なら次のログインがSiS630機でもi810機でも問題なくなるということです。

SiS630ならレジストリを24に書き換える(名はimecrt02.js)
//使用変数宣言
var objnet;
var objShell;
var host,user;
var bppHCU ,bppHU ,bppHLM ,bppHCC ;
var bppHCUd,bppHUd,bppHLMd,bppHCCd;
var i810;
objnet   = WScript.CreateObject("WScript.Network");
objShell = WScript.CreateObject("WScript.Shell");
//ホスト名をもとめる。
host  =objnet.ComputerName;
//ユーザー名をもとめる
user  =objnet.UserName;
//レジストリキーの名前を変数に格納しておく
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";
//レジストリキーの内容を読み出す。
bppHCUd=objShell.RegRead(bppHCU);
bppHUd =objShell.RegRead(bppHU);
bppHLMd=objShell.RegRead(bppHLM);
bppHCCd=objShell.RegRead(bppHCC);
//810機かどうかホスト名から判断する。将来増えても対応できるように
i810=false;
if (host.indexOf("SHEEP",0)>=0) i810=true;
//810ならなにもしない。さもなくばレジストリを24に書き換える
if (i810){
   statusbpp="ok";
}else {
   objShell.RegWrite(bppHU ,"24","REG_SZ");
   statusbpp="U-rewrited";
}
//先に読んでいた内容と書き換えたかをファイルに記録する。
//1回のログオンを1行に書くように改める。
var objFS,objTS;
objFS = WScript.CreateObject("Scripting.FileSystemObject");
//追記の形でオープンする。新規なら objFS.CreateTextFile(ファイル名);
objTS = objFS.OpenTextFile("C:\\bpplog.txt",8,-1);
//c:\\をつけないとルートになるときとwindows内になるときがある。
//日付も入れる
var strDate = new Date();
objTS.Write(strDate+" "+host+" "+user+" ");
objTS.Write(bppHCUd+" "+bppHUd+" "+bppHLMd+" "+bppHCCd+" ");
objTS.WriteLine(statusbpp);
objTS.Close();
SiS630機
レジストリ値が24でも32でも、32で起動
WSHでレジストリに24を書く(現状32はそのまま)
起動後の画面は正常
ログアウトすればレジストリ値24はUSER.DATに書き込まれる

エラーが出ることが判明

実運用になると、SiS630にログオンした生徒のほとんどがエラーを出してしまいました。

このエラーは、びっくりはしますが、[OK]をクリックすればここで実行が中止されるだけなので、その日の授業に実害はありません。

とりあえず、エラー処理をしてみます。VBScriptとJScriptのエラー処理は異なりますが、JScriptでは、

try {
    実行したいステートメント
}catch(変数名){
    tryの範囲でエラーが起きたときの処理
}

という形になるというので、javaから類推して進めます。変数名とあるのは本当はオブジェクト名(というかインスタンス)でしょう。e という変数名にすると、次のような値を使用できるそうです。

e.description  :エラーの説明
e.number       :エラーの番号(下位16ビット)

4箇所の、24または32の値を読むところに、try-catchを仕掛けて、エラーの説明を記録することにします。実際にエラーが起きた HKEY_CURRENT_USER には、エラーが起きたことをerroccというフラグも立てておくことにします。

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; }

エラー対策をしたスクリプト

上記の対策をしたスクリプトが最終版となりました。

エラー処理も加えたスクリプトの最終形態(名はimecrt03.js)
//imecrt03.js if no regkey no operation
//imecrt02.js if not i810 rewrite
//ReWrite BitsPerPixel for i810 machines
// 2001.4.29 ADACHI Junichi

var objnet;
var objShell;
var host,user;
var bppHCU ,bppHU ,bppHLM ,bppHCC ;
var bppHCUd,bppHUd,bppHLMd,bppHCCd;
var i810;
objnet   = WScript.CreateObject("WScript.Network");
objShell = WScript.CreateObject("WScript.Shell");
//get host name
host=objnet.ComputerName;
//get user name
user  =objnet.UserName;
//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; }
//i810 machine? or not
i810=false;
if (host.indexOf("SHEEP",0)>=0) i810=true;
//if 810 or error dont rewrite to 24
if (i810 || errocc){
   statusbpp="NOP";
}else {
   objShell.RegWrite(bppHU ,"24","REG_SZ");
   statusbpp="U-rewrited";
}
//write to file too
var objFS,objTS,strOut;
objFS = WScript.CreateObject("Scripting.FileSystemObject");
//if make new file : objFS.CreateTextFile("bpplog.txt");
objTS = objFS.OpenTextFile("C:\\bpplog.txt",8,-1);
var strDate = new Date();
//strOut = strDate.toLocaleString() +" "+host+" "+user;
//strOut = strOut+" "+bppHCUd+" "+bppHUd+" "+bppHLMd+" "+bppHCCd;
//objTS.WriteLine(strOut+" "+statusbpp);
objTS.Write(strDate+" "+host+" "+user+" ");
objTS.Write(bppHCUd+" "+bppHUd+" "+bppHLMd+" "+bppHCCd+" ");
objTS.WriteLine(statusbpp);
objTS.Close();

エラー処理の記録から判明したこと

Fish38というSiS630機から beta と e9yrisa の2人のユーザーがログインした記録です( e9yrisa は2回)。

beta は32ですが、正常に24に書き換えられました。 e9yrisa は HKEY_CURRENT_USER と HKEY_USERS でエラーを起こして、その他2つでは32という値を読み、書き換えは行われませんでした。

実際は1ログオンは1行ですが、e9yrisa は長いので、折り返してあります。

Tue May 1 15:53:01 UTC+0900 2001 FISH38 beta 32 32 32 32 U-rewrited
Tue May 1 16:06:35 UTC+0900 2001 FISH38 e9yrisa 
   レジストリ キー "HKEY_CURRENT_USER\Display\Settings\BitsPerPixel"
    のルートが無効です。
   レジストリ キー "HKEY_USERS\e9yrisa\Display\Settings\BitsPerPixel"
    のルートが無効です。 32 32 NOP
Tue May 1 16:15:30 UTC+0900 2001 FISH38 e9yrisa 
      レジストリ キー "HKEY_CURRENT_USER\Display\Settings\BitsPerPixel"
       のルートが無効です。
      レジストリ キー "HKEY_USERS\e9yrisa\Display\Settings\BitsPerPixel"
       のルートが無効です。 32 32 NOP

e9yrisa は値を書き換えないので、何度やっても同じエラーが記録されます。つまり、画面の崩壊の原因である、HKEY_CURRENT_USER\...\BitsPerPixel の値は「ない」ままということのようです。

ではこれでi810機にログインしたらどうなるでしょうか。Sheep33というi810機での記録です。

Tue May 1 16:09:51 UTC+0900 2001 SHEEP33 e9yrisa   24 24 NOP
Tue May 1 16:11:38 UTC+0900 2001 SHEEP33 adachi 24 24 24 24 NOP
Tue May 1 16:12:32 UTC+0900 2001 SHEEP33 beta 24 24 24 24 NOP
Tue May 1 16:14:11 UTC+0900 2001 SHEEP33 e9yrisa   24 24 NOP

e9yrisa はルートが無効というエラーが記録されていません。24/32のどちらも記録されていません。後ろの2つは24です。そして画面も乱れず、正常にログインできます。betaなどは、24の値が4つ並んでいますので、比較すると値が(実はkeyも)「ない」ことがわかります。

これはちょっと意外な結果でした。e9yrisa ユーザーでSiS630機からログインして画面のプロパティをいじってみました。その後再度ログインすると、beta ユーザーと同じになりました。

Tue May 1 16:16:53 UTC+0900 2001 FISH38 e9yrisa 32 32 32 32 U-rewrited

つまり、HKEY_CURRENT_USER\...\BitsPerPixel は画面のプロパティの変更をしない限り作られないキーということのようです。画面のプロパティをいじっていない生徒は24に書き換えなくても画面が乱れることはないということのようです。

画面のプロパティを設定してしまう生徒もいる可能性があるので、結論としては、HKEY_CURRENT_USER\...\BitsPerPixel のキーがあって、なおかつそれが32の場合には24に書き換えるという最終版のスクリプトは必要ということです。