Quantcast
Channel: プログラミング雑記
Viewing all 70 articles
Browse latest View live

WiX toolsetのBootstarpperを試してみた(1)

$
0
0
多国語対応のmsiのためのBootstarpper作りでWiXのBurnを試してみた。これによる言語切り替えも実現できたのだが、それだけの目的ならdotNetInstallerによるBootstrapperBootstrapper作りの方が面倒がない。とはいえ本格的なインストーラを作る場合には強力なツールなので、Burnについて試したことをまとめておく。

WiXはV3.11を使用。

概要
Burnは複数のプロジェクトを一つのバンドルにまとめるもので、そのプロセルは大きくふたつに別れる。インストールを開始する前に実行するBootstarpperと、それに続く一連のインストール実行だ。

Burnを使ってインストーラを作る場合は、Bootstarpperにより必須環境を整え、そのUIによりユーザがインストール等の条件を設定し、それ以降はその条件に従い実行され、個々のmsiでは設定を変更しないのが基本のようだ。

msiにUIを表示させることはできるが、長時間かかるインストールの途中で何度もダイアログを表示するのはいただけないし、Bootstarpperが決めた条件をその後のmsiで変更できるようでは、複数のmsi間で不整合が生じうる。

また、Burnはインストール時のシーケンスを作るだけでなく、アップデート、変更、アンイストールも含めて管理するできるようになっている。そのため、Burnが作るバンドルもレジストリに登録され、コントロールパネルの「プログラムと機能」一覧に表示される。個々のmsiもここに表示することは可能だが、全てバンドルで管理できるようにしておく必要がある。

開発者のコメントもあります。
B is for Bundle and that's good enough for me.

それでも、BundleのBootstapperからオプションを設定してmsiを起動し、msiのUIでインストール/アンインストールを行うことができる。以下、多言語対応msi用のBundle作りを題材とした手順。

はじめに

試す前にレジストリのバックアップを取っておくことをお勧めする。アンインストールが適切に行なえないと、インストーラでは削除できない項目がレジストリに残ることがある。

mis作成時のProduct.wxsで、Product要素のIDを"*"としていた場合は固定のGUIDを用いてビルドし直す。"*"だと既にmsiがインストール済みか検知するときに見つからず、アンインストールできない、あるいは常にInstallしようとする、ということが発生する。

インストーラのログは次の場所に書き出される。
C:\Users\UserName\AppData\Local\Temp\

Burnによるバンドル作成
  • misプロジェクトが含まれるソリューションを開く。
Bootstarpperプロジェクト追加
  • ソリューション右クリック > 追加 > 新しいプロジェクト
    左ペイン WiX toolset v3 > 中ペイン Bootstapper Project for WiX v3 > OK
    • Bootstrapper1の名前で保存
      これでソリューションにBootstarpperプロジェクトが追加される。
  • Bundle.wxs変更
    • namespace追加
    • <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
           xmlns:bal="http://schemas.microsoft.com/wix/BalExtension"> 
      • xmlns:balは必須ではないがやがて必要になる
    • Bundle要素を変更
      <Bundle Name="MyAppSetup" Manufacturer="name" ...
              UpgradeCode="GUID">
      • Name: コントロールパネルのプログラムの機能一覧に登録される名前。
        exeの名前はプロジェクト > プロパティー > Output nameで設定。
      • Manufacturer: 空だとエラーになる。
      • UpgradeCode: msiとは別のものを設定する。
    • Chain要素にMsiPackage要素を追加
    • <Chain>
        <MsiPackage SourceFile="..\SetupProject1\bin\Release\WavCutterSetup.msi"
                    DisplayInternalUI="yes" Visible="no">
          <MsiProperty Name="TRANSFORMS" Value="[TRANSFORMS]" />
        
        </MsiPackage>
      </Chain>
      • DisplayInternalUI="yes"の場合、msiのUIを表示(実体はmsiexecの起動パラメータ)
      • Visible="yes"の場合、msiもコントロールパネルの「プログラムと機能」一覧に登録される。
StandardBootstrapperApplication.RtfLicenseによるテスト
  • BootstrapperApplicationRef要素追加
    <BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.RtfLicense">
    • StandardBootstrapperApplication: 予め用意されている標準アプリ
    • RtfLicenseはライセンス表示後Chain中のPackageを実行する。
      • HyperlinkLicenseもあるが、試していない。

  • ビルド > MyAppSetup.exe実行
    ライセンスに同意し、Installをクリックするとmsiが実行される。
    msiが再度ライセンス同意を求める。

  • 「プログラムと機能」に登録
    上記設定の場合、コントロールパネルの「プログラムと機能」一覧にMyAppSetupが登録される。

  • MyAppSetupダブルクリック
    MyAppSetupダブルクリックで変更/アンインストールが実行される。
    「Repair」「Uninstall」はBundleとしてのものなので、msiがこれらに対応していなくてもが表示される。

  • 削除実行
    「Uninstall」クリックで、サイレントでmsiによる削除が実行される。
Bundle想定しているインストールの手順は、概ねこのようなものと考えられる。

ここでは何も設定していないが、本来はDOT.NET Frameworkのチェック、インストールなど、必須環境を整える作業を行ってから、Chain内のインストールが順次行われる。

ちなみに、msiでは必須コンポネントの存在有無チェックは行えるが、無い場合はメッセージ表示でインストールを中断する。

アンインストール

このMyAppSetupを二度実行した場合、既にBundleが登録されているため、コントロールパネルからの場合と同様に「Modify Setup」のダイアログとなる。

msiにスタートメニューへのアンインストールボタン追加が設定されている場合は、そのボタンも追加される。先にmsiによるアンインストールを行うと、このアプリは削除されるがBundleは残っているため、再度MyAppSetupを実行すると「Modify Setup」のダイアログとなる。Bundleでアンインストールを行えば、アプリ削除後であってもBundleが削除される。msiがひとつだけだと違和感はあるが、Bundleの動作としては問題ない。

MsiPackageでVisible="yes"とすると、msiも「プログラムと機能」一覧に追加されるので、これと同じことが起こる。

ManagedBootstrapperApplicationHostによる機能追加
RtfLicenseは表示するライセンス、ダイアログのスタイルなどをカスタマイズできるが、UIに機能を追加することはできない。これが必要な場合はManagedBootstrapperApplicationHostを使用する。

ManagedBootstrapperApplicationHost は BootstrapperApplication のサブクラスを含むクラスライブラリ(.dll)を読み込み、そのBootstrapperApplication インスタンスのイベントハンドを順次呼び出すことで、インストール環境を整える。
  • クラスライブラリ追加
    ソリューション右クリック > 追加 > 新しいプロジェクト > Visual C# > クラスライブラリ(.NET Framework)
    • プロジェクト名、アッセンブリ名=ClassLibrary1で追加。
      追加するクラスはMyBootstrapper.csとする。
    • 参照追加
      C:\Program Files (x86)\WiX Toolset v3.11\SDK\BootstrapperCore.dll
    • 次のconfigファイルをBootstrapper1プロジェクトへ追加
      C:\Program Files (x86)\WiX Toolset v3.11\SDK\BootstrapperCore.config
    • BootstrapperCore.config 変更
      <host assemblyName="ClassLibrary1"></host>
  • BootstrapperApplicationRef の設定を変更
<BootstrapperApplicationRef Id='ManagedBootstrapperApplicationHost'>
    <Payload SourceFile="..\ClassLibrary1\bin\Release\ClassLibrary1.dll" />
    <Payload Name="BootstrapperCore.config" SourceFile="BootstrapperCore.config" />
</BootstrapperApplicationRef>

<WixVariable Id="WixMbaPrereqPackageId" Value="Netfx4Full" />
<WixVariable Id="WixMbaPrereqLicenseUrl" Value="NetfxLicense.rtf" />
  • WixMbaPrereqPackageId、WixMbaPrereqLicenseUrl が設定されていないとエラーになる。
  • MyBootstrapper.csの実装
      • namespace行の前にアノテーション追加
        [assembly:BootstrapperApplication(typeof(MySetup.MyBootstrapper))]
        namespace MySetup
        {
            public class MyBootstrapper : BootstrapperApplication
            { .... }
        }
      • Runメソッド追加
        public class MyBootstrapper : BootstrapperApplication
        
        protected overridevoid Run()
        {
             Engine.Quit(0);
        }
        • Run()の中でUI表示や、インストール条件設定など行う。
        • Engine.Quit(0)でexeの終了処理に入る。ここで実行すると実際には意味がないが、これがないとバックグランドプロセスが残るのでテスト中は適当なところでEngine.Quit(0)を呼ぶ。もし呼びそこなうと、タスクマネージャーでプロセスを終了させることになる。
      • イベントハンドラ追加
        少なくとも次のふたつのイベントハンドラを追加する。
        protected override void Run()
        {
            PlanComplete += OnPlanComplete;
            ApplyComplete += OnApplyComplete;
            if (Command.Action == LaunchAction.Install)
            {
                Engine.Plan(LaunchAction.Install);
            }
            else
            {
                //「プログラムと機能」一覧から起動した場合
                Engine.Plan(LaunchAction.Uninstall);
            }
            //Engine.Quit(0)はここでは呼ばない。
        }
        
        private void OnPlanComplete(object sender, PlanCompleteEventArgs e)
        {
            if (e.Status >= 0)
            {
                Engine.Apply(System.IntPtr.Zero);
            }
            else
            {
                Engine.Quit(0);
            }
        }
        private void OnApplyComplete(object sender, ApplyCompleteEventArgs e)
        {
            Engine.Quit(0);
        }
      ここまでで msi 単独のときと近い動作になるが、MyAppSetup.exe 起動時の Install / Uninstall の切り分けをコマンドラインパラメータからセットされる LaunchAction で行っているため、MyAppSetup.exe を繰り返し起動したとき常にInstallしようとし、結果二度目以降は何もせずに終了する。

      WiX toolsetのBootstarpperを試してみた(2)Detect追加

      $
      0
      0
      前回はPlanCompleteとApplyComplete のイベントハンドラ だけを追加したクラスからインストールを開始し、msiのUIによるインストール/アンインストールを行うところまで設定した。

      今回は DetectPackageComplete を追加する。
      • Runメソッド変更
        protected override void Run()
        {
            this.DetectPackageComplete += this.OnDetectPackageComplete;
            this.PlanComplete += this.OnPlanComplete;
            this.ApplyComplete += this.OnApplyComplete;
            Engine.Detect();
        }
      • DetectPackageComplete イベントハンドラ追加
        private void OnDetectPackageComplete(object sender, DetectPackageCompleteEventArgs e)
        {
            if (e.PackageId == "MySetup.msi")
            {
                if (e.State == PackageState.Absent)
                {
                    Engine.Plan(LaunchAction.Install);
                }
                else if (e.State == PackageState.Present)
                {
                    Engine.Plan(LaunchAction.Uninstall);
                }
            }
        }
        
      前回は Runメソッドで Engine.Plan を呼んでいたが、これを Engine.Detect に変更。その結果、MySetup.msi が見つからなければ Install、見つかれば Unistall を実行するようになる。

      これにより MySetup.exe を複数回起動すると、Install と Unistall が交互に繰り返されるようになる。

      このとき、次のようなログが書かれる。
      Detected package: MySetup.msi, state: Absent, cached: None
      または
      Detected package: MySetup.msi, state: Present, cached: Complete

      何度実行しても常に Absent の場合は msi 作成時の ID 設定をチェックする。これが"*"の場合は Present にならない。"*"を固定の GUID に変更する。

      Engine.Detect では他に次のイベント呼び出しがあるが、詳しく調べていない。
      • DetectRelatedMsiPackage
      • DetectRelatedBundle
      • DetectComplete
      次回で Bootstapper で msi 起動時のパラメータを設定し、言語切り替えを行えるようにする。
      参考リンク
      Bootstrapper Application Interface
      IBootstrapperApplicationの説明。ソースを見ていないが、ManagedBootstrapperApplicationHost はこれを実装し、C#の BootstrapperApplication のイベント呼び出しを行うものでしょう。

      Creating a custom UI installer with WIX Burn Bootstrapper
      WPFアプリケーションにブートストラッパ実装例。

      EngineとBootstrapperの関係、イベント一覧があります。

      発生順のイベントリストがあります。


      WiX toolsetのBootstarpperを試してみた(3)msiexecパラメータ設定

      $
      0
      0
      前回で Bundle から msi の UI を使ってのインストール/アンインストールができるようになった。今回は Bundle のブートストラッパ―で設定したパラメータを用いて msi を起動し、msi側で言語切り替えを行うようにする。
      • Bundle の子要素にVariablesタグ追加
        <Bundle ...>
        <Variable Name="TRANSFORMS" 
             Value=":ja-JP.mst" Type="string" bal:Overridable="yes"/>
        • Bundleエンジンに変数を追加する。
          Name:変数名。
          Value:初期値。この例では必ずBootstarpperで設定するので、Valueは別の値でもよい。
        • bal:Overridable="yes": Bootstarpperで変更できるように"yes"に設定する。
          • bal namespace が認識されない場合はWixタグに次のxmlnsを追加
            xmlns:bal="http://schemas.microsoft.com/wix/BalExtension"
      • MsiPackage 要素変更
      <Chain>
          <MsiPackage ....>
              <MsiProperty Name="TRANSFORMS" Value="[TRANSFORMS]" />
          </MsiPackage>
      </Chain>
      
      
      • Name:msi 作成プロジェクトの Product.wxsのPropertyタグのIdと同じ名前。
        msiexecの起動パラメータとして使われる。
        <Property Id="TRANSFORMS" Value="Default" />
      • Value="[TRANSFORMS]":BundleのVariableのTRANSFORMSの値で置換される。
        
        
    • MyBootstrapper.csのRunメソッド変更
      次のようなコードで Variable TRANSFORMS の値をセットする。
      if (CultureInfo.CurrentUICulture.TwoLetterISOLanguageName == "ja")
      {
          this.Engine.StringVariables["TRANSFORMS"] = ":ja-JP.mst";
      }
      else
      {
          this.Engine.StringVariables["TRANSFORMS"] = "";
      }
    • MySetup.exe実行これらの設定、コードの変更後、MySetup.exeを実行するとMyAppSetup.mstを起動するmsiexecのパラメータに
      TRANSFORMS=":ja-JP.mst"、またはTRANSFORMS=""が追加される。
    • ログ出力
      Bundleのログに、次のように書き出される
      • Installの場合:
        Applying execute package: MySetup.msi, action: Install, 
            path: C:\Users\UserName\AppData\Local\Package Cache\{GUID}v1.0.1\MyAppSetup.msi, 
            arguments: ' ARPSYSTEMCOMPONENT="1" MSIFASTINSTALL="7" 
            TRANSFORMS=":ja-JP.mst"
      • Uninstallの場合:
        Applying execute package: MySetup.msi, action: Uninstall, 
            path: (null), 
            arguments: ' ARPSYSTEMCOMPONENT="1" MSIFASTINSTALL="7" 
            TRANSFORMS=":ja-JP.mst"
      • Valueが空文字の場合は TRANSFORMS=""となる。
      これでmsiのUIを使ったインストールは実現できたが、アンインストールはサイレントで実行される。

      TRANSFORMSと同様にmsiexecのパラメータ UILevel、CLIENTUILEVEL を設定してみたが、Bundleが CLIENTUILEVEL=3 (サイレント)をその後ろに付けるため有効にならない。アンインストール実行確認ダイアログや、アンインストール完了時のメッセージ表示などのUIはBootstarpperで用意する必要がある。
      • アンインストール時のmsiexecのログ
        ARPSYSTEMCOMPONENT=1 MSIFASTINSTALL=7 TRANSFORMS=:ja-JP.mst 
        UILevel=5 CLIENTUILEVEL=5REBOOT=ReallySuppress IGNOREDEPENDENCIES=ALL 
            REMOVE=ALL CURRENTDIRECTORY=C:\WINDOWS\system32 
            CLIENTUILEVEL=3 MSICLIENTUSESEXTERNALUI=1 CLIENTPROCESSID=6296
        
        • UILevel=5 CLIENTUILEVEL=5 はMsiPropertyで設定したもの。burnが設定するCLIENTUILEVEL=3が有効になる。
      関連ブログ
      VisualStudio 2017 Community & WiX tools & WiX Edit によるWindowsアプリ インストーラ(msi)作成
      dotNetInstallerによるシンプルBootstarpper作成
      WiX toolsetのBootstarpperを試してみた(1)
      WiX toolsetのBootstarpperを試してみた(2)Detect追加
      WiX toolsetのBootstarpperを試してみた(4)UI追加

        WiX toolsetのBootstarpperを試してみた(4)UI追加

        $
        0
        0
        さて、前回まででmsi側のUIを使うBootstapperが一応できたが、アンインストール時になにも表示せずに実行、終了してしまうので、UIの追加を行ってみる。

        次のようなコードで、OnDetectPackageCompleteで実行/キャンセルの確認ダイアログを表示し、OnApplyCompleteで終了メッセージが表示される。

        参照に System.WIndows.Forms を追加。using System.Windows.Forms;
        
        private void OnDetectPackageComplete(object sender, DetectPackageCompleteEventArgs e)
        {
            if (e.PackageId == "MyAppSetup.msi")
            {
                if (e.State == PackageState.Absent)
                {
                    Engine.Plan(LaunchAction.Install);
                }
                else if (e.State == PackageState.Present)
                {
                    DialogResult res = MessageBox.Show("MyAppSetup",
                        "アンインストールを実行しますか?", MessageBoxButtons.OKCancel);
                    if (res == DialogResult.Cancel)
                    {
                        Engine.Quit(0);
                    }
                    else
                    {
                        Engine.Plan(LaunchAction.Uninstall);
                    }
                }
            }
        }
        
        private void OnApplyComplete(object sender, ApplyCompleteEventArgs e)
        {
            if (IsUninstall)
            {
                MessageBox.Show(ClassLibrary1.Properties.Resources.UnistallDone);
            }
        Engine.Quit(0);}
        せっかくmsiを多国語化したのに、Bootstarpperが未対応になってしまった。しかし、BootstarpperはC#のクラスライブラリなので、プロジェクトのProperties に String の Resouces.resx を追加することで多国語対応できる。

        msiと同様、ベースを英語とし日本語のResources.ja.resxを追加した場合は次のようにBundle.wxs の BootstrapperApplicationRef にresources.dllを追加する。
        <BootstrapperApplicationRef Id='ManagedBootstrapperApplicationHost'>
            <Payload SourceFile="..\ClassLibrary1\bin\Release\ClassLibrary1.dll" />
           <Payload SourceFile="..\ClassLibrary1\bin\Release\ja\ClassLibrary1.resources.dll"
                Name="ja\ClassLibrary1.resources.dll" /><Payload SourceFile="BootstrapperCore.config" />
        </BootstrapperApplicationRef>
        
        Name属性で ja サブフォルダを追加している。Name属性は展開先のパスで、デフォルト値はファイル名であるため、これがないと同じフォルダにファイルがフラットに展開され、resources.dllがロードされずベース言語のみとなる。

        ここまでで、Cultureに従ってパラメータを切り替えて多国語化した msi を起動するBootstrapper作るという目的は一応実現した。しかし、これだけのためならdotNetInstallerを使ったBootstrapperのほうが目的に適っている。BurnをするならUIをBootstrapperに移し、msiはサイレントで実行した方がよい。

        上記の方法ではインストール実行中、常時ウインドウを表示しておくことはできない。Runは独立したスレッドで呼ばれ、Runからリターンするとスレッドも終了し、ここで表示したFormも消えるか、下手をするとゾンビで残る。また Engine.Quit(0) を呼ぶとプロセスが終了しUIも終了してしまうので、UIが終了してから Engine.Quit(0) を呼ぶ必要がある。

        そのためにはUI用のイベントループを作ればよい。ここではFormアプリによる実装例を紹介しておく。

        Run メソッドで Detect を呼んだあと、Application によるループを開始する。
        このループ終了後、Engine.Quit(0) を呼ぶ。
        BootstapperとFormで相互に互いのインスタンスの参照を持ち、適宜メソッド呼び出し、変数設定などを行う。

        //クラスライブラリにForm1を追加
        private Form1 form;
        
        protected override void Run()
        {
            this.DetectPackageComplete += this.OnDetectPackageComplete;
            this.PlanComplete += this.OnPlanComplete;
            this.ApplyComplete += this.OnApplyComplete;
            this.ExecuteProgress += this.OnExecuteProgress;
        
            if (CultureInfo.CurrentUICulture.TwoLetterISOLanguageName == "ja")
            {
                Engine.StringVariables["TRANSFORMS"] = ":ja-JP.mst";
            }
            else
            {
                Engine.StringVariables["TRANSFORMS"] = "";
            }
            Engine.Detect();
        
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            form = new Form1();
            form.Bootstrapper = this;
            Application.Run(form);
        
            //formがクローズされると Application.Run が終了し、ここにくる。
            this.Engine.Quit(0);
        }
        

        また、ExecuteProgressイベントで進行状態が通知される。ここでFormに通知を転送することができ、また ExecuteProgressEventArgs にResult.Cancelをセットするとインストールを中断することができる。

        private void OnExecuteProgress(object sender, ExecuteProgressEventArgs e)
        {
            if ( someEroorCoccured )
            {
                e.Result = Result.Cancel;
            }
            else
            {
                form.SetProgress(e.OverallPercentage.ToString());
            }
        }

        関連ブログ
        VisualStudio 2017 Community & WiX tools & WiX Edit によるWindowsアプリ インストーラ(msi)作成
        dotNetInstallerによるシンプルBootstarpper作成
        WiX toolsetのBootstarpperを試してみた(1)
        WiX toolsetのBootstarpperを試してみた(2)Detect追加
        WiX toolsetのBootstarpperを試してみた(3)msiexecパラメータ設定

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

        $
        0
        0
        以前のブログで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 を別のものに置き換えメッセージを書き換えているが、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>
        関連ブログ

        Windowsファイル履歴のエラー

        $
        0
        0
        Windows ファイル履歴が実行されない場合の対処法

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

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

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

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


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

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

        そうはいってもこれに該当するファイルがあるか目視で調べるのは厄介なので、javascriptでスクリプトを作ってチェックしてみました。このスクリプトで”要チェック”と表示されるファイル/フォルダ名をチェックしてください。エラーにならない場合もどちらか一方しかファイル履歴に保存されていない可能性がありますので、チェックしてみてください。

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

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

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

        以下は次の環境で試した結果です。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は同等ですが、半角より全角の方が大きいと判断され、全角/半角はファイル名としては別のものとして扱われます。TEST.TXTとTEST.TXTは別ファイルになります。

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

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

        私の場合にも確かに上記のファイル名に該当するものがありましたが、それを直してもまだエラーが発生しました。そこで次の方法を試したところ回復しました。ライブラリに含まれるドキュメントなどの場所を変更したりしていたので、何かConfigがおかしくなったのかもしれません。
        • 次のフォルダを削除(リネームし成功したら削除する方が安全)
          C:\Users\(userName)\AppData\Local\Microsoft\Windows\FileHistory
        • コントロールパネル > ファイル履歴でドライブ選択し「今すぐ実行」
        ------------------- スクリプト -------------------

        //以下のスクリプトをテキストファイルにコピーし、
        //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の名前をチェックする。
        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;
            }
        }

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

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

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

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

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

          いろいろ試したところでは、Properties.Settings.Defaultはいったん作られると再度作り直されることがないようで、私の結論としてはuser.configを削除し、アプリを再起動するしか回復の方法がありません。

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

          ところが、user.configの場所は単純にはわかりません。インストールに仕方でフォルダの場所が異なり、バージョンごとにフォルダが異なり、またフォルダパスにハッシュ文字列が含まれます。開発環境ではビルドによっても異なるハッシュ値が作られます。

          これに関して次の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();
          }

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

          Windowsキーを無効にする方法

          $
          0
          0
          幼児用のお遊びアプリを作ってみたのですが、なにせメチャクチャにキーをたたくので、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 開発 / メッセージ管理 / メッセージフック

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

          $
          0
          0
          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を全て削除し、必要なタグだけ設定する方法です。

              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のエクスプローラで文字化けが発生します。試しにバイナリエディタで余分な”00”を削除してみると、文字化けが解消します。

          上記のように 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は定義されていないようです。

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

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

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

          移行作業

          ①「バックアップと復元」で「システム修復ディスク」を作成する。
          ②「バックアップと復元」で「システムイメージ」を作成する。私はシステムディスク(C:)だけのイメージを作成。
          ③移行先のディスクをdiskpartで完全消去する。
           もしかしたら、購入したままのまっさらなディスクならこの作業は不要かもしれない。
           これを行なわないと復元実行時に次のエラーが発生する。
            データ ディスクが現在 BIOS 上でアクティブに設定されています。その他の
            ディスクをアクティブに設定するか、または DiskPart ユーティリティを使用
            してデータ ディスクから不要なデータを消去した後で、復元操作を再試行し
            てください。

           コマンド実行要領
           >diskpart
             >select disk n
             >clean

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

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

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

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

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

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

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

          $
          0
          0

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

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

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

          TagLibはファイルから読み込んだバイト列をByteVectorオブジェクトに格納し、これをStringで読み出す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にスペースを一文字足してからバイト列化しています。
          StringをByteVectorにしたものを、次の要領でtagにセットします。

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

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

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

          -------

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

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

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



          Panasonic ブルーレイ レコーダー 修理記録

          $
          0
          0

           Panasonic DIGA DMR-BRT220 が故障した。

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

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

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

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

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

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

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

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


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

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


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

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


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

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

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

          Windows関連メモ

          $
          0
          0
          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再インストール



          Windows 11 がえらくもたつくようになってGoogle日本語入力に置き換えた顛末

          $
          0
          0

          しばらく前からWindows 11 の動作が妙にもたつくようになった。

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

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

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

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

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

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

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

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

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

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

          自作アプリがウイルス扱いになった ⇒ 解決

          $
          0
          0

          先日「自作アプリがウイルス扱いになったのが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 ao\pplicaion 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コマンドでもエラーが発生。





          WiX ToolSet v3.xでの.NET Frameworkのバージョンチェックの追加

          $
          0
          0
          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>

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

          $
          0
          0

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

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

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

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

          「重要なボリュームの1つ」ということでバックアップ対象を眺めてみると、システム予約のパティション管理領域もボリュームの1つに含まれている。256GのSSDのためか、この領域が50mbと小さく、残り容量がわずかだった。そこで、これをAOMEI 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で異常がないか確認するとよいでしょう。

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

          DELL Inspiron 14 3452 復活奮闘記(継続中)

          $
          0
          0

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

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

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

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

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

          あれこれ試してみてはいるものの、使い続けようという代物ではないな、というのが正直なところです。インタネットで見ると皆さん工夫したり、苦労したり、諦めたりしているようです。

          クセが強いジャンクの 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に次のキーボードの広告があった。これ自体は英語キーボードのものだが、キーボード裏表の写真から互換性がありそうと判断し、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」実行
          ⇒プロパティー左下の「システム ファイルのクリーンアップ」も実行

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

          Windowsオフラインアップデート
          メモリ容量不足で場合はMicrosoftの「Windows 10 のダウンロード」ページ⇒「ツールを今すぐダウンロード」をダウンロード。USBインストールメディアを作成する。exeの名前はバージョンにより異なる。最新のメジャーリリースなので、既存バージョンより新しいとは限らない。

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


          レベル2
          USBメモリまたはSDカード常時装着
          速度的にはUSB3.0(以上)がお勧めでしょう。USB2.0なら最速SDカードの方が速いかも。本体左側がUSB3.0、右側がUSB2.0です。
          NTFSでフォーマットしておく。
          以下、USBメモリとする。

          注意:リムーバルディスクの場合、ファイル/フォルダ削除はゴミ箱への移動ではなく完全削除となる。かつ、削除のたびに「このファイルを完全に削除しますか?」のダイアログが表示される。完全削除についてはOneDrive内のデータについては履歴から回復で対処可能。

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

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

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

          USBへ移動後はファイル オンデマンドをOFFにしておくのがお勧め。
          上の「OneDriveの設定確認」参照

          レベル3
          USBメモリを固定ドライブ化
          アプリケーションをUSBメモリにインストールをしようとすると「インストール ディレクトリはローカル ハード ドライブにあることが必要です。」と警告される。
          これへの対応を試してみたが、今のところ成功していない。

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

          アプリケーション インストール先をこのフォルダ下に設定する。インストールは完了し起動するが、アンインストール、修復で失敗する。悲惨な状態になるのでお勧めしない。

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

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

          いずれの方法の場合もアプリ本体(.exe)は”C:\Program Files (x86)”に作られ、付属するdllなどがUSBメモリにコピーされる。そのため、苦労する割に節約できるメモリは少ない。

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

          その他いくつかのフォルダをリンクを使用してUSBメモリに移動させることを試みたが、重要なシステムファイルが含まれるようなものはアクセス拒否され移動できないか、移動後不具合が生じる。

          レベル4
          HDD用空きスペースの利用
          これはまだ試していないが、HDD用のスペースにSATA-USB変換アダプタを付けた内蔵HDDまたはSSDを入れ、USBコネクタと結線する。How to install SSD for Inspiron 14 3452?で紹介されている。USB2.0の口となら実現できそう。

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

          結局のところ、USBメモリを追加し、ユーザライブラリ、OneDriveをそこへ移動するという程度のことしたできていない。

          PhotoLibrayにある画像、ビデオのファイルの名前、作成日時(アップロード日時)を取得する方法(iCloud共有含む)

          $
          0
          0
          PhotoLibrayにある画像、ビデオのファイルの名前、作成日時(アップロード日時)を取得する方法(iCloud共有含む)


          iCloud共有にアップロードされたファイルをダウンロードすると、jpegの場合はExifデータから撮影日がわかるが、ビデオは撮影日時を調べるのに写真アプリで情報をチェックする必要があり、面倒だった。そこでプログラムでファイル名、CreateionDateを取得しようとすてみると、これもまた意外と面倒でした。

          次のサンプルコードはPhotoLibray内のiCould、ローカル、画像、ビデオ、ライブフォトを含めてファイル名を取得するようにしたものです。

          iPhoneで撮影したvideoをそのデバイスからiCloud共有にアップロードした場合は作成日時=撮影日時と考えて良いと思いますが、それ以外で撮影し、Macからアップロードした場合は作成日時=アップロード日時になるでしょう。また、その場合に拡張子がMP4以外の場合、iCloud共有からイクスポートしたファイルはもとの拡張子(M4Vなど)ですが、以下のプログラムで取得するファイル名の拡張子は全てMP4になります。

          iPhoneとMacでiCloud共有の同じ写真やビデオのファイル名を比べてみると、同じものが多いものの、異なるものが結構あります。デバイス毎にファイル名が異なりますね。

          MyAssetListViewControllerを呼び出す遷移元で、次の要領でPHAssetをフェッチする。


          let sharedAlbums = PHAssetCollection.fetchAssetCollections(
                with: PHAssetCollectionType.album,
                subtype: PHAssetCollectionSubtype.albumCloudShared,

                options: fetchOptions)

          let assetsFetchResults = PHAsset.fetchAssets(in: sharedAlbums, options: nil)


          画面遷移時にMyAssetListViewControllerにassetsFetchResultsをセットする。



              overridefunc prepare(for segue: UIStoryboardSegue, sender: Any?) {
                  let assetGridViewController = segue.destinationas! MyAssetListViewController                      assetGridViewController.assetsFetchResults = assetsFetchResults
              }


          //---- MyAssetListViewController ----


          import UIKit
          import Photos

          /// UITableViewCellにプロパティーを追加。IBでコネクトする。
          class MyTableViewCell: UITableViewCell {
              @IBOutletvar thumbnail: UIImageView?
              @IBOutletvar fileName: UILabel?
              @IBOutletvar creationDate: UILabel?
              @IBOutletvar resolution: UILabel?
              @IBOutletvar duration: UILabel?
          }

          class MyAssetListViewController: UITableViewController {

              // MyRootListViewControllerから画面遷移する時にセットするインスタンス変数
              var assetsFetchResults: PHFetchResult<PHAsset>?
              var albumTitle: String?
              
              var assetToFileName = [PHAsset: String]()
              let reuseIdentifier = "Cell"
              let imageManager = PHCachingImageManager()
              let dateFormatter = DateFormatter()
              let timeFormatter = DateComponentsFormatter()

              overridefunc viewDidLoad() {
                  super.viewDidLoad()
                  self.navigationItem.title = albumTitle
                  // Formatterの設定
                  dateFormatter.timeZone = TimeZone.current
                  dateFormatter.dateFormat = "yyyy/MM/dd HH:mm:ss"
                  timeFormatter.allowedUnits = [NSCalendar.Unit.minute, NSCalendar.Unit.second]
                  timeFormatter.zeroFormattingBehavior = .pad
              }

              overridefunc numberOfSections(in tableView: UITableView) -> Int {
                  return1
              }

              overridefunc tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
                  guardlet assets = self.assetsFetchResults else { return0 }
                  return assets.count
              }
              
              overridefunc tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
                  let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! MyTableViewCell
                  guardlet assets = self.assetsFetchResults else { return cell }
                  
                  let asset = assets[indexPath.item]
                  cell.tag = asset.localIdentifier.hash
                  //iCloudへアクセスするようにOptionを設定
                  let options = PHImageRequestOptions()
                  options.isNetworkAccessAllowed = true
                  self.imageManager.requestImage(for: asset,
                                   targetSize: CGSize(width:80, height:80),
                                   contentMode: PHImageContentMode.aspectFit,
                                   options: options,
                                   resultHandler: {(result, info)->Voidin
                                   //非同期処理ためcellの再利用により異なるassetの内容を表示している場合がある。
                                   //localIdentifierが異なる場合は何もしない。
                                   guard cell.tag == asset.localIdentifier.hash,
                                         let result = result else { return }
                                   cell.thumbnail!.image = result
                  })
                  // assetから取得できるデータをセット
                  cell.fileName!.text = nil
                  cell.creationDate!.text = asset.creationDate == nil ? "(unknown)" : dateFormatter.string(from:asset.creationDate!)
                  cell.resolution!.text = "\(asset.pixelWidth) x \(asset.pixelHeight)"
                  cell.duration!.text = timeFormatter.string(from: asset.duration)
                  
                  // ファイル名セット
                  // 既にassetResourcesがキャッシュされている場合はそれからファイル名を取得する。
                  // iOS10時点では、LivePhotoの場合にassetResourcesJPGMOVの対がセットされている。
                  let assetResources = PHAssetResource.assetResources(for: asset)
                  if setFileName(from: assetResources, to: cell) == false {
                      // assetResourcesが空の場合はfalseが返される。
                      // LivePhoto以外のimagevideoの場合は空。LivePhotoでもキャッシュされていない場合は空。
                      // assetResourcesがない場合はrequestContentEditingInputを取得する。
                      setFileNameFromContentEditingInput(with: asset, to: cell)

                      // AVURLAssetを使う場合はこちらに変更。
                      //setFileNameFromAVAsset(with: asset, to: cell)
                  }
                  return cell
              }
              
              /// assetResource配列の各要素のoriginalFilenameを結合してlabelにセットする。
              /// assetResourcesが空の場合は"(searching)"をセットする。
              /// - parameter from: PHAssetResource.assetResources(asset)
              /// - parameter cell: MyTableViewCell
              func setFileName(from assetResources:[PHAssetResource], to cell: MyTableViewCell) -> Bool {
                  let label = cell.fileName!
                  if assetResources.count > 0 {
                      label.text = nil
                      for assetResource in assetResources {
                          let fileName = assetResource.originalFilename
                          if label.text == nil {
                              label.text = fileName
                          } else{

                              //assetResourcesが複数あるので同じfileNameが重複する可能性がある。
                              label.text = label.text! + ", " + fileName
                          }
                      }
                      returntrue
                  } else {
                      //assetResourcesが空の場合は"(searching)"をセット。
                      label.text = "(searching)"
                      returnfalse
                  }
              }
              
              /// assetからContentEditingInputを得て、livePhotoimagevideoの種類に応じてfileNameをセットする。
              /// - parameter with: PHAsset
              /// - parameter to: MyTableViewCell
              func setFileNameFromContentEditingInput(with asset: PHAsset, to cell: MyTableViewCell) {
                  let options = PHContentEditingInputRequestOptions()
                  options.isNetworkAccessAllowed = true
                  asset.requestContentEditingInput(with: options,
                     completionHandler: { (contentEditingInput, info) -> Voidin
                     guard cell.tag == asset.localIdentifier.hash else { return }
                     
                     iflet livePhoto = contentEditingInput?.livePhoto {
                         // live photoの場合はassetResourcesを取得してファイル名をセットする。
                         let_ = self.setFileName(from: PHAssetResource.assetResources(for: livePhoto), to: cell)
                     } elseiflet url = contentEditingInput?.fullSizeImageURL {
                         // imageの場合
                         cell.fileName!.text = url.path.components(separatedBy: "/").last
                     } elseiflet urlAsset = contentEditingInput?.audiovisualAsset as? AVURLAsset {
                         // videoの場合
                         cell.fileName!.text = urlAsset.url.path.components(separatedBy: "/").last
                         cell.duration!.text = self.timeFormatter.string(from: asset.duration)
                     } else {
                         cell.fileName!.text = "(not found)"
                     }
                  })
              }
          }

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

          Live Photoの場合にJPG、MOVの対を取得するにはContentEditingInputを使わないとダメそうですが、MOVだけでよければ次のようなコードでAVURLAssetからファイル名を取得することができます。
          上のコードのsetFileNameFromContentEditingInput呼び出しをこれの呼び出しに変更します。

          func setFileNameFromAVAsset(with asset: PHAsset, to cell: MyTableViewCell) {
              if asset.mediaType == PHAssetMediaType.video {
                  //iCloudへアクセスするようにOptionを設定
                  let options = PHVideoRequestOptions()
                  options.isNetworkAccessAllowed = true
                  //PHAssetに対応するAVPlayerItemを取得
                  let _ = self.imageManager.requestAVAsset(forVideo: asset,
                                 options: options,
                                 resultHandler: { (avAsset, avAudioMix, info) -> Void in
                                     guard let urlAsset = avAsset asAVURLAsset,
                                           let fileName =   urlAsset.url.path.components(separatedBy: "/").last
                                     else { return; }
                                 cell.fileName!.text = fileName
                              })
                  }
              }

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

          PHAssetからvalue(for: "filename")でファイル名を取得する方法もありました。
          ビデオの場合も拡張子JPGのファイル名が返されますが、用途によってはこれで十分な場合もあるでしょう。
          なお、PHAssetの公開仕様ではないので、今後のバージョンで同じ方法が使い続けれらるかはわかりません。


          cell.fileName!.text = asset.value(forKey: "filename") as? String ?? "<no filename>"


          DELL Inspiron 14 3452 復活奮闘記(継続中)

          $
          0
          0

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

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

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

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

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

          あれこれ試してみてはいるものの、使い続けようという代物ではないな、というのが正直なところです。インタネットで見ると皆さん工夫したり、苦労したり、諦めたりしているようです。

          クセが強いジャンクの 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に次のキーボードの広告があった。これ自体は英語キーボードのものだが、キーボード裏表の写真から互換性がありそうと判断し、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 

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

          Windowsオフラインアップデート
          メモリ容量不足で場合はMicrosoftの「Windows 10 のダウンロード」ページ⇒「ツールを今すぐダウンロード」をダウンロード。USBインストールメディアを作成する。exeの名前はバージョンにより異なる。最新のメジャーリリースなので、既存バージョンより新しいとは限らない。

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


          レベル2
          USBメモリまたはSDカード常時装着
          速度的にはUSB3.0(以上)がお勧めです。USB2.0なら最速SDカードの方が速いかも。本体左側がUSB3.0、右側がUSB2.0です。
          NTFSでフォーマットしておく。
          以下、USBメモリとする。

          注意:リムーバルディスクの場合、ファイル/フォルダ削除はゴミ箱への移動ではなく完全削除となる。かつ、削除のたびに「このファイルを完全に削除しますか?」のダイアログが表示される。完全削除についてはOneDrive内のデータについては履歴から回復で対処可能。

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

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

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

          USBへ移動後はファイル オンデマンドをOFFにしておくのがお勧め。
          上の「OneDriveの設定確認」参照

          レベル3
          USBメモリを固定ドライブ化
          アプリケーションをUSBメモリにインストールをしようとすると「インストール ディレクトリはローカル ハード ドライブにあることが必要です。」と警告される。そこで、いくつかの方法を試した。

          方法1
          USBメモリ上のファイルをVHD(Virtual Hard Disk)に割り当てる。
          HDDとして扱ってくれることで、アプリケーションのインストールやライブラリ類の移動、ファイル履歴の保存先などが行える。また、他の方法ではファイル/フォルダ削除がゴミ箱移動ではなく完全削除になるが、この方法の場合はゴミ箱移動となる。

          欠点として、(設定の問題があるかもしれないが)サインイン前にマウントが完了していないことがあり、手動でのマウントするか、再サインインが必要が必要になる。その影響で、サインイン時の環境が不完全な場合がある。これが許容できるなら、良い方法と思う。

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

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

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

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

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

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

          方法4
          USBメモリ上のフォルダを共用にし、ネットワークドライブに割り当てる。
          このドライブはファイル履歴の保存先に選択可能となる。
          USBメモリメモリからのフォイル/フォルダ削除はゴミ箱への移動とならないので、ファイル履歴を設定しておくのは次善の策となるでしょう。

          レベル4
          HDD用空きスペースの利用
          外付けHDD/SSDを使用するのがアプリインストール、バックアップなども含めて用途的には望ましい。

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

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

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

          結局のところ、USBメモリを追加し、ユーザライブラリ、OneDriveをそこへ移動するという程度のことしたできていない。

          おまけ
          テスト中に利用したコマンド類

          試みにUSBメモリをこの方法で初期化してみた。この目的では特に効果なし。

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