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が出力される。