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はObjectを対象とするが、NullがVariantであるため発生する。

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

    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 = elm.getElementsByTagName("div")

一応処理できるようになったが、ちょっとおかしい。
IsNull(elm)がTrueにならない。この場合、elmがEmpty値になっている。
Setでelmに代入するので、elmはObjectとなり、Nullにはならないということですね。
elmがEmptyだと後続処理でエラーになる。

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

    On Error Resume Next
    Dim elm As Object, divs as IHTMLElementCollection
    Set elm = ie.document.getElementById("someID")
    If elm Is Empty 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コマンドでもエラーが発生。

2022年7月27日水曜日

IMEが原因でWindows 11 がえらく重くなった

しばらく前からWindows 11 の動作がえらく重くなった。

特にIMEの動作が遅くなった。キー入力後に漢字変換の候補が出るまでに、時には数秒待たされることもあった。タスクバーのIMEアイコンが☓になり、「IMEが無効です」となったりした。

IMEが全く反応しないこともあったが、これはIMEの設定で以前のバージョンに戻すことで改善はしたものの、もたつきは相変わらずだった。また、IMEアイコンは出ても右クリックに反応せず、コンテキストメニューが表示されないことがあった。

ctfmon.exeを手動で実行しても変化がない。タスクマネージャで動作をチェックすると、一瞬現れるがすぐに消えたりする。

ビデオ編集にVideoStudioを使っているが、マウスドラッグがスムーズに行えなくなったりもした。このせいで、IMEというよりもWindowsそのものや、ハードになにか問題がありそうに思えた。

もともとメモリにちょっと問題があったこともあり、BIOSやドライバをアップデートしたり、またWindowsを少し前の復元ポイントに戻したりもしたが、改善されない。

ウイルスも疑い、defenderでフルスキャンをしたが、特に問題は見つからない。

IMEの設定で予測変換をOFFにすると若干よくはなったようだが、やはり我慢できないほどもたつく。

などなど、さんざんに試した挙げ句、Googleの日本語入力に置き換えてみると、入力はさくさくできるようになり、VideoStudioの動作も元にもどった。

"Windows 11 ime 不具合"といったキーワードで検索すると結構出てくるので、IMEの不具合自体は珍しいことではないようだ。それでも、そのせいで他のアプリのマウス操作にまで影響がでるのは驚きだ。

しばらくはGoogle日本語入力を使い続けることになりそうだ。

2022年3月28日月曜日

Windows関連メモ

Windiws 11 で Windows セキュリティーが開かない
アップグレード後にフルスキャンをしておこうと思ったらWindows セキュリティーが開かない。
次のリンクの方法で、とりあえず起動するようにできた。コマンドの意味が書いてなく、まだ調べていないので他のアプリへの影響などはわからない。
手順
  • すべてのアプリを終了した状態(アイドル状態)
  • Windows PowerShell を管理者権限で起動
  • コマンド実行
    Get-AppxPackage Microsoft.SecHealthUI -AllUsers | Reset-AppxPackage
  • Windows セキュリティを起動、動作確認
Windows 11 コマンドでシステム>電源を開く
ディスプレイの電源OFF、スリープまでの時間設定のダイアログにたどり着くに多くの手数が必要。次の要領でシステム>電源へのショートカットを作れる。
  • 画面の空白部分を右クリック > 新規作成 > ショートカット
  • [項目の場所を入力してください]に"ms-settings:powersleep"を設定
  • [次へ]クリック
  • [このショートカットの名前を入力してください]に(例えば)"電源スリープ"を設定
  • [完了]クリック
デスクトップにショートカットアイコンが作成される。

Windows 11 エクスプローラの行間を狭くする(以前と同じにする)
ファイルエクスプローラーの行間が広くなって表示項目数が減ってしまった。
エクスプローラ > 表示 > コンパクトビュー で行間を以前と同じように狭くできる。

アクティブなウインドウのディスプレイ切替
マルチディスプレイ環境でディスプレイの接続が切れると、そこにあったウインドウが表示されなくなり困ることがある。そんなときに便利。

スタートメニューの場所

  • ユーザ別
    C:\Users\UserName\AppData\Roaming\Microsoft\Windows\Start Menu\Programs
  • 全ユーザ共通
    C:\ProgramData\Microsoft\Windows\Start Menu\Programs

Windows MediaPlayer再インストール



2022年2月3日木曜日

DIGA DMR-BRT220 電源が落ちる 修理記録

 Panasonic DIGA DMR-BRT220 が故障した。

最初はハードディスクが認識されなくなったと思われたのだが、どうやら電源が数十秒で落ちるようになっていたためらしく、最終的には冷却ファンを掃除することで直ってしまった。とはいえ、そこまでたどり着くのに結構手間暇かかったので、ちょっとまとめておく。

まずは録画済みの番組をなんとか復活できあいものかと試みたが、これは残念ながら失敗してしまった。これについては後ほど補足する。

もともとのハードディスクと同じモデルのものを購入、換装することで動作するように見えたが、30秒程で電源が落ちてしまう。

最初に見つけたのはこのページ

電気屋さんのお仕事 パナソニックディーガ XW300電源が入らない

ICプロテクタ(フューズのようなものらしい)が切れると同じ現象になるらしい。
このページを参照して、同様の故障を修理した記事もある。

検索したらただ乗りだった件

DIGAとICプロテクタに関しては、他にもいろいろ見つかる。


そこで、分解し基盤を取り出して調べてみる。DMR-BRT220のICプロテクタについてズバリ書いてある記事は見つからなかったので、それらしいものを探してみる。
基盤に印刷してある部品名がIPで始まり、四角の中にKとマークしてものが表に二つ、裏にひとつあった。テスタで調べてみるとどれも導通している。う~~む、これのせいではなさそうだ。

電源回路のコンデンサが壊れている場合も、同様の現象になるらしい。


この場合には膨れているコンデンサがあるようだが、見ためにはそれらしきものは見当たらない。

半ばあきらめつつ他の手がかりを探してみると、冷却ファンの故障が原因というのが見つかった。"ファン"をキーワードに入れるといくつも出てくる。


電源を入れてみると、ファンが一瞬回るがすぐ止まる。これが正常な動作なのか分からない。埃を取って、指で回したりを何度か繰り返しているうちに回るようになった。すると、電源も入ったままになり、なんと直ってしまった。

ということで、電源がすぐに落ちてしまう現象が発生したら、まずはファンが回転しているかチェック。回転していない、あるいは不安定ならファンの掃除を念入りに行う。精密機械用潤滑油をさすのもよさそうです。

参考リンク


それでもだめなら、ICプロテクタや電源回路のコンデンサを疑いましょう。

2021年6月14日月曜日

C# TagLib#でwavファイルのタグを設定する方法

TagLibでmp3のタグ編集アプリを作りつつ、ついでにwavのタグ設定を試してみました。

mp3で文字化けに悩まされたましたが、wavでもやはり発生しました。

wavの場合の問題は、WindowsはwavのタグをShift-JISで読み書きするのに対し、TagLibはUTF8で読み書きすることです。

TagLibはファイルから読み込んだバイト列をByteVectorオブジェクトに格納し、これをAPIを介してStringにしています。

なので、そのAPIを介さずに、TagLibが保持しているバイト列をShift-JIS EncodingでString化し、Saveする前にバイト列を直接セットすれば文字化けを回避できます。

TagFileを作るときはファイルの種類を意識する必要はありません。

    TagLib.File TagFile = TagLib.File.Create(FilePath);

TagFileからTagを取得するときは、TagLib.TagTypes.RiffInfoを使います。

    TagLib.Riff.InfoTag tag = TagFile.GetTag(TagLib.Riff.InfoTag, true);

次の要領でShift-JIS Endodingでバイト列をString化します。

    Encoding SjisEnc = System.Text.Encoding.GetEncoding("shift-jis");
    String GetStringFromSjisPropery(TagLib.Riff.InfoTag tag, TagLib.ByteVector id)
    {
        TagLib.ByteVectorCollection vals = tag.GetValues(id);
        foreach (TagLib.ByteVector v in vals)
        {
            byte[] b = v.Data;
            if (b.Length > 0)
            {
            return SjisEnc.GetString(b);
            }
        }
        return null;
    }

Artist, Album, Titleのidは次のようになります。

    ByteVector idArtist = new ByteVector(Encoding.ASCII.GetBytes("IART"));
    ByteVector idAlbum = new ByteVector(Encoding.ASCII.GetBytes("IPRD"));
    ByteVector idTitle = new ByteVector(Encoding.ASCII.GetBytes("INAM"));

出力時は、save直前にShift-JISでバイト列化したByteVectorを当該項目にセットします。

    ByteVector ToByteVector(ByteVector id, String str)
    {
        return new ByteVector(SjisEnc.GetBytes(str+ " "));
    }

なぜか文字化けるので、strにスペースを一文字足してからバイト列化しています。
(スペースではなくnull('\0')が正しいのかも?)

StringをByteVectorにしたものを、次の要領でtagにセットします。

    ((TagLib.Riff.InfoTag)tag).SetValue(id, ToByteVector(id, str));

uintのTrackなど、String以外の項目はTagLibのAPIで直接操作できます。

Windows内だけ利用する場合は、これでOKでしょう。他のOS環境や、アプリによっては文字化けるかもしれません。

[追記]

Wavのヘッダー仕様はRIFFで定義されていますが、これのTRACKのIDが曖昧です
IPRTITRK, TRCKの3つが使われる場合があるようです。
TagLibは
IPRTで読み書きしていますが、WindowsのエクスプローラはITRKの値を表示します。

TagLibでITRKに値のRead/Writeは次のようにします。 

  ByteVector idTrack = new ByteVector(Encoding.ASCII.GetBytes("ITRK"));
  //Read
  uint trackNum = ((TagLib.Riff.InfoTag)tag).GetValueAsUInt(idTrack);
  //Write
  ((TagLib.Riff.InfoTag)tag).SetValue(idTracktrackNum);

-------

ついでながら、TagLibは様々なフォーマットに対応しています。
wma、m4a(aac)を試したところ、これらはutf8で問題ないようで、基本的にmp3と同じ要領で読み書きできます。

TagFile.GetTag(TagLib.TagTypes)の引数に使う定数は次のようになります。

    wma: TagLib.TagTypes.Asf
    m4a: TagLib.TagTypes.Apple

2019年12月26日木曜日

Windows 10 システムを「バックアップと復元」を使って別ディスク(SSD)へ移行する方法

システムディスクをSSDへ移行するにあたり、引っ越しソフトがあればそれを使うのが簡単ですが、ディスクが壊れた時の復旧時にも必要なことなので「バックアップと復元」を使って行なってみました。これが意外と単純ではなかったので、メモしておきます。

「バックアップと復元」でシステム移行を行う場合、移行先ディスクのデータ容量は元ディスクと同じか、大きくなければいけません。

もし小さいディスクへ移行させたい場合はAOMEI Partition Assistantなどを利用し、元システムのパティション構成を変更してからバックアップを作れば対応可能です。

移行作業

①「バックアップと復元」で「システム修復ディスク」を作成する。
②「バックアップと復元」で「システムイメージ」を作成する。私はシステムディスク(C:)だけのイメージを作成。
③移行先のディスクをdiskpartで完全消去する。
 もしかしたら、購入したままのまっさらなディスクならこの作業は不要かもしれない。
 これを行なわないと復元実行時に次のエラーが発生する。

  データ ディスクが現在 BIOS 上でアクティブに設定されています。その他の
  ディスクをアクティブに設定するか、または DiskPart ユーティリティを使用
  してデータ ディスクから不要なデータを消去した後で、復元操作を再試行し
  てください。

 コマンド実行要領
 >diskpart
   DISKPART> list disk   
   (表示される一覧からターゲットディスクの番号を探す)
   DISKPART>select disk n
   DISKPART>clean

 詳しくはこちらを参照
 <windows|トラブル|解決済>OSバックアップを復元出来ない

④現在のシステムディスクを取り外し、移行先のSSDを取り付ける。現在のシステムディスを取り付けたままだとそのディスクが回復対象になり、別ディスクには移行できない。

⑤「システム修復ディスク」で起動
 詳しくはこちらを参照
 Windows 10 システムイメージからの復元(戻す)方法を徹底解説

⑥「イメージでシステムを回復」を実行
 詳しくは⑤のリンクを参照

追記
システム修復時に再起動後ブートできず”inaccessible boot device”エラーでブルースクリーンになった。UEFI(BIOS)のBOOT設定でACHIがIDEに変わっていたことが原因で、ACHIに変更後、無事再起動できた。

移行先ディスクの回復パティション移動

ここまでで別ディスクにシステムが移行ができたのですが、元ディスクに「回復パティション」がある場合、これも含めてまったく同じ場所に復元されてしまいます。より大きなディスクを用意しても、これが邪魔でシステムのパティションを大きくすることができません。
フリーバージョンのAOMEI Partition Assistantで、パティションをディスクの末尾に移動させることができます。

詳しくはこちらを参照

試していませんが、diskpartを使って回復パティションの移動(削除・再設定)を行う方法が紹介されています。
詳しくはこちらを参照

回復パティション移動/削除後は、「ディスクの管理」を使ってパティションの拡張が可能です。

回復パティションの削除
引っ越し完了後にもとのディスクを再利用しようするにあたり「回復パティション」を削除したいが、「ディスクの管理」では表示のみで削除不可なので、diskpartコマンドで削除しました。
「AOMEI Partition Assistant」では、できそうでできませんでした。有償版なら可能かもしれません。

・コマンドプロンプトを管理者権限で起動
・次の要領でコマンド実行
 >diskpart
 DISKPART> list disk
   ディスク      状態           サイズ   空き   ダイナ GPT
   ###                                          ミック
   ------------  -------------  -------  -------  ---  ---
   ....
   ディスク 3    オンライン           119 GB   118 GB

 DISKPART> select disk=3
 ディスク 3 が選択されました。

 DISKPART> list partition
   Partition ###  Type                Size     Offset
   -------------  ------------------  -------  -------
   Partition 1    回復                 510 MB   118 GB

 DISKPART> select partition=1
 パーティション 1 が選択されました。

 DISKPART> delete partition override
 DiskPart は選択されたパーティションを正常に削除しました。

詳しくはこちらを参照
Windows10 - 回復パーティションの削除(DiskPart)

2019年6月11日火曜日

C# TagLib#のMP3タグ文字化け対策

C#でMP3タグの編集を行うアプリを作るのにTagLib-sharpを使ってみました。
まずは、こんな簡単にタグを設定できます、といった例が見つかります。

    TagLib.File tagFile = TagLib.File.Create(filePath);
    tagFile.Tag.Title = "My Favorite Things";
    tagFile.Tag.Album = "BEST";
    tagFile.Tag.Performers = new string[] { "My Favorite Singer" };
    tagFile.Save();

確かにこれでできるのですが、日本語だとWindows 10のファイルエクスプローラで文字化けすることがあります。そんなときもGrooveミュージックやiTunesでは表示されるので、これはTagLibの問題ではなく、タグを解釈するアプリ側の問題です。

手っ取り早い解決策は、次の例のようにタグのバージョンをID3v2.4にすることです。

    tag = (TagLib.Id3v2.Tag)TagFile.GetTag(TagLib.TagTypes.Id3v2, true);
    tag.Version = 4;

File作成時はMP3ファイルのタグバージョンが適用されるので、読み込み後に変更します。
GetTag()の第二引数にtrueをつけると、Tagがない場合は新規作成します。

いささか解せないのは、タグ未設定のMP3にエクスプローラのプロパティーで属性を設定すると、ID3V2.3のタグが設定されることです。ID3V2.3がデフォルトバージョンならちゃんと対応してほしいものですが...

もうひとつの方法はいったんID3V2.3を全て削除し、必要なタグだけ設定する方法です。これは後述しますが、ID3V2タグを再作成すると非同期化がOFFになるからです。

    TagFile = TagLib.File.Create(filePath);
    TagFile.RemoveTags(TagLib.TagTypes.Id3v2);
    TagLib.Id3v2.Tag tag =
        (TagLib.Id3v2.Tag)TagFile.GetTag(TagLib.TagTypes.Id3v2, true);

余分なタグを消してファイルサイズを小さくできるので、場合によっては有用でしょう。

これで一件落着なのですが、なぜ文字化けが発生するか調べたので書いておきます。

冒頭のコードで発生するエクスプローラの文字化けには、次の要因が絡んでいます。
①ID3v2タグの非同期化フラグがONになっている。
②項目により文字化ける場合と無効(非表示)となる場合がある。
③ID3V2タグで未設定だがID3V1に対応する項目があると、ID3V1の項目が適用される。
④ID3V2タグが無効だがID3V1に対応する項目があると、変更は無視され、ID3V1の項目が表示される。

①の非同期化とは、MP3v2タグに対応していなアプリがタグを音声データと誤認しないようにするための処理で、この結果UTF16文字列の先頭BOMが0xFFEE"だと”0xFF00EE"に変換されます。この場合にWindows 10のエクスプローラで文字化けが発生します。試しにバイナリエディタで”0xFFEE00"に変更してみると、文字化けが解消します。

冒頭のコードのように tagFile.TagのプロパティーにStringをセットした場合は、ID3V2.3ではUTF16ですが、ID3V2.4ではUTF8で出力されます。そのため、ID3V2.4では”0xFF00EE"は発生しません。

非同期化フラグはID3V2.4にも存在し、ONの場合に類似の処理が行われます。以下のコードでUTF16で出力すると”0xFF00EE"というバイトシーケンスが発生します。ですが、エクスプローラはID3V2.4は適切に処理してくれるようで、文字化けしません。

frameの文字コードを設定し、frameのTextにStringをセットします。

    TextInformationFrame fTIT2 =
        TextInformationFrame.Get(tag, FrameType.TIT2, StringType.UTF16, true);
    fTIT2.TextEncoding = StringType.UTF16;
    fTIT2.Text = new String[] { "タイトル” };

非同期化をOFFにすれば文字化けしなくなるのですが、MP3ファイルの非同期化フラグはTagLib.Tagクラスのプライベート変数にセットされ、それを操作するパブリックメソッドはありません。

ただし冒頭に書いたように、いったんID3V2タグを削除し再作成すれば、非同期化がOFFの状態になります。

TagLibのソースコードを変更すればプログラムでの対応も可能で、例えば次のようにTagLib.Id3v2.Tag.cs にメソッドを追加し、Save()を呼ぶ前にこのメソッドを呼ぶと非同期化をOFFにできます。

    public void ClearUnsynchronisationFlag ()
    {
        header.Flags &= ~HeaderFlags.Unsynchronisation;
    }

非同期化はID3V2.3を認識しない再生アプリのための処理なので、そんな古いアプリを考慮する必要がなければ問題ないでしょう。Windows 10のエクスプローラでも、MP3ファイルのタグ情報をプロパティダイアログの詳細で変更、保存すると非同期化はOFFになります。これを利用すればID3V2.3のままでの文字化け対策にもなります。

項目によって現象が異なり、TitleとAlbumは次の要領でUTF16BEを適用することで文字化けを解消できます。UTF16BEにはBOMがなく”0xFF00EE"が発生しないことが影響しているのでしょう。

    TextInformationFrame fTIT2 = TextInformationFrame.Get(tag2, FrameType.TIT2, StringType.UTF16BE, true);
    TextInformationFrame fTALB = TextInformationFrame.Get(tag2, FrameType.TALB, StringType.UTF16BE, true);
    fTIT2.TextEncoding = StringType.UTF16BE;
    fTALB.TextEncoding = StringType.UTF16BE;
    fTIT2.Text = new String[] { TitleString };
    fTALB.Text = new String[] { AlbumString };

既存のFrameがある場合、Getが返すFrameのStringTypeは既存の設定のままなので、StringTypeの再設定を行っています。

残念ながらPerformers(参加アーティスト)はこれでも文字化けます。あまり現実的ではありませんが、Performersは半角英数字(Laten1)に限れば次のコードで文字化け回避できます。

    TextInformationFrame fTPE1 = TextInformationFrame.Get(tag2, FrameType.TPE1, StringType.Latin1, true);
    fTPE1.TextEncoding = StringType.Latin1;
    fTPE1.Text = new String[] { PerformersString };

③の現象は、MP3ファイルにまだID3V2タグがない、あるいは対象項目がセットされていない場合に発生します。
TextInformationFrame.Get(tag2, FrameType.TIT2, StringType.UTF16, true);を使用することでID3V2タグがまだない場合は新規作成してくれます。
 TextInformationFrame.Get(tag2, FrameType.TIT2, StringType.Latin1, true);を使用することで、対象項目がセットされていない場合は追加してくれます。

それでも非同期化がONだと④の現象が発生し、ID3V2への変更は無視され、ID3V1の情報が表示されます。

ついでながら、TagLibではUTF16LEも定義されています。TagLibとしては処理しているのですが、UTF16LEではエクスプローラに文字が表示されません。GrooveミュージックでもiTunesでもダメです。調べた範囲では、ID3V2の仕様ではUTF16LEは定義されていないようです。

----------

【追記】
既に設定済みのMP3タグをTagLibで読み込むと文字化けしてしまうことがあります。
次のページに対応策が紹介されています。

2019年1月7日月曜日

Windowsキーを無効にする方法

幼児用のお遊びアプリを作ってみたのですが、なにせメチャクチャにキーをたたくので、Windowsキーなどシステムが優先的にイベントを扱うキーが押されるとスタートメニューが表示されたり、Print Scrで画面がキャプチャーされたりしてしまいます。

そこで、こうしたキーを無効にする方法を調べてみました。

まずはMicorosoftのページ。
Disabling Shortcut Keys in Games

ゲームなどで誤って Windows key を押してしまっても、好ましくない動作をしないようにする方法を紹介しています。しかし、C++なので C# のプロジェクトにはそのままでは導入できません。

こちらからはC#のサンプルがダウンロードできます。
A Simple C# Global Low Level Keyboard Hook (StomySpike / Code Project)

そのままソースを取り込んでも動作可能ですが、このソースのままだと次のようなエラーが発生します。

マネージ デバッグ アシスタント 'CallbackOnCollectedDelegate'  が発生しました
…コールバックが、型 '...+keyboardHookProc::Invoke' のガベージ コレクションされたデリゲートで行われました。…デリゲートをアンマネージ コードに渡すとき、デリゲートは 2 度と呼び出されないことが確実になるまでマネージ アプリケーションによって維持されなければなりません。'

これの解決策が次の Stack Overflow のQ&Aにありました。私は、これに従って若干変更して使っています。

C++のアンマネージドの環境ではdelegateがガベージ コレクションされなかったが、C#のマネージドの環境ではガベージ コレクションされてしまい、その結果上記のエラーが発生します。

同様の内容のページがあります。
CallbackOnCollectedDelegate が発生しました。(Cafe's Room)

callbackOnCollectedDelegateについてのMicrosoftの解説です。
callbackOnCollectedDelegate MDA

このソースで使われている関数については、次のページの説明が参考になります。
EternalWindows / Windows 開発 / メッセージ管理 / メッセージフック

2018年12月2日日曜日

C# Properties.Settings user.configの破損による例外発生への対応

Properties.Settingsを使用したアプリケーション設定を利用していますが、これが保存されるuser.configが何故か壊れ、次の例外が発生するようになりました。

 System.Configuration.ConfigurationErrorsException
 構成システムを初期化できませんでした。

たぶんデバッグ中にuser.configが正常に保存されなかったためだと思いますが、この状態になるとなんとも困ったことになります。

user.configはxmlファイルですが、これが破損するとロード時にエラーが発生します。その結果、Properties.Settings.Defaultのプロパティーに適切な値が設定されず、get/setいずれでもエラーが発生するようになってしまいます。

いろいろ試したところでは、Properties.Settings.Defaultオブジェクトは一度だけ作られ、Reloadしても作り直されません。私の結論としては、user.configを削除しアプリを再起動するしか回復の方法がありません。

user.configを削除してアプリを再起動すれば、設定は初期状態になるものの、例外は発生しなくなります。

user.configは通常は次の場所にあるでしょう。
C:\Users\UserName\AppData\Local\AppName\AppName.exe_Url_HashValue

このフォルダの場所はOSバージョンやインストールの仕方で異なる可能性があり、ハッシュ値はアプリのバージョンにより異なること可能性があります。また、開発環境ではビルドによっても異なるハッシュ値が作られることがあります。

この問題への対処方法が次のWebページにありました。

Properties.Settings.Defaultへのアクセスで例外が発生したときに、そのInnerExceptionのFileNameを使い、user.configを削除しています。

このWebページでは削除後に Settings.Default.Reload(); を呼んでいますが、どうやらReloadはすでに読み込んだxmlから値を元に戻すようで、Settings.Defaultのプロパティーのエラー状態は回復しません。Reloadではうまくいかないことはこのページのコメントにも書かれています。

そこで、次のような要領で、エラーが発生したらuser.configを削除し、アプリを再起動するようにしました。

try
{
String val = Properties.Settings.Default.someStringProperty;
}
catch (Exception ex)
{
 //MessageBox.Show("設定ファイルが破損しています。...");
string filePath = ((ConfigurationErrorsException)ex.InnerException).Filename;
File.Delete(filePath);
 Application.Restart();
}

VisualStudioでConfigurationErrorsExceptionが見つからない場合は、プロジェクトの参照にSystem.Configurationを追加します。

2018年5月20日日曜日

Windowsファイル履歴のエラー「ファイルのバックアップを実行できません」

Windows ファイル履歴実行されない場合の対処法
(同一フォルダ内の全角/半角同名のファイルをチェックするJSスクリプト)

しばらく前からWindows 10のファイル履歴が更新されなくなっていました。
イベントログを見るとエラーが発生しています。

コントロールパネル > ファイル履歴 > 詳細設定 > ファイル履歴イベントログにつぎのエラーが書かれていました。

C:\Users\(userName)\AppData\Local\Microsoft\Windows\FileHistory\Configuration\Config でユーザー ライブラリの変更のスキャンと変更されたファイルのバックアップを実行できません

これは、同じフォルダーに、全角と半角で同じ名前のファイルが保存されていると、正しくバックアップできないために発生するとのこと。

マイクロソフト コミュニテー
ファイル履歴が動作しない 

富士通Q&A
[Windows 10] ファイル履歴で正しくバックアップされているかどうかを確認する方法を教えてください。

私の場合にも
確かに上記のファイル名に該当するものがありましたが、それを直してもまだエラーが発生しました。そこで次の方法を試したところ回復しました。ライブラリに含まれるドキュメントなどの場所を変更したりしていたので、何かConfigがおかしくなったのかもしれません。
  • 次のフォルダを削除(リネームし成功したら削除する方が安全)
    C:\Users\(userName)\AppData\Local\Microsoft\Windows\FileHistory
  • コントロールパネル > ファイル履歴でドライブ選択し「今すぐ実行」
問題となるファイルを探すのに目視で調べるのは厄介なので、javascriptでファイル名のダブりをチェックするスクリプトを作ってみました。このスクリプトで”要チェック”と表示されるファイル/フォルダ名を調べてみてください。エラーにならない場合もどちらか一方しかファイル履歴に保存されていない可能性がありますので、チェックしてみる価値はあるでしょう。

このブログの後半にソースを載せてあります。

このスクリプトには全角/半角文字の対象リストがあります。私の場合はこれで問題になっていたファイルが見つけられましたが、このリストに問題になる文字が全て含まれているかわかりません。また、このスクリプトで”要チェック”とされても、実際には問題ない場合もあるでしょう。

状況はいささか複雑で、ファイル履歴が更新されるがいずれか一方しか履歴に保存されない場合もあり、またファイル履歴に保存・更新される順序でエラーになったりならなかったりすることもあるようです(手間がかかるので十分調べていません)。
また、フォルダ名とファイル名の間でも同様のことが発生します。

以下は次の環境で試した結果です。Windowsのバージョンによって動作が異なるかもしれません。

テスト環境:Window 10 Pro, バージョン 1709, OS ビルド 26299.431
  • エラーが発生する例:
    space .txt <=> space .txt  (全角/半角スペース)
    space ].txt <=> space ].txt  (全角/半角スペース)
    TEST1.txt <=> TEST1.txt (全角/半角数字)
    test  <=> TEST (半角フォルダ名と全角ファイル名)  
  • エラーは発生しないがどちらか一方しか履歴に残らない例:
    space[ ].txt <=> space[ ].txt  (全角/半角スペース)
    TEST.TXT <=> test.txt  (全角/半角アルファベット、ピリオドいずれも半角)
    test#  <=> TEST# (半角フォルダ名と全角ファイル名)
なぜこんなことが起こるか考えてみました。

ポイントは、エラーは発生しないがどちらか一方しか履歴に残らない場合がある、ということでしょう。エクスプローラにとっては別ファイルだが、ファイル履歴にとっては同一ファイルとみなされる場合あるということです。

まずはWindowsのファイル名ではアルファベットの大文字小文字は区別されません。TEST.TXTとtest.txtは同じフォルダ内では同一ファイルとなり、そもそも別々に保存できません。これはファイル履歴でも同じです。

ファイルエクスプローラでのファイル名順のソートはSQLサーバの照合順序(WI)に準じているようで、全角/半角で同一文字とみなされるものは同等に扱われます。全角Aと半角Aは同等ですがイコールではなく、いずれも全角B、半角Bより小さいが、全角Aは半角Aより大きいと判断され、全角/半角はファイル名としては別のものとして扱われます。TEST.TXTとTEST.TXTは別ファイルになります。ところが、ファイル履歴ではどちらか一方しか保存されません。つまり同一ファイル(イコール)とみなされます。

ファイル履歴ではファイルエクスプローラの照合順序で同等のファイル名は同一ファイルとして扱われているような感じです。照合順序の判定では最初に同一文字として扱う文字を半角大文字に統一するなどして大小比較するでしょう。ここでイコールの場合に、ファイルエクスプローラでは元の名前で比較し大小を決定しているようですが、ファイル履歴では同一ファイルとして扱っているのではないかと思います。どういう場合にエラーになるかはわかりませんが、異なるファイルを同一ファイルとして扱おうとするのだから、エラーが発生しても不思議ではありません。

理由はともあれ、あやしそうなファイルはリネームしておいた方が無難でしょう。
------------------- スクリプト -------------------

//以下のスクリプトをテキストファイルにコピーし、
//fileNameCheck.jsなどの拡張子.jsの名前で保存する。

//コマンドプロンプト(cmd.exe)で、CScriptで実行する。
//例: >CScript fileNameCheck.js

//WScriptだとEchoの度にダイアログが表示される。

//ファイル履歴の対象フォルダをプログラムで取得する方法がわからないので、
//チェックするフォルダのリストを各自の環境に合わせて設定する。
var homeDir = "C:\\Users\\UserName\\";
var folderPaths = [
    homeDir + "Desktop",
    homeDir + "Documents",
    homeDir + "Pictures",
    homeDir + "Videos",
    homeDir + "Music"
];

//半角と同じと判断される文字の全角、半角対照辞書。
//regularizeNameでzen2hanに含まれる全角文字が見つかった場合、半角文字に置き換える。
//'#'などの記号は問題ないかもしれない。
//また、ハイフン、ピリオドなどは対応する全角文字が複数あるため、適切な対応は不明。
var zen2han = {
    ' ': ' ',
    'a': 'a',
    'b': 'b',
    'c': 'c',
    'd': 'd',
    'e': 'e',
    'f': 'f',
    'g': 'g',
    'h': 'h',
    'i': 'i',
    'j': 'j',
    'k': 'k',
    'l': 'l',
    'm': 'm',
    'n': 'n',
    'o': 'o',
    'p': 'p',
    'q': 'q',
    'r': 'r',
    's': 's',
    't': 't',
    'u': 'u',
    'v': 'v',
    'w': 'w',
    'x': 'x',
    'y': 'y',
    'z': 'z',
    'A': 'a',
    'B': 'b',
    'C': 'c',
    'D': 'd',
    'E': 'e',
    'F': 'f',
    'G': 'g',
    'H': 'h',
    'I': 'i',
    'J': 'j',
    'K': 'k',
    'L': 'l',
    'M': 'm',
    'N': 'n',
    'O': 'o',
    'P': 'p',
    'Q': 'q',
    'R': 'r',
    'S': 's',
    'T': 't',
    'U': 'u',
    'V': 'v',
    'W': 'w',
    'X': 'x',
    'Y': 'y',
    'Z': 'z',
    '1': '1',
    '2': '2',
    '3': '3',
    '4': '4',
    '5': '5',
    '6': '6',
    '7': '7',
    '8': '8',
    '9': '9',
    '0': '0',
    '@': '@',
    '!': '!',
    '"': '"',
    '#': '#',
    '$': '$',
    '%': '%',
    '&': '&',
    '\': '\'',
    '(': '(',
    ')': ')',
    '=': '=',
    '-': '-',
    '~': '~',
    '^': '^',
    '|': '|',
    '\': '\\',
    '[': '[',
    ';': ';',
    ':': ':',
    ']': ']',
    ',': ',',
    '.': '.',
    '/': '/',
    '`': '`',
    '{': '{',
    '+': '+',
    '*': '*',
    '}': '}',
    '<': '<',
    '>': '>',
    '?': '?',
    '_': '_'
};

var fso = new ActiveXObject('Scripting.FileSystemObject');
//zenに含まれる文字が対象文字列にあるか判断するのに用いる正規表現。初期化時にセットする。
var regexZen;

//.jsファイルをダブルクリックするとWScriptで実行されるので、exeの名前をチェックし、WScriptの場合は警告を出して終了する。
if (/cscript\.exe$/i.test(WScript.FullName)) {
    //zenに含まれる文字が対象文字列にあるか判断するのに用いる正規表現をセットする。
    var str = "[";
    for (key in zen2han) {
        str += key;
    }
    str += "]";
    regexZen = new RegExp(str);
    //対象のフォルダを順次チェックする。
    for (var i = 0; i < folderPaths.length; i++) {
        var folderPath = folderPaths[i];
        if (fso.FolderExists(folderPath)) {
            folderCheck(fso.GetFolder(folderPath));
        } else {
            WScript.Echo("Folderなし: " + folderPath);
        }
    }
} else {
    WScript.Echo("CScriptで実行してください。")
}

function folderCheck(folder) {
    //動作確認時にコメントを外すとチェック中のフォルダ名をログ表示する。
    //WScript.Echo("FOLDER: " + folder.Path);

    //フォルダ毎に辞書を初期化。
    var nameDict = [];
    //フォルダ内のファイル名をチェック。
var files = new Enumerator(folder.Files);
    for (; !files.atEnd(); files.moveNext()) {
        var file = files.item();
        regularizeName(file, nameDict);
    }
    //サブフォルダの名前をチェックし、再帰的にfolderCheckを実行。
var subFolders = new Enumerator(folder.SubFolders);
    for (; !subFolders.atEnd(); subFolders.moveNext()) {
        var subFolder = subFolders.item();
        regularizeName(subFolder, nameDict);
folderCheck(subFolder);
}
}

//zenに含まれる文字がある場合、それに対応するhanの文字に置き換える。
function regularizeName(item, nameDict) {
    var name = item.Name, newName;
    if (regexZen.test(name)) {
        //zen2hanに含まれる全角文字が見つかった場合、対応する半角に置き換え。
        var chars = name.split('');
        for (var n = 0; n < chars.length; n++) {
            var han = zen2han[chars[n]];
            if (han) {
                chars[n] = han;
            }
        }
        newName = chars.join('').toLowerCase();
    } else {
        //zen2hanに含まれる全角文字がない場合は小文字に統一。
        newName = name.toLowerCase();
    }
    var registerdItem = nameDict[newName];
    if (registerdItem) {
        //zen2hanで全角を半角に変換した名前が既に登録済みの場合にログを出力。
        WScript.Echo("要チェック:" + item.Path + " <=> " + registerdItem.Name);
    } else {
        //まだnameDictに登録されていない場合は追加。
        nameDict[newName] = item;
    }
}

    2017年11月15日水曜日

    WiX toolset msi ダイアログ多国語対応 

    以前のブログでWiX toolsetによる多国語対応msiの作り方について書いたが、インストーラの主要なメッセージの表示が日本語化されても、細かなダイアログのメッセージが英語のままであった。

    WiX tooksetの以下の場所に多国語メッセージ定義ファイルがある。
    C:\Program Files (x86)\WiX Toolset v3.11\SDK\wixui

    これに含まれる WixUI_ja-jp.wxl で日本語メッセージが設定されているが、これが適用されない項目がある。

    ここに同様の件でのQ&Aがあった。
    WIX: Statuses while rolling back uninstall are not localized in French

    これによるとActionに対応するローカライズテキストがデフォルトでは設定されていないようで、Product.wxsに次のような追加を行うと、日本語メッセージが適用されるようになる。

    この Q&A では String の Id を別のものに置き換え、言語別 wxl でメッセージを書き換えているが、WiX規定のものであれば次の要領で日本語を適用できる。

     <UI>
        <ProgressText Action="RemoveShortcuts" Template="!(loc.ProgressTextRemoveShortcutsTemplate)">
                !(loc.ProgressTextRemoveShortcuts)
        </ProgressText>
    </UI>
    

    
    
    これにより Action="RemoveShortcuts” に ja-JP では WixUI_ja-jp.wxl の ProgressTextRemoveShortcuts が適用されるようになる。

     WixUI_ja-jp.wxl では次のように設定されている。

    <String Id="ProgressTextRemoveShortcuts" Overridable="yes">ショートカットを削除しています</String>
    <String Id="ProgressTextRemoveShortcutsTemplate" Overridable="yes">ショートカット: [1]</String>
    

    ProgressText の Template がなくてもエラーにはならないが、メッセージが表示されなくなるかもしれないので設定しておいたほうがよいだろう。ただし、メッセージによっては Template が設定されいないものがあり、その場合は Template は不要で、設定したが対応するStringがないとエラーになる。

    Actionに対応するものは以上で置き換わるが、これだけでは日本語化されないものもある。
    たとえば、”Please wait while Windows configures MyApp"があるが、これはWiX toolset で次のように設定されている。



    ErrorProgressText.wxs
        <Error Id="20">!(loc.Error20)</Error>
    
    WixUI_en-us.wxl
        <String Id="Error20" Overridable="yes">Please wait while Windows configures [ProductName]</String>
    

    このようなものについては Product.wxsの UI タグに次のように設定を追加する。
    
    
     <UI>
        <Error Id="20">!(loc.Error20)</Error>
    </UI>

    以下 UI タグの設定例。気付いたところだけ対応したもの。また、Actionについては上記Q&Aに含まれていたものを入れてあるので、msiによっては不要なものもある。

    <UI>
        <ProgressText Action="FileCost">!(loc.ProgressTextFileCost)</ProgressText>
        <ProgressText Action="InstallFiles" Template="!(loc.ProgressTextInstallFilesTemplate)">!(loc.ProgressTextInstallFiles)</ProgressText>            
        <ProgressText Action="CreateShortcuts" Template="!(loc.ProgressTextCreateShortcutsTemplate)">!(loc.ProgressTextCreateShortcuts)</ProgressText>
        <ProgressText Action="WriteRegistryValues" Template="!(loc.ProgressTextWriteRegistryValuesTemplate)">!(loc.ProgressTextWriteRegistryValues)</ProgressText>
        <ProgressText Action="RegisterUser" Template="!(loc.ProgressTextRegisterUserTemplate)">!(loc.ProgressTextRegisterUser)</ProgressText>
        <ProgressText Action="RegisterProduct" Template="!(loc.ProgressTextRegisterProductTemplate)">!(loc.ProgressTextRegisterProduct)</ProgressText>
        <ProgressText Action="PublishFeatures" Template="!(loc.ProgressTextPublishFeaturesTemplate)">!(loc.ProgressTextPublishFeatures)</ProgressText>
        <ProgressText Action="PublishProduct">!(loc.ProgressTextPublishProduct)</ProgressText>
        <ProgressText Action="RemoveFiles" Template="!(loc.ProgressTextRemoveFilesTemplate)">!(loc.ProgressTextRemoveFiles)</ProgressText>
        <ProgressText Action="RemoveExistingProducts" Template="!(loc.ProgressTextRemoveExistingProductsTemplate)">!(loc.ProgressTextRemoveExistingProducts)</ProgressText>
        <ProgressText Action="RemoveShortcuts" Template="!(loc.ProgressTextRemoveShortcutsTemplate)">!(loc.ProgressTextRemoveShortcuts)</ProgressText>
        <ProgressText Action="Rollback" Template="!(loc.ProgressTextRollbackTemplate)">!(loc.ProgressTextRollback)</ProgressText>
        <ProgressText Action="RollbackCleanup" Template="!(loc.ProgressTextRollbackCleanupTemplate)">!(loc.ProgressTextRollbackCleanup)</ProgressText>
        <Error Id="16">!(loc.Error16)</Error>
        <Error Id="20">!(loc.Error20)</Error>
        <Error Id="21">!(loc.Error21)</Error>
    </UI>

    でも、これって ErrorProgressText.wxs の一部ですね。ということで、ErrorProgressText.wxs の UI タグの中身をすっかり Product.wxs にコピーしてみた。特に問題はなさそうだ。違いはというと ErrorProgressText.wxs は wixlib のビルド時に適用され、Product.wxs はmsiビルド時に適用されているということだと思う。

    関連ブログ