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しようとし、結果二度目以降は何もせずに終了する。

    0 件のコメント: