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ビルド時に適用されているということだと思う。

関連ブログ

2017年11月12日日曜日

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

さて、前回までで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)作成
WiX toolset msi ダイアログ多国語対応
dotNetInstallerによるシンプルBootstarpper作成
WiX toolsetのBootstarpperを試してみた(1)
WiX toolsetのBootstarpperを試してみた(2)Detect追加
WiX toolsetのBootstarpperを試してみた(3)msiexecパラメータ設定

2017年11月10日金曜日

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

前回で 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=5 REBOOT=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)作成
WiX toolset msi ダイアログ多国語対応
dotNetInstallerによるシンプルBootstarpper作成
WiX toolsetのBootstarpperを試してみた(1)
WiX toolsetのBootstarpperを試してみた(2)Detect追加
WiX toolsetのBootstarpperを試してみた(4)UI追加

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

前回は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の関係、イベント一覧があります。

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


2017年11月7日火曜日

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

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

インストール手順は次のブログを参照
VisualStudio 2017 Community & WiX tools & WiX Edit によるWindowsアプリ インストーラ(msi)作成

概要
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作りを題材とした手順。

はじめに

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

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

Burnによるバンドル作成
  • msiプロジェクトを含めるソリューションを開く。
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しようとし、結果二度目以降は何もせずに終了する。

    2017年11月6日月曜日

    dotNetInstallerによるシンプルBootstarpper作成

    前回で多言語対応のmsiを作成したので、コマンドラインオプションを設定してmsi起動するBootstarpper作りを試みた。

    本格的なものを作るならWiXのBurnを用いてBundleを作ることができるが、その場合はUIをBundle側のBootstrapperに移し、msiはBootstrapperの設定にしたがってUIなしで実行するのが本道のようだ。となると、せっかくmsiを多国語対応した意味がなくなるので
    、このmsiを利用するシンプルなBootstarpperを作りを試みた。

    ちなみにWiXでmsiにActionを追加することができるが、これはmsiが起動したあとで使われるので、起動時の言語切り替えには使えなかった。

    まずはmsiとは別のexeを作り、そこからWindowsのCulture設定に従いmsiを起動する方法。例えば次のようなコードでコンソールアプリを作ればよい。

    static void Main(string[] args)
    {
        FileInfo msiFileInfo = new FileInfo(Directory.GetParent(Assembly.GetExecutingAssembly().Location) + "\\MyAppSetup.msi");
        if (!CultureInfo.Exists) {
    
        if (CultureInfo.CurrentCulture.Name == "ja-JP")
        {
            System.Diagnostics.Process.Start(msiFileInfo.FullName, "TRANSFORMS=\":ja-JP.mst\"");
        }
        else
        {
            System.Diagnostics.Process.Start(msiFileInfo.FullName);
        }
    }
    

    この方法の問題点はmsiとexeの二つが存在し、msiを直接起動することもできてしまうことだ。言語切り替えだけであれば実質的な問題はないとも言えるが、できれば一本化したい。

    dotNetInstaller 

    探してみたところ dotNetInstaller というオープンソフトがあった。公式ページではV2.3(Windows8対応)までだが、GitHubからはV2.4(Window10 対応)をダウンロードできる。

    dotNetInstallerダウンロード
    git.hubサイト (dotNetInstaller 2.4
    dotNetInstaller公式サイト

    dotNetInstallerも本格的なインストーラ作成に対応しているが、ここでは単に言語切り替えを実現するだけ。dotNetInstallerの設定でmsiをsetup.exeに組み込みこむ。setup.exe実行で、これをTempフォルダに展開し、展開後msiexecでmsiを実行する。このとき、日本語の場合、そうでない場合の二つの構成を用意しておき、言語設定に応じて使い分ける。

    この方法については次のWebページで紹介されており、サンプルもダウンロード可能。

    dotNetInstaller には非常に多くの設定がありますが、今回関係したところいがいはよくわかりません。

    dotNetInstallerはconfigurationファイルを作成するInstaller Editorと、exeを作るコマンド InstallerLinker.exe からなる。

    Installer Editor / Configurationファイル作成

    準備
    手順を単純化するため、どこかに作業フォルダを作成し、MySetup.msi をコピーする。
    コマンドプロンプトを起動し、そのフォルダに移動しておく。InstallerLinker.exe 起動はここから行うことし、以下の定数#APPPATHはこの場所を指す。

    dotNetInstaller起動
    dotNetInstallerをインストール後、スタートメニューからInstaller Editor起動。

    File > New
    • Logging > log_enabledをTrueに変更(推奨)
      log_fileの場所に出力される。
      • #TEMPPATHは標準では次の場所
        C:\Users\UserName\AppData\Local\Temp
    • UI > ui_levelをsilentに変更(デバッグ中はbasicでもよい)
    • Locale > lcidtype を User(またはUserExe以外)に変更
    Edit > Add > Configurations > Setup Configuration
    • Language > lcid_filterを”1041”(日本語)に設定
    • Runtime > complete_commandに次のコマンドを入力
      msiexec /i #CABPATH\MySetup.msi TRANSFORMS=":ja-JP.mst"
      • #CABPATHはdotNetInstallerが作成するTempフォルダ。標準では次の場所。
        C:\Users\UserName\AppData\Local\Temp\{GUID}
      • "ja-JP"の部分は定義済み定数”#OSLOCALE”で置換可能だが、msiに組み込まれているmstと一致しないとエラーになる。
      • UIを表示させる場合は、少なくともメッセージのAPPLICATION_NAMEを変更する。
    • Main Dialog > dialog_show_installed をFalseに変更
    • Runtime > show_progress_dialogをFalseに変更(Trueでもよい)
    "install:"を右クリック > Add > Embed > Embed File
    • sourcefilepathに次のパスを入力
      #APPPATH\MySetup.msi
    • targetfilepathにMySetup.msiを入力。
      #CABPATHからの相対パスで、別の名前でもよく、またサブフォルダを加えることもできるが、
      msiexec のパラメータと一致させること。
    もうひとつSetup Configurationを追加
    • Setup Configuration以下の手順を繰り返し、もうひとつSetup Configurationを追加する。
      • Language > lcid_filterを"!1041"(日本語以外)に設定
      • Runtime > complete_commandに次のコマンドを入力(TRANSFORMSなし)
        msiexec /i #CABPATH\WavCutterSetup.msi 
    configuration.xml保存
    • File > Save または Save As で準備で作成したフォルダ内に保存
    InstallerLinker.exe実行 / setup.exe作成
    • 標準では次のフォルダにインストールされている。
      C:\Program Files (x86)\dotNetInstaller\bin
    • 次の要領でInstallerLinker.exeを実行する。
      >set linkerFolder="C:\Program Files (x86)\dotNetInstaller\bin"
      >%linkerFolder%\InstallerLinker.exe /o:setup.exe /t:%linkerFolder%\dotNetInstaller.exe /c:configuration.xml /v+
    • setup.exeが出力される。

    2017年10月20日金曜日

    クラスメソッド +initilalize について

    +initializeが複数回呼ばれることがあるため、[super initialize]を呼ぶべきかどうかも含めて調べてみた。
    結論としては、[super initialize]は呼ぶ必要はないが、空でも+initializeを実装しておいた方が良い場合がある。

    クラスが初めて参照された時に+initializeが呼ばれるが、あるサブクラスのインスタンスを作成すると、そのクラス階層の上位からinitializeが呼ばれる。

    例えば、次のようなクラス階層を作る。


     @interface MyClass1 : NSObject
     @interface MyClass2 : MyClass1
     @interface MyClass3 : MyClass2
    各々に次のような+initializeメソッドを実装する。
     + (void)initialize
     {
         NSLog(@"MyClass1.initialize");
     }

    どこかでMyClass3のインスタンスを作る。
     MyClass3 *myClass3 = [[MyClass3 alloc] init];

    この場合、次のようにログが書き出され、クラス階層の上位から+initializeメソッドが呼ばれていることがわかる。
     MyClass1.initialize
     MyClass2.initialize
     MyClass3.initialize

    次のように順次上位のクラスのインスタンスを作ってみる。
     MyClass1 *myClass1 = [[MyClass1 alloc] init];
     NSLog(@"%@", myClass1);
     MyClass2 *myClass2 = [[MyClass2 alloc] init];
     NSLog(@"%@", myClass2);
     MyClass3 *myClass3 = [[MyClass3 alloc] init];

     NSLog(@"%@", myClass3);

    この場合は次のようにログが書き出される。
     MyClass1.initialize
     <MyClass1: 0x6000000089c0>
     MyClass2.initialize
     <MyClass2: 0x600000008940>
     MyClass3.initialize

     <MyClass3: 0x6000000089d0>

    すでに+initializeが呼ばれているクラスについては、繰り返し+initializeが呼ばれない。

    ちなみに次の順序でインスンタンスを作ると、
     MyClass3 *myClass3 = [[MyClass3 alloc] init];
     NSLog(@"%@", myClass3);
     MyClass1 *myClass1 = [[MyClass1 alloc] init];
     NSLog(@"%@", myClass1);
     MyClass2 *myClass2 = [[MyClass2 alloc] init];

     NSLog(@"%@", myClass2);

    ログは次のようになる。
     MyClass1.initialize
     MyClass2.initialize
     MyClass3.initialize
     <MyClass3: 0x60000000aa80>
     <MyClass1: 0x60000000aad0>

     <MyClass2: 0x60000000aae0>


    myClass3を作った時にMyClass1、MyClass2、MyClass3の+initializeが順次呼ばれており、それ以降のmyClass1、myClass2作成時には呼ばれない。



    さて、+initializeメソッドで[super initialize]を呼ぶように変えてみる。
     + (void)initialize
     {
         [super initialize];
         NSLog(@"MyClass1.initialize");
     }

    すると、
     MyClass3 *myClass3 = [[MyClass3 allocinit];

    の場合に、次のようにinitializeが呼ばれるようになる。
     MyClass1.initialize
     MyClass1.initialize
     MyClass2.initialize
     MyClass1.initialize
     MyClass2.initialize
     MyClass3.initialize

    MyClass1、MyClass2、MyClass3の順に+initializeが呼ばれるが、MyClass2の時にsuperのMyClass1が再度呼ばれ、MyClass3の時に同様にMyClass2、MyClass1が再度呼ばれる。

    ということで、[super initialize]を呼ぶと無駄な呼び出しが繰り返されることになり、実装によっては不具合を生じる可能性もある。

    さてここで、[super initialize]は削除し、さらにMyClass3の+initializeを削除してみる。

    すると、
     MyClass3 *myClass3 = [[MyClass3 allocinit];

    の場合のログは次のようになる。
     MyClass1.initialize
     MyClass2.initialize
     MyClass2.initialize

    superクラスのMyClass1、MyClass2の+initializeが順次呼ばれたあと、myClass3の+initializeが呼ばれようとするが、これが実装されていない場合はそのsuperクラスのinitializeが呼ばれる。

    サブクラスが+initializeを実装していない場合、そのsuperクラスのinitializeが複数回呼ばれることになる。

    MyClass2には+initializeが実装されているが[super initialize]はないため、MyClass1はここでは再度呼ばれない。

    さらにMyClass2の+initializeを削除すると、ログは次のようになる。
     MyClass1.initialize
     MyClass1.initialize
     MyClass1.initialize

    myClass2、myClass3は+initializeがないのでログは書かないが、+initializeがないのでsuperを辿り、MyClass1の+initializeが実行され、MyClass1のログが3回書かれる。

    このようにsuperの+initializeを実行するのは、ダイナミックにクラスを生成する場合の初期化に必要なためらしい。

    ということで、[super initialize]を書かなくても上位のクラスから+initializeが呼ばれるので、書く必要はない。むしろ書かない方が良い。

    一方、+initializeを実装しない場合は上位クラスの+initializeが複数回呼ばれる。サブクラスが必ずしも+initializeを実装するとは限らないので、+initializeでは複数回呼ばれても問題ないような実装にする必要がある。また、特別な理由がなければ、空の+initialize 
    を実装しておいた方が良いように思える。

    もし+initializeが一度だけしか実行されないようにする必要がある場合は、次のような方法がある。
     + (void)initialize
     {
        static dispatch_once_t once;
        dispatch_once(&once, ^{
            ......
        });
     }

    2017年10月15日日曜日

    Could not load the "imageName.png" image referenced from a nib in the bundle with identifier ...

    Could not load the "imageName.png" image referenced from a nib in the bundle with identifier ...

    pngをプロヘクトに追加するとInterfaceBuilderではUIにimageを設定できるようになるが、それだけではビルド時にbundleにコピーされないため、実行時にリソースが見つけられず発生する。

    プロジェクトビュー > Target > BuildPhases > Copy Bundle Resources
    エラーが発生している画像ファイルを追加する。

    2017年9月12日火曜日

    VisualStudio 2017 Community & WiX tools & WiX Edit によるWindowsアプリ インストーラ(msi)作成

    Windowsインストーラ(msi)を無料で作成できる方法を探したところ、MS発祥のオープンソースプロジェクトのWiX toolsetがあり、3.11ではVisualStudio 2017 Communityでインストーラを作成することができるようになっていた。また、VisualStudioだと多言語でのインストーラを一気に作成することもできる。

    VisualStudioでいきなりXMLに取り組むのはしんどいので、もうひとつ別のオープンソースのWiX Editを使い、XMLを作成した。

    以下、その手順です。

    WiX toolset ダウンロード/インストール

    WiX toolset 3.11、WiX Toolset Visual Studio 2017 Extensionをダウンロード、インストール。
    ダウンロードページ:http://wixtoolset.org/releases/
    同ページからWix Toolset Visual Studio 2017 Extensionへ移動しExtensionをダウンロード。

    Visual Studio 2019用はこちら
    Wix Toolset Visual Studio 2019 Extension

    Visual Studio 2022用はこちら
    WiX v3 - Visual Studio 2022 Extension

    ちなみにアンインストールはVisualStudioで行う。
    拡張機能⇒拡張機能の管理⇒Wixで検索⇒Wix v3をアンインストールする

    WiX Edit ダウンロード、インストール

    (VS2022ではt拡張機能でアルチテック株式会社のWixデザイナーがインストールできるが試していない。)

    WiX Editで簡単サンプル作成

    Toru Takahashi さんの「Windows上でWixおよびWixEditを使ったインストーラ作成
    」に従ってサンプルを作る。

    サンプル拡張

    次の二点に対応するため、サンプルを作り直す。
    ・exeと多言語対応のresources.dllを含める。
    ・デスクトップショートカットとスタートメニューを追加する。

    以下、Toru Takahashi さんのチューアルと共通する手順は割愛。
    斜体部分は実際の対象の名前で置き換える。

    このチュートリアルでは後半の方で紹介される「WiXで指定ディレクトリ以下をまとめてインポート」する方法を新規作成時に行う。

    File > New > Next で表示されるダイアログで、PFilesを右クリック。

    ImportFolderを選択。

    exeとresoucesが含まれるReleaseフォルダを選択。
    フォルダ内の全ファイルがインポートされるので、不要なファイルを削除する。

    削除後はこんな感じ。

    このチュートリアルにはショートカット追加が含まれていないので、ウィザードでの新規作成の際にフィーチャーを追加。次のものにチェックを入れる。
    Add User Interface
    Desktop application shortcut
    Startmenu application shortcut
    Startmenu uninstall shortcut

    ”Add a userinterface”に"WixUI_InstallDir"が含まれていないので、仮設定しておき、あとで修正する。

    デスクトップのショートカットを設定する。
    下段はショートカットアイコンの下に表示される名前。

    スタートメニューのショートカットを設定する。
    中段はスタートメニューのアプリ起動ショートカットに表示される名前(XMLに反映されないようだが)。
    exeを起動するだけのショートカットなら下段は空欄のままにしておく。

    スタートメニューのアンインストールショートカットを設定する。

    適宜情報を変更してウィザード終了。
    VS2017でculture指定で日本語版msiを作る場合は、Languageの変更、Codepageの追加はしなくてもよい。

    左ペインFiles > 中ペインPFiles > Release を選択。
    ReleaseはImportしたフォルダ名。
    右ペインのId=Nameの値をProgramFilesFolderの下に作るフォルダ名(通常はアプリケーション名と同じ)に変更する。
    私はIdもAppNameAppFolderに変更したが、その場合はFeaturesでこのIdを参照している項目も同時に変更する必要がある。

    左ペインGlobal > 中ペインUIRefを選択。
    右ペインのIdの値を"WixUI_InstallDir"に変更する。

    左ペインProperties > 中ペイン右クリック > Add Newで表示されるダイアログで"WIXUI_INSTALLDIR"を追加し、その値にアプリケーションフォルダのIdを入力する。この例では”Release"、あるいは変更した場合は"AppNameAppFolder"。
    これを行わない場合、ビルドはされるがインストーラ実行時にエラーが発生する。(下記error code 2819参照)

    VisualStudioのソリューションにSetupプロジェクト追加

    詳しくは(といってもとてもシンプルな)http://wixtoolset.org/のVisualStudioへのプロジェクト追加手順のページを参照してください。

    WiX toolsetがインストールされると、新規プロジェクト追加の際のプロジェクトタイプに”WiX Toolset”が追加される。3.11をインストールしたので"Setup Project for WiX v3"を選択。

    wixtoolset.orgの手順では独立したソリューションを作り、既存アプリを参照に追加しているが、私はもとのアプリのソリューションにSetupプロジェクトを追加してみた。
    プロジェクト追加ダイアログ左下の選択肢で"ソリューションに追加"を選択。
    名前は"Setup”とした。

    Setup > References に次のdllを追加する。
    C:\Program Files (x86)\WiX Toolset v3.11\bin\WixUIExtension.dll

    この参照がないとビルド時に次のエラーが発生する。
    Unresolved reference to symbol 'WixUI:WixUI_InstallDir' ...

    Setup > Product.wxs にWiX Editで作成したwxsをコピー/ペースト。

    wxsのReleaseフォルダへのパスをソリューションに合わせて変更。
    ..\AppName\bin\Release\

    ビルド構成マネージャーで構成変更・追加。(一例)
    Debug、ReleaseのビルドからプロジェクトSetupを除く(チェックをはずす)。
    新規構成”Setup"を追加。Release、Setupをビルドに追加。

    ビルド構成Setupでビルドすると構成に従った出力フォルダ(この例では...\Setup\bin\Release\)内にSetup.msiが出力される。

    多言語対応

    プロジェクトSetupのプロパティー画面 > Build > General > Cultures to buildに対象カルチャーを追加。en-US;ja-JPのように複数のときはセミコロンで句切る。

    ビルドすると構成に従った出力フォルダ(この例では...\Setup\bin\Release\)内に対象カルチャー毎のサブフォルダが作られ、その中にSetup.msiが出力される。

    異常で主要なメッセージの表示が日本語化されるが、細かなダイアログのメッセージが英語のまま。これについてば続きのブログ参照。

    UIの画像入れ替え

    次のようなタグ名=WixVariable、id=WixUIDialogBmpの要素を追加すると、最初のダイアログの背景を入れ替えることができる。
    画像はbmp形式、解像度498x312
    <WixVariable Id="WixUIDialogBmp" Value="WavCutterBK.bmp" />

    次のようなタグ名=WixVariable、id=WixUIDialogBmpの要素を追加すると、ふたつめ以降のダイアログのバナーを入れ替えることができる。
    画像はbmp形式、解像度498x58
    <WixVariable Id="WixUIDialogBmp" Value="WavCutterBanner.bmp" />


    Upgradeインストール

    以上の手順で作成したインストーラの場合、同じバージョン番号でもリビルドしたインストーラで再度インストールを行うと「別のバージョンの製品が既にインストールされています」の警告が出て、先にアンインストールを行わないとインストールできない。

    Product.wxsのProductタグのIDを"*"に変更し、MajorUpgradeタグを追加する。

    <Product Id="*" ...>
          <MajorUpgrade AllowSameVersionUpgrades="yes" 
            DowngradeErrorMessage="A newer version of [ProductName] is already installed." />

    ProductのIdが同じ値の場合、AllowSameVersionUpgrades="yes" でも上の警告が出る。バージョンごとに独立したGUIDを設定すればよいが、ProductのId="*"とするとビルドの度に新規GUIDで置き換わるので、なんどもビルドする場合は楽。
    ただし、BurnによるBootstarpperを作成する場合、"*"だとmsiがインストール済みか検知できなくなる。その場合はmsiの確定版は固定のGUIDで作成する。

    DowngradeErrorMessage は AllowDowngrades="yes" でない場合は必須。

    メッセージを日本語にする場合は次項の「メッセージ多言語対応」にするか、Product タグにCodepage="932"を追加し、プロジェクトの"Cultures to build"にja-JPのみを設定する。

    メッセージ多言語対応

    プロジェクトSetupを右クリック > 追加 > 新しいい項目 > Localization File でファイル追加。

    日本語、英語のふたつを用意する場合は次のようになる。

    Strings_ja-JP.wxl、Strings_en-US.wxlのようにカルチャーを付けたファイルを各々に追加する。

    自動で追加されるファイルのWixLocalizationタグのxmlnsは"http://wixtoolset.org/schemas/v4/wxl"となっているが、WiX V3なので"http://schemas.microsoft.com/wix/2006/localization"に変更する。

    次の要領で各々のファイルにメッセージを追加する。

    Strings_en-US.wxl
    <WixLocalization Culture="en-US" ...>
      <String Id="DowngradeErrorMessage">A later  virsion of [ProductName] is already installed. Setup will now exit.</String>
    </WixLocalization >

    Strings_ja-JP.wxl
    <WixLocalization Culture="ja-JP" ....>
      <String Id="DowngradeErrorMessage">[ProductName]の新しいバージョンが既にインストールされています。</String>
    </WixLocalization >

    Product.wxsのメッセージを上記のStringの参照に置き換える。

    <MajorUpgrade AllowSameVersionUpgrades="yes"
                            DowngradeErrorMessage="!(loc.DowngradeErrorMessage)" />

    プロジェクトSetupの” Cultures to build”に対象カルチャーをen-US;ja-JPとする。(「多言語対応」参照)

    TRANSFORMによる多言語メッセージ対応

    「メッセージ多言語対応」ではCulture別に独立したmsiが作られる。
    バージョン間の差分を取り出し、それをインストーラに適用する機能を利用してUIを切り替えることができる。

    torch.exeで、ベースmsiと言語対応するmsiの差分を作り、EmbedTransform.exeでmsiに組み込む。

    EmbedTransform.exeはWiX toolset ドキュメント: Morphing Installers のページのリンク(EmbedTransform tool)からダウンロードする。

    Setupプロジェクトのプロパティー > Build Events > Post-build Event Command Line
    に次のようなバッチ処理を設定する。

    set BIN=C:\Program Files (x86)\WiX Toolset v3.11\bin
    copy en-US\WavCutterSetUp.msi .
    "%BIN%\torch.exe" -p -t language WavCutterSetUp.msi ja-JP\WavCutterSetUp.msi -out ja-JP.mst
    "%BIN%\EmbedTransform.exe" WavCutterSetUp.msi ja-JP.mst

    EmbedTransform.exeはWiX toolsetインストールフォルダのbinに保存した。
    この例ではCulture別のフォルダのうちen-USのmsiをひとつ上のフォルダにコピーし、それをベースのmsiとして、ja-JPとの差分をja-JP.mstに出力し、それをベースのmsiに組み込んでいる。

    torch.exeは本来は異なるバージョン間の差異を作るのが目的のようで、多言語対応のための差分作成ではバージョンが同じであるため、AllowSameVersionUpgrades="yes"が設定されていると次の警告が発生する。

    warning LGHT1076: ICE61: This product should remove only older versions of itself. The Maximum version is not less than the current product. (1.0.1 1.0.1)

    この警告が出ても処理は続行され、msiが作られる。警告を出さないようにしたい場合は
    Setupプロジェクトのプロパティー > Tool Settings > Suppress specific ICE ValidationにICE61をセットする。

    msiを直接実行した場合は英語版、次のコマンドで実行した場合は日本語版となる。msiexec /i folderPath\WavCutterSetUp.msi TRANSFORMS=":ja-JP.mst"

    言語切り替えを行うBootstarpperについては続きのブログ参照


    エラーと対策

    warning CNDL1113 : Because it is an advertised shortcut, the target of shortcut 'desktopShortcut' will be the keypath of component 'APPNAME.EXE' rather than parent file 'APPNAME.EXE'. To eliminate this warning, you can (1) make the Shortcut element a child of the File element that is the keypath of component 'APPNAME.EXE', (2) make file 'APPNAME.EXE' the keypath of component 'APPNAME.EXE', or (3) remove the @Advertise attribute so the shortcut is a non-advertised shortcut.

    この警告が出ても正常に動作するmsiが作られる。
    WiX Editは次のようなタグ構造を作る。
    <Component Id="APPNAME.EXE” ...>
        <File Id="APPNAME.EXE"  ...>
            <Shortcut Id="desktopShortcut" ... />
            <Shortcut Id="ExeShortcut" ... />
        </File>
    </Component>
    これを、Shortcut要素がComponent要素の直接の子要素になるように変更する。
    <Component Id="APPNAME.EXE” ...>
        <File Id="APPNAME.EXE"  ...></File>
        <Shortcut Id="desktopShortcut" ... />
        <Shortcut Id="ExeShortcut" ... />
    </Component>
    WiX EditのUIでは変更できないので、Tools > Launch External Editorでwxsを開き、編集する。
    WiX EditがComponent とFileに同じIdを振るため、メッセージが分かりにくなっている。
    WiX toolsetのマニュアルの例ではこの場合のComponent のIdをMainExecutableとしている。このIdを変更する場合はFeaturesのComponentRefでの参照も変更すること。(次項のエラー参照)

    error LGHT0094 : Unresolved reference to symbol 'Component:APPNAME.EXE' in section 'Product:GUID'.

    上記警告でComponent要素のIdを変更したが、それを参照しているRefのIdを一致させていない場合に発生する。当該要素のIdを変更する。

    error CNDL0006 : The Directory/@Name attribute's value cannot be an empty string.  If you want the value to be null or empty, simply remove the entire attribute.

    値が必須の要素で、値がブランクになっている。
    上記手順で作成した場合、Files > ProgramMenuFolder > Dictionary のNameの値が(ウィザードで入力したはずだが?)ブランクになっている。スタートメニューのアプリ起動ボタンに表示される名前を入力する。

    error LGHT0267 : Found orphaned Component 'SomeCompnent'.  If this is a Product, every Component must have at least one parent Feature.  To include a Component in a Module, you must include it directly as a Component element of the Module element or indirectly via ComponentRef, ComponentGroup, or ComponentGroupRef elements.

    Component 要素が出現したが、ComponentRef, ComponentGroup, ComponentGroupRef のいずれとも関連付けられていない。
    私が作った範囲ではFiles >Features の DefaultFeatures から漏れている場合に発生。DefaultFeatures に追加する。

    error LGHT0130 : The primary key 'UninstallProduct' is duplicated in table 'Shortcut'.  Please remove one of the entries or rename a part of the primary key to avoid the collision.

    どういう手順で発生したか不明だが、Component Id="UninstallProduct"が二つできたときに発生。不要な方を削除する。

    error code 2819
    This installer has encountered an unexpected error installing this package. This may indicate a problem with this package. The error code 2819.
    インストーラ実行時に発生するエラー。
    "UIRef"を"WixUI_InstallDir"にした場合で、"Properties"に"WIXUI_INSTALLDIR"を
    追加しなった場合に発生する。
    "Properties"に"WIXUI_INSTALLDIR"を追加し、その値にアプリケーションフォルダのIdを入力する。

    ... characters that are not available ...

    A string was provided with characters that are not available in the specified database code page '1252'. Either change these characters to ones that exist in the database's code page, or update the database's code page by modifying one of the following attributes: Product/@Codepage, Module/@Codepage, Patch/@Codepage, PatchCreation/@Codepage, or WixLocalization/@Codepage.

    Upgradeインストールの設定でDowngradeErrorMessageを日本語で設定し、Codepage="932"とした場合で、プロジェクトの"CUltures to build”が設定されていない、または"ja-JP"以外の言語が含まれている場合に発生する。"CUltures to build”に"ja-JP"のみ設定する。
    または「メッセージ多言語対応」の要領でCulture別のwxlファイルを用意する。

    Unresolved reference to symbol 'Property:NETFRAMEWORKxx' in section 'Product:*'.

    .NET Frameworkのバージョンチェックのために次のようなタグを追加した場合で、プロジェクトのReferencesにWixNetFxExtensionを追加していない場合に発生する。

    <PropertyRef Id="NETFRAMEWORK45"/>
    <Condition Message='This setup requires the .NET Framework 4.5installed.'>
      <![CDATA[Installed OR NETFRAMEWORK45]]>
    </Condition>

    Setup > References に次のdllを追加する。
    C:\Program Files (x86)\WiX Toolset v3.11\bin\WixNetExtension.dll

    関連ブログ
    WiX toolset msi ダイアログ多国語対応
    dotNetInstallerによるシンプルBootstarpper作成
    WiX toolsetのBootstarpperを試してみた(1)
    WiX toolsetのBootstarpperを試してみた(2)Detect追加
    WiX toolsetのBootstarpperを試してみた(3)msiexecパラメータ設定