2024年8月9日金曜日

X68030の電源 SH4 のATX化、バックアップ充電池交換

長年眠っていた X68030 本体と関連グッズを手放すことにし、動作チェックを行っていましたが、電源が故障してしまいました。電源を取り外し、コンデンサなどの部品を順次取り換えてみましたがなかなか手ごわく、復活しません。

いろいろ調べてみるとX680x0の電源SH4のATX化がいくつか見つかり、試してみたところ、おかげさまで起動することができるようになりました。

まずはお礼を兼ねて、丁寧な図を載せてくださったmojiraさんのサイトをご紹介。
作成手順はこちらのサイトをご覧ください。

X68000電源換装
http://mojira68.blog24.fc2.com/blog-entry-1.html

類似の製品がbeep-shopなどで販売されてるようですが、品切れ状態が続いているので私も自作することにしました。

これは以前に満開製作所が作ったATX/SH4変換ケーブルがもとなっているようです。電源自体は対応する電圧どうしを繋ぐだけですが、ATXのPower Onのシグナルが負(GND)に対し、SH4は正(+)状態と反対なため、ロジックICで反転させています。これによりX68030 本体のスイッチで電源ON/OFFが行えます。

X68030 は電源スイッチをOFFにしたあと、フロッピーの状態のチェックなどを行ってから電源が切れます。この間LEDが点滅しますが、これもちゃんと行われます。

手っ取り早く代用品が必要な場合は、SH4とATXで対応する電圧の線を繋ぎ、ATXのPower OnをGNDにつなげば使えるかもしれませんが、乱暴な電源ON/OFFは本体にダメージを与えかねません。ロジックIC(7404)が入手できるなら先人の成功例に倣うのがよいでしょう。

最初、電源ON後にちょっとだけフロッピードライブの動作音がしたあとディスクを読みにいきませんでした。フロッピーディスクドライブのフラットケーブルはSCSIモジュール経由で本体底面に差しますが、右タワー用の二極コネクタを穴を通して渡すときに抜けてしまっていました。動作確認のときはコネクタが全部繋がっているか、確認しましょう。

とりあえずは元の電源のケーブルを利用して作成しましたが、元の電源も修理したかったのでコネクタ類を調達し、作成し直しました。

以下、調達した部品のまとめです。
価格は2024年7月時点で、税抜、税込混在していますので、目安とお考え下さい。

ATX電源
200Wの小ぶりなSFX電源でも動作しました。ただし、私のX68030にはハードディスクがありません。

DELTA ERECTONICS, INC. DPS-200PB
メルカリで中古1,200円で購入。サーバから取り外した電源とのことで、そのためかいささか音が大きい。継続的に使用するにはファンの交換が必要でしょう。

IN WIN POWER MAN IP-P300DF1-0
所持していたので試しました。こちらは300Wで音も静かです。

mojiraさんの記事では20ピンのATX電源ですが、もちろん24ピンでもかまいません。私はフロッピードライブ用の3本を追加の4ピンの方とつなぎました。

ちなみにATX電源には同じ電圧のピンがいくつもありますが、回路別に電流を分散させるのが目的で、根っこは同じところにつながっているようです。

ATXコネクタ
SH4代用電源専用とするなら直接線どうしを接続してしまうこともできますが、そうすると移動するときに本体と外部電源を一緒に運ばなければいけません。やはりコネクタを付けた方がよいでしょう。

ATX電源側がオスなので、メスのコネクタが必要です。
mojiraさんと同じく、ATXオスメス変換ケーブルを購入しました。
Amazonで400円前後で購入できます。

6ピンコネクタ 3.96mmピッチ
同じ形状のものが通販で見つかりました。後述の山本無線でも扱っていました。

若松通商 VHR-6N 40円 (コンタクトピン付き)
共立エレショップ  VHR-6N 16円 (コンタクトピン別)

2ピンコネクタ 3.96mmピッチ
右タワー用の5V電源用。
秋葉原駅前ラジオセンター2Fの山本無線電材店で同じ形状のものが見つかりました。

モレックス 5195-02 50円
コンタクトピン モレックス 5194TL 15円/本

型番が分かるとMarubeni Online Shopで通販でも購入できるのがわかりましたが、その前に試しに似たものを購入しました。一応代用にはなります。ただし、+-を差し間違えないよう気を付けましょう。

若松通商 VHR-2N 26円 (コンタクトピン付き)
共立エレショップ  VHR-2N 11円 (コンタクトピン別売)

3ピンコネクタ 2.5mmピッチ 
コンタクトピンの仕様が異なりますが、サイズ、ピッチが同じものが山本無線電材店にありました。

AMP171822-3 50円 コンタクトピン別売 

試しに購入したものはちょっと小さくお薦めできませんが、とりあえず動作確認は行えました。これも+-を差し間違えないよう気を付けましょう。

共立エレショップ  EHR-3 11円 (コンタクトピン別売)

ロジックIC 7404
汎用の7404を購入しました。

若松通商 M53204P (7404) 64円

------------------------------------------------------

ATX電源で本体の動作が確認電できたところで、SH4電源の修理を再開したところ、どの部品が決め手になったのかわかりませんが、修理に成功しました。

なにぶん素人なもので部品全取り換えまでは行わず、電解コンデンサ、ツェナーダイオードと、問題ありそうな部分だけ取り換えています。

問題部分を探すのに、Kunihiko Ohnakaさんの
X68000シリーズ電源の部品面から見た抵抗配置図https://drive.google.com/file/d/1aXbfzF_zwfEswQ6yfBAbI7jxxhpNHxB6/view
が役立ちました。

「部品実装状態での実測値」を見ながら測定したところ、R37が絶縁状態になっていたのと、R4が怪しい値でした。おかしくなるには理由があるわけで、その周辺の部品を交換しています。どの部品が問題だったかまでは突き止めていませんが、ZD32、D33、D35などを交換しています。

SH4電源修理のためにコンデンサ、抵抗、ダイオード等のセットをメルカリで購入しましたが、それに含まれていなかったものを探しました。

[]内の名称は配線図の部品番号と部品名です。
なお、互換性は私個人の判断ですので、保証の限りではありません。

ツェナーダイオード
[ZD51 HZS15-1L] 
HZ15-1, HZ15-1TA 14.1~14.7 500mW 若松通商 53円

FET
[Q1 2SK643/2SK724/2SK735]
2SK735 若松通商 481円

------------------------------------------------------

ついでながら、ディスプレイケーブルのDsub15ピン2列を3列のRGBケーブルに変換することも試しました。通販で部品と取り寄せるとき、送料の方が部品代より高くついてしまうので、ついでに買うものはないかとディスプレイのコネクタも追加したからです。

RGBケーブルが一本余っていたので、これのコネクタ部分を切り離し、2列のコネクタに接続しました。

X68030はVキーを押しながら起動すると640x480モードになります。線を繋ぎ替えるだけで液晶ディスプレイへ表示することができました。

Dsub15ピン2列のアナログディスプレイケーブルと3列のRGBケーブルのピンアサインの対照は次のようなWebページに載っています。

VGAケーブルを作って見た。(2列←→3列 D-SUB15ピン)
http://rdstyle.cocolog-nifty.com/gm/2020/02/post-3324c0.html#google_vignette

VGA端子のピンアサインと形状
https://ameblo.jp/holycater/entry-12467338511.html

既製品のRGBケーブルは製品により配線の具合が異なるようです。私が使ったのものは6,7,8ピンのRGBのGRDがケース、シールド線と接続されており、線としては出ていません。RGB信号線が個別にアルミ箔でシールドされ、その中に細い線が一本はいっており、これがGNDと接続されています。そこで、この細い線を2列コネクタRGB GNDに接続しました。これらはみなGRDとどこかで接続されています。シールド線は2列コネクタのケースに接続しました。これで液晶ディスプレイに表示できました。

私の場合の配線の対応は次のようになりました。

3列             2列
1                1
1GRD          2
2                3
2GRD          4
3                4
3GRD          5
13               14
14               15
シールド        ケース

ただ、解像度が640x480なので横長ディスプレイは使い勝手はよくありません。ブラウン管ディスプレイが壊れたときの緊急対応用に保管しておきます。

------------------------------------------------------

一難去ってまた一難で、フロッピーディクドライブが不調になりました。

「システムを起動できませんでした。リセットして下さい。」
「エラーが発生しました。リセットして下さい。」

などが発生します。ドライブを取り外してグリスを塗ったりしていたので、何か悪さをしてしまったかた、再度チェック。さらに分解するとかえってとりかえしのつかない状態になりそうなので、いったん諦める。

いろいろ調べると、SRAMのバックアップ電池が切れているとこの症状がでるそうでそれを交換したら直ったという記事を見つけました。

SHARPパソコン X68000 EXPERT-HDのバックアップ電池交換http://kanchan707.web.fc2.com/X68KBATT.htm

X68030の底蓋を外してメインボードを取り出し、バッテリーをとりはずします。バッテリーの型はPanasonicのVL-1220/HFK。これとズバリ同じものはがみつからず、端子タイプが異なるVL-1220/VFKをAmazonで購入。ただいま到着を待っています。AliExpressにVL-1220/HFKがあるにはあったのですが、値段も安くなく到着がいつになるかわからなのでやめておきました。

ちなみにML-1220というのもありますが、VLがバナジウムリチウム二次電池に対し、MLはマンガンリチウム二次電池で、仕様を見ると出力電圧はどちらも3Vですが充電電圧が異なります。発火の原因になるといけないので、MLで代用するのはやめた方がよさそうに思います。

さて、バッテリーが届いたのでさっそく交換する。端子タイプが縦型だが、折り曲げるとうまい具合に穴にはいった。

交換後すぐは以前と同じ状態が発生。これで諦めるのはつらいので、再度ドライブを取り外してコネクタをしっかりはめ直してみる。おお、起動した!

バッテリーの交換が功を奏したのか、コネクタの接続が甘かったのか定かではないが、いずれにせよ古いバッテリーは電圧が0.3Vくらいで寿命が尽きていたので、交換が必要だったのは間違いない。



2024年6月2日日曜日

Windows 11 Excel VBA で InternetExplorerが使えなくなったことへの対応

Excel VBAでこれまで使えていた InternetExplorerがWindows 11で使えなくなりました。(と思ったら、また使えるようになっていた。よくわからないが、いずれは使えなくなる前提でコーディングしておいた方がよいでしょう。)

Excelはサポート切れの2010と、ちと古いです。

ちょっと調べると、代替え策としてフォームのWebBrowserを使う方法が出てきます。Navigateでurlのページを表示するだけならこれが良さそうです。ですが、DOMのメソッドを使って操作する場合、使えないメソッドがいくつかあります。たとえば

でエラーが発生します。他にも使えないメソッドがあるかもしれません。

これへの解決策として、次のコードのようにDocument.Allを使って全エレメントを取得し、個々にnameやclassNameをチェックする方法が出てきます。

    Dim elm As HtmlElement     For Each elm In WebBrowser1.Document.All I       if elm.GetAttribute("className") = "someName" Then             Debug.Printh elm.InnerText         End If     Next

これは単発で使うには十分かと思いますが、ループで使用するにはいささか重い処理になりそうで、検討の余地ありです。加えてclassには複数の値が設定されることが多く、"="での比較はほとんど場合に不適切でしょう。

ちなみに、「このページのスクリプトでエラーが発生しました」が発生する場合は、navigateを呼ぶ前に次の行を追加します。

    WebBrowser1.Silent = True     

次に見つけたのはMSXML2.XMLHTTPを使う方法です。次のようなコードでXmlDocumentを作ります。

    Dim MyRequest As Object     Set MyRequest = CreateObject("MSXML2.XMLHTTP")     MyRequest.Open "GET", url     MyRequest.send     Do Until MyRequest.readyState = 4: DoEvents: Loop     Set xmldoc = MyRequest.responseXML

これで成功する場合は良さそうですが、これは TLS 1.2 以上には対応していないという問題があるようです。次のようにするとよいという指摘があります。

    Set MyRequest = CreateObject("MSXML2.ServerXMLHTTP")

私は MSXML2.XMLHTTP.6.0 を使いましたが、今のところ問題ありません。

    Set MyRequest = CreateObject("MSXML2.XMLHTTP.6.0")

なお、MSXML2.XMLHTTP.6.0 でエラーが出る場合は参照を追加します。

    ツール⇒参照設定⇒Microsoft XML v6.0追加

さらに調べた結果、次の方法が見つかりました。これはHTMLをDOMDocumentとして取得し、それからHTMLDocumentを作ります。

次のFunctionを作り、実際に使用しています。

Public Function GetHTMLDoc(url As String)     Dim httpReq As Object     Set httpReq = CreateObject("MSXML2.XMLHTTP.6.0")     httpReq.Open "GET", url, False     httpReq.send (Null)     Do Until httpReq.readyState = 4: DoEvents: Loop     Dim htmlDoc As IHTMLDocument     Set htmlDoc = New HTMLDocument     htmlDoc.Write httpReq.responseText     Set GetHTMLDoc = htmlDoc End Function

この方法はWebページを表示する必要がなく、DOMメソッドで操作するのが目的な場合に適しています。表示を行わないため、処理は高速です。表示も必要な場合は、WebBrowerにHTMLをセットすればよいでしょう。

さて、これで作った HTMLDocument ですが、ちょっと奇妙な動きをします。

htmlDoc.GetElementsByClassName(className)

は動作します。(先頭の"G”は大文字です。)

ですが、個々の HtlmElement には getElementsByClassName を適用できません。

この範囲で十分であれば、従来のIEと同様の方法で利用することができます。

ですが、ある Element の子要素から getElementsByClassName で要素抽出をしたい場合には処理が複雑になります。

そこで、次のようなFunctionを作りました。あるエレメント内の指定のtagNameのものからclassNameを含むものを抽出します。

Public Function GetElementsByClassName(htmlElm As IHTMLElement, tagName As String, className As String)     Dim elm As Object, attr As String, list As Collection     Dim re As New RegExp: re.pattern = "(^| )" & className & "( |$)"     Set list = New Collection     For Each elm In htmlElm.getElementsByTagName(tagName)         If re.test(elm.getAttribute("className")) Then             list.Add elm         End If     Next     Set GetElementsByClassName = list End Function

正規表現はclass属性の最初、または最後か、前後がスペースで区切られているものとマッチします。

    re.pattern = "(^| )" & className & "( |$)" 

戻り値の型はCollectionになります。インデックスを使う場合、配列と異なり先頭の要素のインデックスは 1 になることに注意してください。

加えて、次のようなFunctionも作りました。私の場合、getElementsByClassNameで見つかった最初の要素を使うことが多く、IEを使った処理では次のようなコードでした。

    name = someElm.getElementsByClassName("name")(0).innerText

そこで、Collectionで返さず最初に見つかった要素を返すようにしました。

Public Function GetFirstElementByClassName(htmlElm As IHTMLElement, tagName As String, className As String)     Dim elm As Object, val As String     Dim re As New RegExp: re.pattern = "(^| )" & className & "( |$)"     For Each elm In htmlElm.getElementsByTagName(tagName)         If re.test(elm.getAttribute("className")) Then             Set GetFirstElementByClassName = elm             Exit Function         End If     Next     Set GetFirstElementByClassName = Nothing End Function  

呼び出し方は次のようになります。

    name = GetFirstElementByClassName(someElm, "div",  "name").innerText

戻り値がNothingとなる場合があるなら、次のようなチェックを入れます。

    Set elm = GetFirstElementByClassName(someElm, "div",  "name")     If Not elm Is Nothing Then name = elm.innerText

HtmlDocumentの先頭からclassNameのものを探したい場合は htmlDoc.body または htmlDoc.DocumentElement を htmlElm として渡します。

    divs = GetElementsByClassName(htmlDoc.body, "div",  "someClass")

私の場合はclassNameでエレメントを抽出する場合はtagNameも決まっているのでこの方が都合がよく、かつ処理速度も多少なりとも速くなっているだろうと思います。

もし異なるtagNameのものも含めて抽出したい場合は、たとえばtagNameにvbNullStringを渡し、次のような使い分けをすればよいでしょう。

    Dim elms as IHTMLElementCollection
    If tagName = vbNullString Then
        Set elms = htmlElm.all
    Else
        Set elms = htmlElm.getElementsByTagName(tagName)
    End If

結果的にはWebBrowserによる表示が不要なため、この変更で処理速度は格段に速くなりました。

補足

この例で生成する HtmlDocument にはいくつか制約があります。

・sectionなど非対応のtagがあり、その場合はHTMLUnknownElementとなり、単独のElementとして作られるがDOM構造には含まれず、getElementsByTagName、allなどのメソッド/プロパティーが使用できない。

原因ははっきりしませんが、フリーズすることがありました。速度が速くなりすぎ、Excelの再描画追いつかないためかもしれません。そんなときは再描画の一時中止/再開を試してみてください。  

    Application.ScreenUpdating = False         再描画が発生する処理     Application.ScreenUpdating = True 

 

2024年5月26日日曜日

Mac インストールUSBドライブ作成エラー ...app does not appear to be a valid OS installer application.

Webページ https://support.apple.com/ja-jp/101578 を参照し、
Macbool Air 2011 A3169 用に High Sierra のインストールUSBドライブを作成しようとしたらエラーが発生。

Webページ「ターミナルを使って起動可能なインストーラを作成する」に記載されているHigh Sierra用のコマンドをそのままコピペ(MyVolumeは適宜変更)でターミナルで実行するとエラーが出る。

コマンド
sudo /Applications/Install\ macOS\ High\ Sierra.app/Contents/Resources/createinstallmedia --volume /Volumes/MyVolume

エラー
/Applicaiont/macOS\ High\ Sierra.app does not appear to be a valid OS installer application.

これは日本語環境でAppStoreからインストーラをダウンロードするとインストーラの名前が

Install macOS High Sierra.app

ではなく

Install macOS High Sierraインスール.app

となり、異なっているため。どうも"Install macOS High Sierra.app”という名前をチェックしているようなので、ファイル名を英語表記に合わせて変更しないとダメなようだ。

MyVolumeは実際のターゲットのマウントポイント名に変更するが、実際のマウントポイントが表示名と異なることがある。この場合は見た目は一致していてもマウントポイントが見つからないというエラーが出る。

ディスクユーティリティーでUSBドライブのプロパティーを見て、コマンドのMyVolumeをマウントポイントに一致させる。

あるいはディスクユーティリティーでUSBドライブを消去し、名前を”MyVolume”でフォーマットすればコマンドはそのままでよい。

MyVolumeは createinstallmedia 実行後に既定の名前に変更される。










2024年4月7日日曜日

C# FormアプリでMicrosoftのライブラリだけでheic画像表示

WPFではサポートされてもFormアプリではではダメなものがあります。

Imageのheic対応もそのひとつで、次のようなコードでheicファイルを表示しようとすると、メモリ不足のエラーが発生します。

pictureBox1.Image = Image.FromFile(mediaItem.FilePath);

Webでheic表示関連を検索するとMagick.NETを使う例が見つかりますが、Microsoftのライブラリだけで行う方法はまだ見つかりませんでした。Magick.NETは様々なフォーマットを扱える優れたものだと思いますが、dllをアプリに含めると、小さいなアプリでは本体よりMagick.NETの方が大きくなってしまいます。heicサポートの追加だけが目的な場合はあまり好ましくないので、Microsoftのライブラリだけで行う方法をいくつか試してみました。

なお、前提としてWindows 10, 11にHEIF画像拡張をインストールしてあるものとします。HEIF画像拡張がインストールされていない場合はMicrosoft Store から ダウンロードできます。

方法1
BitmapImage
BitmapEncoderを使用する。
今のところ、これが一番良好な結果になっています。
次のようなステップで行います。

System.Windows.Media.Imaging.BitmapImageをファイルから作る。BmpBitmapEncoderでMemoryStreamに書き出す。
MemoryStreamからSystem.Drawing.Imageを作る。
pictureBox1.Imageにセットする。

コードは次のようになります。

            BitmapImage bImage = new BitmapImage();
            bImage.BeginInit();
            bImage.CacheOption = BitmapCacheOption.None;
            bImage.DecodePixelHeight = height;
            bImage.DecodePixelWidth = width;
            bImage.UriSource = new Uri(FilePath, UriKind.Absolute);
            bImage.EndInit();
            using (MemoryStream ms = new MemoryStream())
            {
                BitmapEncoder enc = new BmpBitmapEncoder();
                enc.Frames.Add(BitmapFrame.Create(bImage));
                enc.Save(ms);
                pictureBox1.Image = Image.FromStream(ms);
            }

ひとつの静止画を扱うなら、これで十分でしょう。イメージのサイズを設定できるので、メモリ消費も必要最低限に抑えられ、スピードもこれ以上は難しいでしょう。

また、書き出し先をファイルにすればフォーマット変換に使えます。

System.Windows.Media.Imagingのための参照追加 は次のとおりです。

プロジェクトの参照右クリック⇒参照の追加⇒アッセンブリ
PresentationCoreをチェック⇒OK

方法2
BitmapDecoder
BitmapEncoderを使用する。

BitmapDecoderの使い方はよくわかっていませんが、複数のFrameを持ち、Animationなども扱えます。複雑な画像表示を行えるようですが、ひとつの静止画だけを扱うなら方法1で十分そうです。もしかしたら有用かもしれないので紹介しておきます。

ステップは方法1のBitmapImageがSystem.Windows.Media.Imaging.BitmapDecoderに置き換わっただけです。

コードは次のようになります。

            BitmapDecoder uriBitmap = BitmapDecoder.Create(
                new Uri(FilePath, UriKind.Absolute),
      BitmapCreateOptions.None,
                BitmapCacheOption.Default);
            using (MemoryStream ms = new MemoryStream())
           {
               BitmapEncoder enc = new BmpBitmapEncoder();
               enc.Frames.Add(uriBitmap.Frames[0]);
               enc.Save(ms);
               pictureBox1.Image = Image.FromStream(ms);
           }

調べた限りでは、この流れでは画像サイズを指定できません。そのため、高解像度画像ではメモリ消費が多くなり、スピードも少し遅くなります。

方法3
 AxWMPLib.AxWindowsMediaPlayerを使用する。

MediaPlayerは静止画も表示でき、HEIF 画像拡張インストールされていればheicフォーマットも表示できます。ちなみにHEVCビデオも再生できます。静止画を扱う場合は uiMode="none"が良いでしょう。

メリットとしては、まずは静止画も動画も同じインタフェイスで扱えることです。また、対応していないフォーマットや壊れたファイルのエラー対応もMediaPlayerまかせにできます。

これまでのFormアプリと同じ要領で使え、次の要領でコントロールにファイルのパスを設定するだけなので、お手軽といえます。

(AxWMPLib.AxWindowsMediaPlayer)player.URL = filePath;

VisualStudioでMediaPlayerを使う方法は次のリンクを参照してください。
Microsoft Visual Studio で Windows メディア プレーヤー コントロールを使用する
https://draft.blogger.com/blog/post/edit/6538117324271148932/209217070553378409#

デメリットとしては、方法1,2に比べると遅いこと、メモリ消費量も特に高解像度のheicの場合は多くなります。

HEIFコーディックの有無チェック 
Magick.NETと違い、HEIFコーディックがないとエラーになります。
以下の要領でHEIFコーディックの有無がチェックできます。 

bool HeifDllExists = Registry.GetValue(@"HKEY_CLASSES_ROOT\CLSID\{E9A4A80A-44FE-4DE4-8971-7150B10A5199}\InprocServer32", null, null) != null;

参照:HEIF Format Overview
https://learn.microsoft.com/ja-jp/previous-versions/windows/desktop/legacy/mt846532(v=vs.85)




2024年2月25日日曜日

Excel VBA 備忘録 基礎知識編

ときどきExcelのVBAを書きますが、普段使わない言語だとなにかとつまづきます。
自分自身のための備忘録ですが、きっと初心者ならだれもつまづくところだと思います。

VariantとObject
    Variant
     すべての変数。
     Integerなどの値のみを持つプリミティブなデータ型とオブジェクト型のどちらも代入可能。
       配列もプリミティブなデータ型なのでVariantで宣言した変数にしか代入できない。    
    Object
     データとプロパティー、メソッドがカプセル化されたもの。
       オブジェクト型のみ代入可能。
       これにプリミティブなデータを代入すると「オブジェクトが必要です」エラーとなる。

Null, Nothing, Empty, vbNullString の違い
 Null             Variant型変数内に有効な値が入っていない状態
 Nothing       Objectとして空の状態
 Empty         Variant型として空の状態
   vbNullString 値が0のString 

代入時にSetが必要な場合、不要な場合
   Setが必要な場合
        Objectに代入する場合で、値だけでなくObjectのプロパティーの設定が行われる。
 Setが不要な場合
     Integerなどのデータ型に値のみ代入する場合。

Sub呼び出しでCallが必要な場合
 引数を()でくくる場合。

コンパイルエラー:プロパティーの使い方が不正です
    Setを付けずにObjectへ代入しようとした場合。

コンパイルエラー:型が一致しません
 Objectで宣言した変数ににIntegerなどの値をSetしようとした場合。
  Dim o as Object: Set o = 1

コンパイルエラー:オブジェクトが必要です
   Variantで宣言した変数に値をSetしようした場合。
  Dim v as Variant: Set v = 1
 Stringで宣言した変数にSetで文字列を代入しようとした場合。
  Dim s as String: Set s = "str"

コンパイルエラー:ユーザ定義型は定義されていません
    Dimや引数の型宣言で規定のデータ型にもユーザ定義型にもない名前が使われている場合。
    だいたいは書き間違え

実行時エラーオブジェクトが必要です
    値がNothingのオブジェクトのプロパティーにアクセスした場合。
    Objectで宣言した変数にプリミティブなデータを代入しようとした場合。
    Objectで宣言した変数にプリミティブなデータを代入しようとした場合。

実行時エラー:オブジェクト変数またはWithブロック変数が設定されていません。
    Objectで宣言した変数にSetをつけずにオブジェクト、または値を代入しようとした場合。
     Dim o as Object: o = "a" 
     Dim o as Object: o = 1 

実行時エラー 400










2023年9月7日木曜日

C# メール送信プログラムの「SSPIへの呼び出しに失敗しました。内部例外を参照してください。」エラー対応

 数年前に作ったメール送信機能のあるプログラムを作り変えようとしたら、「SSPIへの呼び出しに失敗しました。内部例外を参照してください。」エラーが発生した。

以前作ったとき、.NETのSmtpClientがexplisit SSLのみの対応の、私が使っているjcomのサーバのimplicit SSLで発生が発生したため、そのとき見つけたAegisImplicitMailを利用していた。

今回のエラーはSmtpSocketConnection.Openメソッドの中の

    sslStream.AuthenticateAsClient(host)

で発生した。

検索してみたところ、参考になる記述を発見。

 

PowershellでSSL証明書情報取得時にTLSエラーにハマッた件    https://qiita.com/pizza_slice/items/00d00fd900bb3f0fd697

Windows Server 2016

Window 10 21H2 だったら正常にコマンドが通る。
恐らくこれもOS依存のTLSのバージョンの問題。

# 下記を参考にしてオーバーロード
# AuthenticateAsClient(String, X509CertificateCollection, SslProtocols, Boolean)
# Tls12:3072 TLS1.2セキュリティ プロトコルを指定します$stream.AuthenticateAsClient($commonName, $null, "3072", $false)


同じ理由かもしれないと、AuthenticateAsClientをこれに対応するメソッドに置き換えてみる。

    sslStream.AuthenticateAsClient(host, null, (SslProtocols)3072, false);

するとエラーが発生しなくなった。
(SslProtocols)3072 としたのは、プロジェクトの.NETのバージョンのせいかと思うが、SslProtocols 列挙型にTls12が含まれていなかったため、強引にそのInt値をcastしたからです。
列挙型の名前とint値の対照は次のページを参照してください。

SslProtocols 列挙型

追記

エラーが発生したのは.NET4.0でした。4.5(以上)に変更するとSslProtocols .Tls12が定義されており、上記のintからのcastは不要になります。

なお、このプロジェクトで使用しているAegisImplicitMailの.NETバージョンの関係で、4.5より新しいバージョンでは試していません。SslProtocols .Defaultの値にTls12が含まれていれば、AuthenticateAsClient(host)でもエラーが発生しなくなるでしょう。




2023年9月4日月曜日

VB Excelのシートを名前で探す方法 高速版

VBAでExcelのシートを名前で探す方法を調べると、たいていループで名前が一致するものを探す方法が紹介されています。

たとえばこんなコードです。

    Sub Sample1(name As String)
      For i = 1 to Worksheets.Count
            if Worksheets(i).name = name Then
                Debug.Print Worksheets.Index
                Exit For
            End If
        Next
    End Sub

一回だけ実行する場合は十分高速で問題ないでしょう。しかしループの中で使用すると繰り返し実行されるため、場合によってはかなり時間がかかります。 かつ結果"シート名"と一致するものがない場合はシート数Xループ回数実行され、無駄に時間を浪費します。

上記の例ではWorksheets(i)と引数にindexを使用しています。またiの範囲がCountの範囲内なので、Worksheet(i)がエラーを起こすことはありません。

では、Worksheetsは引数にシート名を使用することもできるので、次のようなコードを試してみましょう。

    Dim sheet As Worksheet
    Set sheet = Worksheets("シート名" )

"シート名"のWorksheetが存在する場合は問題ないのですが、存在しない場合は「インデックスが有効範囲にありません。」というエラーが発生します。Worksheet(i)でもiが有効範囲外であれば同じエラーが発生しますので、内部では同様のループ処理が行われていることが想像できます。

今度は、つぎのような方法を試してみます。

    Sub Sample2(name As String)
        On Error GoTo NotFound
        Debug.Print Worksheets(name).Index
    NotFound:
    End Sub

これでエラーが回避でき、コードもすっきりしているかと思います。面白いことに、計測してみると単純ループの55~60%程度の処理速度となります。75~80%のスピードアッ!!とも言えます。内部的にはループと同等のことを行っていると思いますが、コンパイル済みかどうかの差が大きいのでしょう。

ループによる実行回数が数千回になるような場合は、数単位での差になるのでバカにできません。

このひとつ前のブログ『InternetExplorer.getElementById 「オブジェクトが必要です」エラー対策』で使用した「On Error Resume Next」も試してみました。

    Sub Sample3name As String)
        Dim sheet As Worksheet
        On Error Resume Next
        Set sheet = Worksheets(name)
        If Not sheet Is Nothing Then
            Debug.Print Worksheets(name).Index
        End If
    End Sub

これは処理速度的にはSample2と同等でした。この例の場合ではSample2の方がコード的にもすっきりしていますが、使用する場面では使い道があるかもしれせん。

---------------------

おそらくほとんどの場合は上記の方法で十分高速になると思いますが、100シート超えで千回超えの検索をするような場合にはDictionaryを使うとさらに高速になります。シート数が少ないとかえって逆効果になることもあるでしょう。

Dim SheetNameDict As Object

Rem シート名がnameのWorksheetを返す。
Function GetSheetByName(name As Variant)
    Rem SheetNameDict初期化。SheetNameDict未設定の場合、
    Rem SheetNameDictに全ワークシートのKey=name, Value = sheetをセット。
    If SheetNameDict Is Nothing Then
         Set SheetNameDict = CreateObject("Scripting.Dictionary")
         For Each sheet In Worksheets
            Call SheetNameDict.Add(sheet.name, sheet)
         Next sheet
    End If

    If SheetNameDict.Exists(name) Then
        Set GetSheetByName = SheetNameDict(name)
    Else
        On Error GoTo NotFound
        Dim sheet As Worksheet
        Set sheet = Sheets(name)
        Set GetSheetByName = sheet
        Call SheetNameDict.Add(name, sheet)
        Exit Function
NotFound:
        Set GetSheetByName = Nothing
    End If
End Function

使用例
Dim sheet as Worksheet
Set sheet = GetSheetByName("someSheetName")
if Not sheet is Nothing then ....

なお、このサンプルの場合は途中でSheetが削除されるとその後存在しないSheetを返すことになるので、Sheetを削除すると同時にSheetNameDict の項目も削除します。

Call SheetNameDict.Remove(name)









2023年8月18日金曜日

VB InternetExplorer.getElementById 「オブジェクトが必要です」エラー対策

VB InternetExplorer.getElementById 「オブジェクトが必要です」エラー対策

VBの InternetExplorer.getElementById は該当する要素がない場合に「オブジェクトが必要です」が発生する。これの扱いに困ったが、次の要領で対処できた。

まずは、次のようなコードでエラーが発生する。

    Dim elm As IHTMLElement
    Set elm = ie.document.getElementById("someID")

要素がない場合にgetElementById はNullを返す。
Nullを代入できるのはVariant型だけなので、IHTMLElementとかObjectで宣言した変数に代入しようとすると「オブジェクトが必要です」が発生する。

そこで、次のようなコードにしてみる。

    Dim elm As Variant
    Set elm = ie.document.getElementById("someID")

こんどは「型が一致しません」エラーが発生する。
これはSetではNullをObjectとして扱おうとするため発生する。

それでは、これではどうだろう。

    Dim elm As Variant
    elm = ie.document.getElementById("someID")
    if IsNull(elm) Then Exit Sub

    Dim divs as IHTMLElementCollection
    Set divs = elm .getElementsByTagName("div")

Nullのときの処理はうまくいくが、今度はelmがObjectとして設定されていないため、後続のコードで「オブジェクトが必要です」エラーが発生する。

そこで次のように変更し、エラー発生時に処理を継続してみた。

    On Error Resume Next
    Set elm = ie.document.getElementById("someID")
    If IsNull(elm) Then Exit Sub

    Set divs = someID.getElementsByTagName("div")

一応うまく処理できるようになったが、ちょっとおかしい。
IsNull(elm)がTrueにならない。この場合、elmがEmpty値になっている。
Setで代入するので、elmはObjectでなければならず、Nullにはならないということですね。
そのまま後続処理に進んでも上記のコードではエラーは発生しないが、具合が悪い場合もあるかもしれない。

そこで、IFの判別を次のようにすると有効になる。

    On Error Resume Next
    Dim elm As Object, divs as IHTMLElementCollection
    elm = ie.document.getElementById("someID")
    If elm Is Nothing Then Exit Sub 

On Error Resume Nextとした結果 elm がEmpty値になるため、elmをObjectで宣言している。

次の式でも判別できる。

    If IsEmpty(elm) Then Exit Sub

NothingはObjectとして空の状態、EmptyはVariant型として空の状態ということですね。Set式のあとなので実際にはObjectとして扱ってよいと思いますが、Dimで宣言した型に合わせるのがコード的には一貫性があると言えるでしょう。

実際にはNullになることはないが、次の方がより安全かもしれない。

    If IsNullOrEmpty(elm) Then Exit Sub

なお、

    On Error Resume Next

でエラーを無視するようにしたので、前後の処理の関係ではこのあとで再度On Errorを設定しなおす必要があるでしょう。

2022年10月4日火曜日

DELL Inspiron 14 3452 復活奮闘記

DELL Inspiron 5458を使用していますが、しばらく前にキーボードの一部が反応しなくなってしまいました。交換部品を探したがお手軽なものがなく、ヤフオクやメルカリで5458の出物をしばらく待っていましたが、安いものはキーボードのテストをしていないとか、メモリもHDDも取り外されて廃墟状態になったようなものばかり。

そこで、同じ型のキーボードと思しきInspiron 3452をヤフオクで落札(これもジャンク品)。送料込で3,500円。結果的には目的のキーボードに加え、ACアダプタと8Gバイトメモリも入手できたので十分元は取れたと思ってます。

残りは部品取りに使えればよいと思って思っていました、分解、組み立てを行っているうちに具合が悪かったキーボードが何かの拍子に復活し、3452も一応使用可能な状態に組み上がりました。そうなるともったいなくなり、なんとか使える状態にしておこうと奮闘が始まりました。

奮闘というのは、3452というモデルが32G eMMCが換装不可で、HDDスペースは空いているもののコネクタがないといった、いささか特異な仕様が故です。入手したときには容量不足でWindows Updateもできない状態に陥っていました。

この貧弱な仕様のおかげでWeb閲覧やメール程度の使用しかしていなかったのか、ジャンク品とはいえキーボードもACアダプタも良好でした。部品取り目的なら3452は狙い目かもしれません。

私が入手したマシンはCPUがCeleron N3050 2コア、Windows 10 64bitで、サクサク感はイマイチながら光学ドライブ、HDD無しなため5458より軽量になってます。あれこれ試してみた結果、ちょっと持ち歩いて軽い使い方をするには悪くないと思い始めてます。

さて、インタネットで見ると皆さん工夫したり、苦労したり、諦めたりしているようです。

クセが強いジャンクの Dell Inspiron 14-3452 へ Linux をインストールしてみた。

Dell Inspiron 14-3452 eMMC 32GB Win10 ⇒ 1709 アップデート作法

Inspiron 14 3452のメモリ増設とSDDの交換ってできませんか?

amazon カスタマーレビュー


これらを参考にいろいろ試したので、今後のために書き留めておきます。
なお、手間がかかるので追試検証など不十分です。

--------------------

番外編
DELL Inspiron 5458のキーボード故障への応急対策
一部のキーが無反応になったため、まずはWindows付属のスクリーンキーボードで対応。
キー入力の途中でキーボードからマウスに手を移すのが面倒なため、KeySwap for XP を導入。普段使わないFuntion keyへ割り当てる。

5458とキーボード互換のモデル
Ali-Expressのキーボードの広告があった。これ自体はBR(ブラジル?)キーボードのものだが、キーボード表裏の写真から互換性がありそうと判断し、3452の購入を決めた。3452と互換性があるモデル、と言い換えもできる。保証はないが互換性がある可能性大。

Dell Inspiron 14-3000 3441 3442 3443 3451 3452 3458 3459 5447 5442 5445 5448 5451 5455 5458 7447 5452 5457 5459用brキーボード

準備
バックアップ
お約束として、大幅なシステム変更を行う前はバックアップを取りましょう。システムイメージを作っておいたほうよいでしょう。

--------------------

レベル1
特別なことはせず、そのまま使う場合のTIPS

ディスククリーンアップ
Cドライブの空き容量を少しでも増やす。
エクスプローラでCドライブを選択⇒プロパティー「ディスクのクリーンアップ(D)」
⇒「OK」実行
⇒プロパティー左下の「システム ファイルのクリーンアップ」も実行

dism.exeというコマンドでクリーンアップする方法もあります。これで更新ファイルを全部削除するようです。
>dism /Online /Cleanup-Image /RestoreHealth 

Windows Updateのクリーンアップ
設定⇒システム⇒(左ペイン)記憶域⇒「記憶域の管理」
またはWindows Update⇒「関連するリンク」グループの「ストレージを確認する」
⇒「ストレージセンサーを構成するか、今すぐ実行する」
⇒「今すぐ空き領域を増やす」の「今すぐクリーンアップ」

OneDriveの設定確認
タスクバーのOneDriveアイコンクリック
⇒設定(歯車アイコン)⇒設定(メニュー)
⇒「ファイルオンデマンデド」がチェックされていることを確認

Windowsオフラインアップデート
メモリ容量不足の場合はインストールメディアを作り、オフラインアップデートを試す。

Microsoftの「Windows 10 のダウンロード」ページ
⇒「ツールを今すぐダウンロード」をダウンロードでUSBインストールメディアを作成
最新のメジャーリリースなので、既存バージョンより新しいとは限らない。

USBインストールメディア装着
⇒再起動時にF12キー連打
⇒起動オプション表示
⇒USB起動(バッテリー不良でBIOSの警告画面が出るようなら、バッテリーを外して起動)
⇒言語/キーボード選択⇒OK
⇒「今すぐインストール」
⇒「適用される通知とライセンス条項」
⇒(同意するなら)「同意します」にチェック⇒「次へ」
⇒「アップグレード:Windowsをインストールし、ファイル設定、アプリを引き継ぐ」


Windows再インストール
実は、私はWindows 10を再インストールしています。中古で入手し、容量不足でWindows Updateもできない状態ですから、そこから復旧する意味はありません。Windowsオフラインアップデートと同じ要領でインスールメディア作り、内蔵eMMCを初期化してインストールしました。

インストール後、いくつかドライバが正常動作しません。Dellのサイトからダウンロード、インストールします。

ドライバアップデート後にWiFiのパスワード再設定が必要。

Windows再インストール直後にフォルダ構成変更を行う場合は、Windows Updateを一旦停止するのがよい。作業中にもバックグランドでダウンロードやインストールが行われるため、作業に支障が出たり、フォルダ移動により不具合が出る可能性がある。

Windowsアップデートの一時休止
フォルダ移動の作業中にもWindowsアップデートがバックグランドで動作し、Cドライブの容量が不足する可能性がある。作業が一段落つくまでWindowsアップデートを休止する。

休止モードの無効化
休止モードが有効だとC:\hiberfil.sysに復帰のためのデータが保存されるため、Gバイト超えに肥大する。

メインメモリに余裕がないためか、設定の電源オプションには休止モードの有効/無効に関する項目が表示されないが、hiberfil.sysが肥大化していた。そのため、コマンドで無効にする。管理者モードのCMD.EXEで実行する。

>powercfg.exe /hibernate off

hiberfil.sysは「保護されたオペレーティングシステムファイル」に属するため、デフォルトではエクスプローラで表示されない。エクスプローラのオプション⇒表示で「保護されたオペレーティングシステムファイルを表示しない(推奨)」のチェックを外す。
CMD.EXEのDIRで表示するには"/AS"のオプションを付ける。

バッテリー切れになるとシステムダウンとなるので、長時間のスリープには注意が必要になる。


ライブラリの移動
「ドキュメント」「ピクチャ」「ミュージック」などのライブラリをUSBメモリに移動。
「ドキュメント」などのフォルダを右クリック
⇒プロパティ⇒場所
⇒フォルダパスを変更(ドライブ名をUSBメモリのドライブ名に変更)⇒移動
⇒「フォルダー"...."は存在しません。新たに作成しますか?」の場合は「はい(Y)」
⇒移動の確認ダイアログ「はい(Y)」


新しいコンテンツの保存先変更
設定⇒システム⇒ストレージ⇒新しいコンテンツの保存先を変更する
CドライブからDドライブへ変更

OneDriveを移動
サブマシンとして使用する場合、OneDriveでメインマシンとデータ共有しておくと用途が広がる。

OneDriveのフォルダに指定するドライブはNTFSでフォーマットされている必要がある。
USBがFATでフォーマットされている場合は次のコマンドで変換可能。
>convert D: /fs:ntfs ("D:"の部分は実際のドラブ文字で置き換え)

タスクバーのOneDriveアイコンクリック
⇒設定(歯車アイコン)⇒設定(メニュー)
⇒ アカウントタブ⇒「このPCのリンク解除」
⇒サインアウト後、OneDrive設定のダイアログ表示
⇒「OneDriveを設定」ダイアログ⇒アカウント入力しサインイン
⇒「OneDreveフォルダー」ダイアログ⇒左下の「場所の変更」でUSBへ移動

USBへ移動後はファイル オンデマンドをOFFにしておいた方が良いでしょう。
上の「OneDriveの設定確認」参照


Tempの移動
Windows使用中にTempにデータが溜まり、容量不足の原因になります。シャットダウン後も残るデータもあります。USB3.0ならTempも移動した方がよいでしょう。USB2.0だと速度の問題が出そうです。
  • Temp移動は起動直後に行う。
  • Cドライブのプロパティーで一時ファイルを削除する。
     「ディスクのクリーンアップ」で「一時ファイル」を削除
     「システム ファイルのクリーンアップ」で「一時ファイル」を削除
  • Tempフォルダにファイルが残っている場合は、移動先のTempフォルダにコピーする。
  • robocopyコマンドでコピーした方がファイル属性が保存されて安全だと思います。
    実行例(管理者権限でcmd.exeを起動)
    >robocopy C:\Windows\Temp D:\Windows\Temp /MIR
    >robocopy C:\Users\usrename\AppData\Local\Temp D:\...\Temp /MIR
  • 環境変数のTEMP、TMPをUSBメモリのパスに書き換える
    参考:【Windows10】一時フォルダを別ドライブに移動して高速化する
  • 変更直後に再起動
再起動後、不要になったTempフォルダが空でない場合は削除する。
私は、念のため一旦フォルダをリネームし、再起動後にフォルダ内のデータを削除し、空のフォルダを残した。(環境変数に関わらずC:\Windows\Tempを使用するプロセスがあるようだ。)
Tempフォルダのリネーム、削除がアクセス権なしで失敗する場合は、cmd.exeを管理者権限で起動し、ren、rmdirなどのコマンドで操作する。

--------------------
レベル2
USBメモリまたはSDカード常時装着&固定ドライブ化
速度的にはUSB3.0(以上)がお勧めです。本体左側がUSB3.0、右側がUSB2.0です。
USB3.0なら内蔵eMMCと同等の速度が期待できます。
SDカードスロットはUSB2.0なので、高速なSDカードを使ってもそれなりの速度しかでません。
以下、SDカードも含めUSBメモリとします。また、ドライブ文字をDとします。

どのような方法にせよUSBメモリにデータを保存すれば紛失時のデータ流出の危険があります。そのような心配のない用途に限るか、心配なら(私は試していませんが)BitLockerによる保護などが必要です。

eMMCやUSBメモリのデータ圧縮はまだ試していません。Cドライブ全体を圧縮すると起動が遅くなると決めつけているからですが、Cドライブの残り容量があまりに少ないので検討してみる予定です。

USBメモリを固定ドライブ化
アプリケーションをUSBメモリにインストールをしようとすると「インストール ディレクトリはローカル ハード ドライブにあることが必要です。」と警告される。
そこで、いくつかの方法を試した。
ファイル/フォルダ削除でゴミ箱移動が可能になる方法もある。

USBメモリにアプリをインストールした後、アンインストール時に,msiフォルダが作成できないというダイアログが表示され、アンインストールに失敗することがある。検証不十分だが、パーティションをactiveにするとエラーが発生しなくなっている。

方法1
USBメモリ上のファイルをVHD(Virtual Hard Disk)に割り当てる。
HDDとして扱ってくれることで、アプリケーションのインストールやライブラリ類の移動、ファイル履歴の保存先に選択などが行える。

また、リムーバルディスクの場合にファイル/フォルダ削除はゴミ箱への移動ではなく完全削除となり、かつ削除のたびに「このファイルを完全に削除しますか?」のダイアログが表示されるが、この方法だとファイル/フォルダ削除がゴミ箱移動となる

VHDは再起動時にマウントする必要がある。タスクスケジューラで自動マウントを設定する。


タスク設定時にユーザのパスワードを要求される。PINではダメなので、ローカルユーザに変更し、パスワードを設定しておく。

欠点として、(設定の問題があるかもしれないが)サインイン前にマウントが完了していないことが多く、その場合にサインイン時の環境が不完全な状態となる。手動でマウントするか、エクスプローラの再起動や再サインインが必要が必要になる。これが許容できるなら、良い方法と思う。

サインイン直後にマウントが完了していないとディスクトップ、タスクバーの再現ができないため、デスクトップはDドライブに移動した。この場合、デスクトップからのファイル削除は完全削除となる。

VHDは起動時にはまだマウントされていないため、起動時に他のUSBメモリが装着されているような場合、若いドライブレターが先に適用される。そのためVHDのドライブレターはD、Eなど他のメモリに使われる可能性があるものは避ける。私はVを割り当てることにした。

方法2
ディスク管理を使用し、USBメモリのパーティションをCドライブ上の空のフォルダにマウントする。

アプリケーション インストール先をこのフォルダ下に設定すると、インストールは完了し起動するが、アンインストール、修復で失敗した。何か設定に問題があったのかもしれない。

もし試す場合は、バックアップからの復旧か再インストールの準備をしておくこと。

もしアプリ削除で問題が発生したら、USBメモリに移動したライブラリ類を標準に戻して、アプリ削除/再インストールを試みる。それでもダメだとバックアップへの復旧か再インストールとなる。

方法3
USBメモリに(例えば)”C:\Program Files (USB)”フォルダを作り、次のコマンドでCドライブにリンクを作る。
>mklink /J "C:\Program Files (USB)" "D:\Program Files (USB)"

アプリケーション インストール先をこのフォルダ下に設定する。インストールは完了し起動し、アンインストール、バージョンアップも成功した。

VHDマウントのタイムラグの問題がないため、この方が良い場合がありそう。

方法4
USBメモリ上のフォルダを共用にし、ネットワークドライブに割り当てる。
このドライブはファイル履歴の保存先として選択可能になる。
あまり好ましく思えなかったので、よく試していない。

--------------------
レベル3
肥大化するフォルダ、サイズの大きいフォルダをUSBメモリへ移動する
多くのフォルダアクセスで管理者権限が必要となる。以下の手順でアクセス拒否が発生した場合は、管理者権限でCMD.EXEを起動し、コマンドで操作する。
また、USBメモリへのデータコピーは属性保存のため、原則としてrobocopyを使用した。
アプリケーション起動後に残る常駐プロセスのためにフォルダはリネームができない場合がある。その場合は再起動直後に行う。
バックグランドの常駐サービスが使用しているためにリネームができない場合がある。その場合は回復の再起動でコマンドプロンプトを表示し、リネームした。

バックアップを作っておく
不具合が発生した場合、最悪Windows再インストールの可能性もある。
これ以降に進む前に、バックアップを作っておいた方がよい。

仮想ハードディスクもバックアップ対象のドライブとして選択可能になるが、USBメモリはWindowsバックアップに含めず、別にrobocopyでコピーする。試していないが、再起動不可の場合はVHDドライブのマウントもできていないので、復元に失敗すると思う。

また、USBメモリにライブラリなどのフォルダを移動するためか、システムイメージは作成できるが、ファイルのバックアップには失敗する。

ページングファイルの縮小
メインメモリが不足するとPagefile.sysに書き込まれ、肥大化する。
固定HDDがある場合などは別ドライブにPagefile.sysを移すことができるが、USBメモリの場合は単純にはできない。
VHDをPagefile.sysにすることはできるが、Cドライブは使用せずVHDだけにPagefile.sysを作る設定だと、おそらく起動時にはまだマウントが完了していないためCドライブにPagefile.sysが作られた。
そこで、CドライブにPagefile.sysに200Mを固定で割り当て、VHDにPagefile.sysシステム管理で設定した。今のところこれで動作している。


AppDataのOneDrive移動
C:\Users\username\AppData\Local\Microsoft\OneDrive
  • OneDriveのリンクを解除する、終了
    タスクトレイのOneDriveアイコン⇒設定アイコン⇒「設定」クリック
    ⇒「このPCのリンク解除」クリック
    ⇒「OneDrive終了」クリック
  • OneDriveのデータコピー
    >cd C:\Users\username\AppData\Local\Microsoft
    >robocopy OneDrive D:\...\OneDrive /MIR
  • OneDriveリネーム
    >cd C:\Users\username\AppData\Local\Microsoft
    >ren OneDrive OneDrive~
  • リンク作成
    >mklink /J OneDrive D:\...\OneDrive
  • OneDrive再設定
     [スタート] ⇒[検索] ⇒[OneDrive] と入力⇒検索結果で [OneDrive] を選択
    参考:OneDrive の同期に関する問題を解決する
  • 再起動、動作確認後、リネーム後のOneDrive~を削除
AppDataのEdge移動
C:\Users\username\AppData\Local\Microsoft\Edge
「AppDataのOneDrive移動」と同様の手順でEdgeをコピー、元フォルダをリネーム、リンク作を行い、Edgeの動作に問題なければ元データを削除する。
Edgeが起動していなければ成功したが、再起動直後に行った方が確実。

AppDataのGoogle移動
Chromeをインストールした場合に実行。

--------------------
レベル4
危険度の高いフォルダ移動。
インストールされたアプリを移動する場合、USBメモリに異常が発生するとアプリのアンインストールができなくなる恐れがある。移動後、速やかにUSBメモリのバックアップを取る。

SoftwareDistributionの移動
WindowUpdateでフォルダが肥大化するため、”C:\Windows\SoftwareDistribution”をUSBメモリへ移動する。

DataStore.edbをコマンドで削除する方法ことで小さくすることができるそうだ。大きくなったらその都度コマンド実行するのも面倒なので、私はSoftwareDistributionを移動した。
  • Windowsアップデートで最新状態にする
  • Windowsアップデートを停止する
    >net stop wuauserv
  • 管理者権限のcmd.exeでコピー、リネーム、リンク作成
    >cd C:\Windows
    >robocopy SoftwareDistribution D:\...\SoftwareDistribution /MIR
    >ren SoftwareDistribution SoftwareDistribution~
    >mklink /J SoftwareDistribution D:\Windows\SoftwareDistribution
  • Windows Update再開
    >net start wuaserv
  • 問題なければリネームしたC:\Windows\SoftwareDistribution~を削除
SoftwareDistribution移動後にWindows Updateでエラーが発生したら、SoftwareDistributionを削除する。

Program Files (x86)のEdge移動
「AppDataのEdge移動」と同様の手順で以下のフォルダを移動。
  • C:\Program Files (x86)\Microsoft\Edge
  • C:\Program Files (x86)\Microsoft\EdgeCore
  • C:\Program Files (x86)\Microsoft\EdgeUpdate
  • C:\Program Files (x86)\Microsoft\WebView
  • C:\Program Files (x86)\Microsoft\Temp
Program FilesのGoogle移動
Chromeをインストールした場合に実行。

Program FilesのDell移動
SupportAssistをインストールした場合に実行。
「AppDataのEdge移動」と同様の手順で以下のフォルダを移動。
  • C:\Program Files\Dell
(リネームに失敗したら回復⇒再起動⇒詳細⇒コマンドプロンプ⇒コマンド実行)

--------------------
レベル5
HDD用空きスペースの利用
How to install SSD for Inspiron 14 3452?で紹介されている。

外付けHDD/SSDを使用するのがアプリインストール、バックアップなども含めて用途的にはUSBメモリより望ましい。SSDをUSB3.0に接続すると、CrystalDiskMarkの測定では内蔵eMMCの3倍近い速度がでた。

しかし、常に外付けを繋いでおくのはうっとうしく、軽量ノートパソコンの意味がなくなる。まだ試していないが、HDD用のスペースにSATA-USB変換アダプタを付けた内蔵HDD/SSDを入れ、USBコネクタと結線する。

USB2.0の口となら実現できそうだが、それだとHDD/SSDの速度は引き出せない。
SB3.0は、線の引き回しが難しそうで、ノイズが乗って期待どおりの速度が出ないとか、エラーが発生する可能性がありそう。

SATAコネクタ追加
マザーボードにはコネクタを取り付けるはずの端子が付いている。果たして実際に動作するかわからないが、腕に自信があり部品が調達できるなら試してみる価値はあるかも。

--------------------

現状のシステム構成
USB3.0にSunDiskの小型USBメモリを常時装着。(Dドライブ)
VHD.vhdフォルダを作成し、V:ドライブにマウント。
D、VにUser、Windowsなどの移動対象のフォルダを原則同じパスになるように設定。
ライブラリ類は移動時に新規作成してくれる。

Vドライブにはvhdマウントのタイムラグを許容できるもので、ファイル削除時にゴミ箱移動にしたいもの(ドキュメントなど)を移動。
Cドライブを圧迫しないよう、新規アプリケーションはVドライブにインストールする。

Dドライブにはvhdマウントのタイムラグの影響が大きいもの(デスクトップなど)を移動。TEMPなどWindowsが直接操作するものもDドライブへ移動。

OneDriveはvhdマウントの影響がでることがあるが、ドキュメントを置く都合でVドライブに移動した。

------------------------------

テスト中に利用したコマンド類
試みにUSBメモリをこの方法で初期化してみた。この目的では特に効果なし。

共有フォルダをネットワークドライブに割当たが、それを解除したときにドライブに☓印のついたアイコンが残り、消さなくなった。次のコマンドで解消。
>net use /PERSISTENT:NO
直後に再起動

ディスク使用状況チェック
DiskInfoを使用し、データ量の多いフォルダを探す。

------------------------------

ストレージRead/Writeベンチマーク
CristalDiskMarkによる測定結果

内蔵eMMC
USB3.0メモリ(SanDisk 64GB Ultra Fit USB 3.1)
シーケンシャルRWはeMMCに匹敵するが、ランダムアクセスで劣る。
頻繁にデフラグしておくのが望ましい。

SDカード(SanDisk Extream Pro 64G)
USB2.0らしい結果。

SSD-USB3.0接続
さすがに速いが外付けなので採用せず。

SSD-USB2.0接続

SSDでもUSB2.0では価値がない。


2022年9月3日土曜日

Windows 10 ディスク領域が不足でのバックアップ失敗

 メインのマシンをWindows 11にしたので、いささか古いサブマシンにWindows 10をインストールした。いちおう完了したのでバックアップを作ろうとしたら「ディスク領域が不足しているため、バックアップに失敗しました」が発生した。

十分ディスク容量はあるのにおかしいなあ、と思いながらもパティション構成を変えたりして何度かためしたが、相変わらず同じエラーで失敗する。

  • ディスク領域が不足しているため、保存場所にボリュームのシャドウコピーを作成できません…
  • 重要なボリュームの1つに十分な空き領域がないため、システムイメージのバックアップをスキップしました。

などといったメーセージが出るが、何が悪いのかよくわからない。ググってみても未解決のQ&Aか、ディスクの空き容量を増やして成功したというものばかり。

「重要なボリュームの1つ」ということでバックアップ対象を眺めてみると、システム予約のパティション管理領域もボリュームの1つに含まれている。256GのSSDのためか、この領域が50mbと小さく、残り容量がわずかだった。そこで、これをAOMEI Partition Assistant Standard(無料版)で150mbほどに増やして再度試みたところ、成功した。

解決したあとで改めてググってみると

PCバックアップエラー
https://okwave.jp/qa/q8675779.html

というQ&Aで「原因:100MBのシステム領域の空き容量不足」と回答されていた。

というわけで、私の場合は「重要なボリュームの1つ」というのがシステム予約のパティション管理領域でした。

ついでなので、AOMEI AOMEI Partition Assistant Standardを使って対処する場合につまづいた点について補足しておく。

  • Partition Assistant Standardでディスク先頭の管理領域の右隣のパティションを右クリック
  • 「パティションサイズをリサイズ/移動」をクリック
  • パティションの先頭に丸アイコンがない場合は「このパティションを移動したい」をチェック(これがつまづきポイント)
  • 先頭位置の丸アイコンをドラッグして先頭(左側)に空き領域を作る
    (サイズの上下アイコンクリックでサイズ微調整が可能)
  • ディスク先頭の管理領域を右クリック
  • 右端の丸アイコンのドラッグ(またはサイズの上下アイコンクリック)で領域拡張
  • 「適用」クリックでリサイズ実行
パティションリサイズ後にCHKDSKで異常がないか確認するとよいでしょう。

もちろん、管理領域以外にもバックアップ対象のボリュームで空き容量が少ないものがあったら、同様の方法でパティションサイズを調整するか、不要なファイル削除などを行う必要があります。

2022年8月22日月曜日

WiX ToolSet v3.xでの.NET Frameworkの必須バージョンの追加

WiX ToolSet v3.xは.NET Fraomeworkのバージョンチェックのための定義を4.6.2までは含んでいるが、それ以降のバージョンについては定義の追加が必要になる。

WiX ToolSetで定義済みの.NETバージョンの場合は、
このページに簡潔な説明がある。

また、次のQ&AでDOT.NET 4.8の定義を追加して、同様のバージョンチェックを行う方法が紹介されている。
NetFxExtension should support .net 4.8 
  <?define NetFx480MinRelease = 528040 ?>でNetFx480MinReleaseの名前で値を定義
  
WiX ToolSetで定義済みの.NETバージョン定義を PropertyRef で取得できる。
例:<PropertyRef Id='WIXNETFX4RELEASEINSTALLED'/>

この値はレジストリに登録されている.NET release keyとなる。

この値と.NET のバージョンの最小値(NetFx480MinRelease=528040)を比較することで、インストールされている.NET Frameworkが要件を満たしているか判別できる。

設定例
次の例ではWIXNETFX4RELEASEINSTALLED とバージョンの最小値を直接比較する。
<PropertyRef Id='WIXNETFX4RELEASEINSTALLED'/>
<Condition Message='This setup requires the .NET Framework 4.7.2 (or greater) to be installed.'>
<![CDATA[Installed OR (WIXNETFX4RELEASEINSTALLED >= "#461808")]]>
</Condition> 次の例ではNetFx480MinReleaseという名前でバージョン番号を定義し、これと比較してWIX_IS_NETFRAMEWORK_480_OR_LATER_INSTALLEDの値を設定する。
<?define NetFx480MinRelease = 528040 ?>
<PropertyRef Id="WIXNETFX4RELEASEINSTALLED" /> <Property Id="WIX_IS_NETFRAMEWORK_480_OR_LATER_INSTALLED" Secure="yes" /> <SetProperty Id="WIX_IS_NETFRAMEWORK_480_OR_LATER_INSTALLED" Value="1" After="AppSearch"> WIXNETFX4RELEASEINSTALLED >= "#$(var.NetFx480MinRelease)" </SetProperty>

2022年8月19日金曜日

自作アプリがウイルス(トロイの木馬)扱いになった ⇒ 解決?

今度は「Behavior:Win32/Hive.ZY」が見つかった警告された。

やれやれまたか...で、こんどは自作アプリを疑う前にDefenderに関する書き込みをチェック。すると「窓の杜」からニュースが流れていた。

マルウェア「win32/hive.zy」が検出されるトラブル ~「Microsoft Defender」ウイルス対策の誤検知か

日本時間(2022年)9月4日より、内部で「Chromium」を利用するアプリ(「Google Chrome」や「Microsoft Edge」、「Spotify」など)を起動するたびに「Microsoft Defender」ウイルス対策が「win32/hive.zy」というマルウェアを検知する現象が発生しているようだ。編集部でも確認をした

このページのお勧めに従って[ウイルスと脅威の防止の更新]を行った。さっそくアップデートがあったようだ。

------------

解決と書いたが、しばらくして再発。

AxWindowsMediaPlayerを使用しており、コントロールを追加するとAxInterop.WMPLib.dll、Interop.WMPLib.dllが追加される。このあたりを疑って試しているうちに、また安定し始めた。

以前にウイルス扱いとなったバージョンも今は問題ない。

どうやらDefenderの定義ファイルが修正されたようだ。

------------

先日「自作アプリがウイルス扱いになったのがWIndows 11再インストールで解決した」と書き込みをしたが、その後システムドライブバックアップ中に検疫にひっかかり、再度トロイの木馬扱いとなった。

ソースコードで関連ありそうなメソッド呼び出し順次止めて試したところ、Assemblyからcopyrightの文字列を取得している箇所を削除することで検疫をパスするようになった。

Attribute copyright = Attribute.GetCustomAttribute(Assembly.GetExecutingAssembly(), typeof(System.Reflection.AssemblyCopyrightAttribute));

ただし、このメソッド呼び出しは別アプリでも使用しており、なにかしら別要因との組み合わせがあると思われる。

------------

2022年8月初旬に自作アプリ(RenameMe)がトロイの木馬(Trojan:Script/Wacatac.H!ml)扱いになって、Defederで検疫されてしまった。

表示されたダイアログをコピーしておかなかったが、次のようなメッセージが表示され、挙句Defenderから脅威が見つかったと警告された。

・This applicaion could not be started.
・このアプリを実行すると、PC に問題が起こる可能性があります。

アプリ本体はDOT.NETのライブラリとメディアプレーヤのWMPLibで、いずれもMicrosoft製のものしか使用していない。

インストーラはオープンソースのWixTookSetを利用しているが、これはMicrosoft製のツールを引き継いだもので、Windows Installer (MSI) パッケージを作成するが実際のインストールはWindowsの機能で行うものだ。また、インストールしたアプリだけでなくReleaseビルドしたexeを起動しても「このアプリを実行すると、PC に問題が起こる可能性があります。」となるのでインスーラは関係なさそう。

今まで動いていたものを突如トロイの木馬扱いにするのだから、これは誤検知に相違ないのだが、といってアプリが動かないのは困る。

まずはアプリをビルドしなおしてみる。やはりダメなので次のことを順次試して、アプリケーションの削除/インストールを繰り返してみる。

・DOT.NETのバージョンを4.7.2から4.8に上げる
・WixTookSetを3.11.2に上げる

DOT.NET Framework4.8の再インストールを試みるが、これは既にインストール済みと拒否される。

これでも同じ現象が続くので、Windows Updateで更新状態をチェックすると、KB5016629のインストール待ち状態だった。これのインストールを試みるが失敗。

次のようなコマンドを実行するがエラーで成功しない。

dism /Online /Cleanup-Image /ScanHealth
dism /online /cleanup-image /startcomponentcleanup
sfc /scannow

エラーメッセージは次のようなもの。
・指定したバッファーに誤った形式のデータが含まれています。
・Windows リソース保護は要求された操作を実行できませんでした。

当然ながらWindows Updateは再度失敗。
オフライン更新も試みるが同じエラーで失敗。

いささか行き詰まり状態になり、復元ポイントやバックアップで前の状態に戻すことを考え始めたが、Window 11では個人データだけでなくアプリも残したのまま上書きインストール可能というのを発見し、これを試みることにする。Windows 10でも上書きインストールできたんですね。

参照: Windows11 上書きインストールで現状を全て保持して修復

isoをダウンロードしインストールを開始すると、なんとTPM2.0が有効になっていないという。UEFI(BIOS)画面でチェックすると、確かに無効になっていた。しばらく前にBIOSをアップデートしたので、そのときに初期状態に戻っていたようだ。Windows 11はTPM2.0必須ではあるが、インストール後これが無効でも起動するのですね。

ついでに高速スタートアップが無効になっていることを確認。

TPM2.0を有効にし、再度上書きインストールを試み、成功。

再起動後にWindows Updateを見るとKB5016629の前のKB5015732、KB4023057も待ち状態になっていた。

KB4023057は「.NET Framework 3.5 および 4.8 の累積的な信頼性の向上が含まれています」となっており、どうもこれが関係しているように思われる。

その後、アプリを再ビルド、再インストールし、今のところ安定して動作している。
⇒その後システムドライブ バックアップ時に再発

振り返ってみると、つぎのような順序で問題が発生したのではないかと思われる。

・しばらく前にWindowsの動作が重くなり、BIOSのアップデートを行った。(実際はBIOSの問題ではなくIMEをGoogle日本語入力に置き換えることで解決した。)

・BIOSアップデート時に設定が初期化され、TPM2.0がオフになった。

・Windows 11は動作しつづけたが、もしかしたらTPM2.0オフが一因でWindows Updateで更新失敗が発生。もしかしたらDOT.NET 4.8が関係してウイルス検知を誘発。

・KB5015732、KB4023057いずれかの更新失敗のため、KB5016629も更新失敗。

・このときのゴミでdism、sfcコマンドでもエラーが発生。