インストール・プロジェクト

19 posts

セットアッププロジェクト : 結論

2月から色々やってみましたが、結論を出しました。

私の場合、セットアッププロジェクトに求められる条件は二つです。

一つは、デモ版と製品版を作成して、製品版ではプロダクトキーの入力を促す

二つ目は、ターゲットコンピュータにMyAppの動作に必要なソフトがインストールされているかどうか調べて、もしなければ自動でインストールする

ということです。

しかし考えてみれば、製品登録はインストールの途中で実行しなくてもよい。インストール後のMyAppの中で登録するようにすれば、セットアッププロジェクトにカスタムアクションを組み込むことはない。

アプリケーションの中で製品登録し、その情報をどこかに保存するのは、難しいことではない。それに、そもそもデモ版と製品版を作る必要はない。

登録情報は、レジストリに書き込む、ファイルに書き込む、インターネット経由でサーバーに書き込む等いくらでも方法があります。登録情報を暗号化することも簡単です。

また、ターゲットコンピュータの必須プログラムの存在の確認は、前回書きましたように、VBでもC#でも簡単なプログラムを書けば十分です。

となるとMyAppのセットアッププロジェクトは単純でいいので、手っ取り早くつくればいい。

二つ方法があります。

一つはVS2010のセットアッププロジェクトを使う。もう一つは、[InstallShield Limited Edition]を使うです。

VS2010のセットアッププロジェクトは、至って簡単にmsiファイルを作成できますが、欠点は、VS2010以降のVSでサポートしていないことです。

2015/7/27 修正
VS2015では再び、Microsoftの[Installer Projects]が復活しました。

まだPreviewer版ですので、製品版のことは分かりませんが。VS2010のものより改良されているようです。

[InstallShield Limited Edition]はマイクロソフトのセットアッププロジェクトに代わるもので、最新のVSでも使えるのですが、私のテストではうまくいきませんでした。多分依存ソフトの設定に問題があると思いますが、これ以上の研究はやめにします。

と言う訳で、プロダクトキーの設定はアプリケーションMyApp本体の中で、MyAppのセットアップ・プログラムmsiの作成はVS2010のセットアッププロジェクトで、ターゲットコンピュータの必須ランタイムの調査およびそのインストール、最後にはmsiを起動するプログラムは独自に作成する。
という方式を結論にします。

結局WiXは使わないことになりました。

Bootstrap その後

「Bootstrapperはもう終わり」と思っていたのですが、WiX Bootstrapperは気持ちの悪い終わり方をしたし、どうしてもこのまま終了にするのが嫌なので、結局やるところまでやることにしました。

まず、マイクロソフトがインストールの方法として、最近はClickOnceを推奨しているので、ClickOnceがどんなものか使ってみることにしました。

簡単なサンプルを作り、ClickOnceの長短をまとめた記事を読んでみました。短所は、細かい指示ができそうにないということで、今後は、改良されてインストール手段のメインになるかもしれませんが、「今は、よしわかった」でClickOnceは終わりにします。

WiX以外のBootoStrapperがあるという情報を得たので、探し出し使ってみました。AutoItです。これを使っていて、はたとわかったことは、「要するに、ターゲットコンピュータに、私の場合Accessがインストールされているかどうか調べて、なければ、所定のURLから用意したAccessRuntimeをダウンロードし、インストールすればいいのだから、このプログラムをVBでもC#でもいいから、自作すればいいだけだ」ということです。

ごく当たり前のことなのに、難しく考えていました。

C#はあまり経験ないので、この際C#で書いてみました。

ターゲットコンピュータのOSのバージョン、32/64ビット版の確認、さらに、Accessがインストールされているかレジストリーをしらべ、なければRunTimeをダウンロード、インストール、それが済んだら自分のセットアップソフト(MyApp.msi)をダウンロードしインストールします。

正味200行もないと思います。

難しいことはありませんが、注意すべき点がいくつかあります。

1. RunTimeの存在を調べるとき、32ビットOSと64ビットOSで調べる場所が異なる点です。

調べるAccessは2つあります。
Access2010でもいいし、Access2013でもいいです。

Access2010のProgIDは[Access.Application.14]、Access2013のProgIDはAccess.Application.15です。

これらがインストールされているかどうかは、レジストリーのLOCAL_MACHINESOFTWAREClassesを探します。

これだけでいいと思いますが、更に念のためここで得られたCLSIDからインストールの状況を調べました。

このとき、32ビットOSではSOFTWAREClassesCLSID、64ビットOSではSOFTWAREWOW6432NodeClassesCLSIDを調べる必要があります。

2. ファイルのダウンロードが完了後に、そのインストールを始めなければいけません。ダウンロードが完了していないのに、インストールを始めては当然いけません。

ダウンロードにはWebClientクラスを使います。
WebClient.DownloadFileメソッドは同期をとっていると思うのですが、作業の終了を確認しないとエラーがでます。

WebClient.IsBusyの間は、次の処理に移行しないようにしました。
これでOKでした。

また、MyAppのインストールの前に、AccessRuntimeのインストール作業を終了しておかなければいけません。

これはProcessを使い、Process.WaitForExit()で同期をとればOKです。
次にコードの一部を示します。

System.Diagnostics.Process p = System.Diagnostics.Process.Start(AccessRuntime);
p.WaitForExit();

	
        private bool AccessRuntimeInstall()
        {
            bool res = false;
            int osbit = System.Environment.Is64BitProcess ? 64 : 32;

            RegistryKey BaseKey = null;
            RegistryKey Key = null;

            // Access.Application.14    Access2010
            BaseKey = Registry.LocalMachine;
            Key = BaseKey.OpenSubKey("SOFTWARE\Classes\Access.Application.14\CLSID");

            bool SearchKeyRes = SearchKey(BaseKey, Key, osbit);
            if (SearchKeyRes == false)         {
                // Access.Application.15    Access2013
                Key = BaseKey.OpenSubKey("SOFTWARE\Classes\Access.Application.15\CLSID");
                SearchKeyRes = SearchKey(BaseKey, Key, osbit);
            }
            
            if (SearchKeyRes == true)   {
                res = true;
                return res;
            }  else {
                String Access = "AccessRunTime.exe";
                try {
                    WebClient client = new WebClient();

                    if (osbit == 32)  {
                        client.DownloadFile("http://www.aa.bb/cc/AccessRuntime_x86_ja-jp.exe", Access);
                    }   else  {
                        client.DownloadFile("http://www.aa.bb/cc/AccessRuntime_x64_ja-jp.exe", Access);
                    }

                    while (client.IsBusy == true)                {
                        System.Threading.Thread.Sleep(1000);
                    }

                    Using (System.Diagnostics.Process p = System.Diagnostics.Process.Start(Access)) {
			p.WaitForExit();
		    }
		    res = true;
                } catch (Exception ex)  {
                    Console.WriteLine(ex.Message);
                    res = false;
                }  finally  {
                    System.IO.File.Delete(Access);
                }
            }
            return res;
        }

        private bool SearchKey(RegistryKey BaseKey, RegistryKey Key, int osbit)
        {
            RegistryKey CLSKey;
            if (Key != null)           {
                String CLSID = (String)Key.GetValue("");
                if (osbit == 32)       {
                    CLSKey = BaseKey.OpenSubKey("SOFTWARE\Classes\CLSID\" + CLSID);
                }   else     {
                    // HKEY_LOCAL_MACHINESoftwareWOW6432NodeClassesCLSID    で  CLSID をキーとして を探す
                    CLSKey = BaseKey.OpenSubKey("SOFTWARE\WOW6432Node\Classes\CLSID\" + CLSID);
                }
                if (CLSKey != null) { return true; }
            }
            return false;
        }
    }

WiX Toolset – Bootstrapper

WiXは少しお休みして、現代史の勉強をしようと思っていたのですが、Bootstrapper Projectを習得したいという欲求の方が強く、結局WiXの勉強を継続しました。

完璧ではないのですが、概要は理解したのでご報告します。

Bootstrapper というのは、インストール・プロジェクトの途中で、別のインストールプログラムをインストールしようというものです。

現在、Visual StudioではWixをベースにしたBootstrapper Projectをサポートしています。私もこのBootstrapper Projectを使いました。

さて、今Accessを必要とするアプリケーション・プログラムがあるとします。
すなわち、このプログラムが動作するためには、ターゲットコンピュータに予めAccessがインストールされていなければいけません。

もし、ターゲットコンピュータにAccessがインストールされていなければ、所定の場所(WEB)から無料のAccessRuntimeをダウンロードして、ターゲットコンピュータにインストールします。

今、アプリケーションのインストールプログラム(MyAppli.ms)は完成しているとして、ここでやるべきことは、
1. ターゲットコンピュータに必要なAccessがインストールされているかどうか調べ
2. なければAccessruntimeをダウンロードし、インストールする
3. Accessruntimeのインストールが成功する、あるいはもともとAccessがインストールされていれば、
4. 自分のアプリケーションをインストールする。
というものです。

Visual Studioを起動して、
プロジェクト・テンプレートのWindows Installer XMLからBootstrapper Projectを選択します。
その中心部分(といっても僅かですが)は以下のようなものです。

	<Bundle Name="Bootstrapper1" Version="1.0.0.0" Manufacturer="" UpgradeCode="98647ef2-65fe-4702-8b60-f59824fb2642">
		<BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.RtfLicense" />

		<Chain>
			<!-- TODO: Define the list of chained packages. -->
			<!-- <MsiPackage SourceFile="pathtoyour.msi" /> -->
		</Chain>
	</Bundle>

この<Chain>要素にExeあるいはMsi形式のセットアッププログラムを記述します。
書かれた順番にインストールが実行されます。

AccessRuntimeExe形式なのでExePackage 要素を使って、またMyAppliMsi形式なのでMsiPackage 要素を使って、以下のように書きます。

	
      <ExePackage Id="AccessRuntime2013"
                  DownloadUrl="http://www.aa.bb.cc/XXX/AccessRuntime_x86_ja-jp.exe"
                  SourceFile="AccessRuntime_x86_ja-jp.exe"
                  Compressed="no" Permanent="yes"
                  InstallCondition="NOT VersionNT64"
                  DetectCondition="NOT VersionNT64 AND (LocalServer3214 OR LocalServer3215)" />

      <RollbackBoundary />

      <MsiPackage SourceFile="MyAppli.msi" DisplayInternalUI="yes"
                  Permanent="yes" Visible="yes"/>

ここでMyAppli.msiのインストールはあまり問題がないと思います。

実は、WiXのBootstrapper Projectには問題があって、Chain要素に書かれたプログラムをインストールするだけでなく、自分自身もターゲットコンピュータにインストールするのです。

すなわち、上の例でいえばBootstrapper1もインストールされて、Windowsの「プログラムと機能」画面に、Bootstrapper1プログラムも表示されます。

そして悪いことに、Bootstrapper1をアンインストールすると、上の例で、AccessRuntimeMyAppliもアンインストールされてしまいます。

これを避けるため、ExePackage 要素とMsiPackage 要素の属性で、Permanent=”yes” を宣言しておきます。

これによって、Bootstrapper1をアンインストールしても、Chain要素のプログラムがアンインストールされることはありません(の筈です)。

WiX4では修正されるようですが、期待しています。

また、<RollbackBoundary />はこれに続くインストールで、不具合が起こってもそれ以前の部分は、Rollbackしないことを指示しています。

すなわち、MyAppliでインストールが中断されても、AccessRuntimeのRollbackはしない指示です。

 

さて少し厄介なのは、Accessがターゲットコンピュータにすでにインストールされているかどうかを調べる部分です。

WiXでは、ターゲットコンピュータのレジストリを調べる機能が用意されています。

Microsoft Officeには、ProgIDが付けられています。

例えば、Accessであれば、Access.Application.14のようです。ProgIDは製品版でもRuntimeでも同じです。

Access2010のProgIDAccess.Application.14、Access2013のProgIDAccess.Application.15です。

またOffice製品は、CLSIDもつけられていて、このCLSIDからターゲットコンピュータのどこにAccessがインストールされているを知ることができます。

以下にサンプルを示します。(後日記:以下の部分を<BootstrapperApplicationRef>要素の直後に挿入します。多分)

    <util:RegistrySearch
      Variable="varClsId3214"
      Root="HKLM"
      Key="SOFTWAREClassesAccess.Application.14CLSID"
      Result="value"/>
    <util:RegistrySearch
      Variable="LocalServer3214"
      Root="HKLM"
      Key="SOFTWAREClassesCLSID[varClsId3214]LocalServer32"
      Result="exists"/>

最初のRegistrySearchAccess.Application.14CLSIDの値を求め、この値を変数varClsId3214にセットし、次のRegistrySearchで、varClsId3214をキーとして、LocalServer32(当該ソフトのインストール先)の存在を確認します。

LocalServer3214の値が”1″ならAccessがインストールされているし、”0″ならインストールされていません。

この値は変数LocalServer3214にセットされます。

したがって、LocalServer3214が”1″なら、Accessインストールを飛ばしてMyAppliをインストールし、”0″ならAccessがインストールされていないので、
ExePackage 要素のDownloadUrl要素に示されたURLから、AccessRuntime_x86_ja-jp.exeをダウンロードし、ターゲットコンピュータにインストールしてから、MyAppliのインストールをします。

以上、WiXについて私が当初目標にしていたところまで勉強を進めました。
WiXで唯一残念なのは、上で書いたようにBootstrapper Projekutで自分自身をインストールすることですが、多分これは次のバージョンV4で修正してくれると思います。

それも間もなくリリースしてくれそうなので、期待しています。

最後に一つだけ追加します。

自分が開発したプログラムの依存ライブラリーも、ターゲットコンピュータにコピーしなければいけませんが、これはVSの参照設定を見ればいいと思っていました。

一方、VS2010では簡単なセットアップ・プロジェクト構築ができますが、ここには依存ライブラリーを収集する機能があります。

ところが、VSの参照ライブラリーとVS2010の依存ライブラリーが少し違います。
どちらがいいのか分かりませんが、VS2010のセットアップ・プロジェクトなら間違いないかなと思っています。

さて、WiXでのセットアッププログラムの作成をご紹介してきました。
一通り勉強してみて、WiXで大抵のことはできる、InstallShieldInstallAwareを使う必要はないと確信しました。

ただし、以前ご紹介した参考書は役に立ちましたが、特にBootstrapの記述が十分ではないし、他にもドキュメントが少ないので、完璧に理解したというわけにはいきません。

とはいえ、WiXは私自身長年習得したいと思っていた技術でしたので、今回一通り勉強できて、また「使える」という感想を持つことができて、ほっとしています。

細かいことは書かないで来ました。
細かいところまで解説する知識がないし、エネルギーがなかったからですが、もう少し勉強して、自信が持てて、更に気分が向いたら、別の場所でWiXの解説をしたいと思います。

WiX Toolset – これまでとこれから

なんとかカスタムアクションを動かすところまで来ました。

これまで、主に参考にしたのは、WiX3.6: A Developer’s Guide to Windows Install XML   (PACKT 2012、以下Guide といいます)と、[WiX Toolset チュートリアル](日本語訳)です。

[Guide]はとてもいい本で、この本をしっかり読めば大抵のことはわかると思います。
私は、しっかり読んだとは言えませんし、それに今半分程度読んだところです。

WiXに関して、私は自分に興味のあるところだけ勉強するつもりなので、全450ページの内、後3分の1程度読めばいいかなと思っています。

残された私の興味は、開発したアプケーションが動作するために必要な、Office等のランタイムが、すでにターゲットコンピュータにインストールされているかどうか調べ、なければWebを介してインストールする仕組みを作ることです。

これは多分[BootStrap]というテクニックを使うと思うのですが、この話が[Guide]の一番最後の方に書かれているので、そこまで辛抱して勉強しなければいけません。

気分転換にまた現代史の本を読みたいと思います。
山のように積まれて待っていますので。

WiX Toolset – CustomActionの組込

ユーザにプロダクトキーの入力を求め、正当性を検査するダイアログUserRegistrationDlgを作成します。

出来上がりは次のような画面です。

コードは次の通りです。プログラミングの経験がある人は、何をしているか見当がつくと思います。ただし、InvalidPidDlgは不正入力があったときの画面です。

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
 <Fragment>
  <!-- TODO: Put your code here. -->
    <UI>
      <Dialog Id="UserRegistrationDlg" Width="370" Height="270" Title="[ProductName] [Setup]" NoMinimize="yes">
        <Control Id="NameLabel" Type="Text" X="45" Y="73" Width="100" Height="15" TabSkip="no" Text="ユーザー名(&amp;U):" />
        <Control Id="NameEdit" Type="Edit" X="45" Y="85" Width="220" Height="18" Property="USERNAME" Text="{80}" />
        <Control Id="OrganizationLabel" Type="Text" X="45" Y="110" Width="100" Height="15" TabSkip="no" Text="会社名(&amp;O):" />
        <Control Id="OrganizationEdit" Type="Edit" X="45" Y="122" Width="220" Height="18" Property="COMPANYNAME" Text="{80}" />
        <Control Id="CDKeyLabel" Type="Text" X="45" Y="147" Width="50" Height="10" TabSkip="no">
          <Text>CD キー(&amp;K)</Text>
        </Control>
        <Control Id="CDKeyEdit" Type="MaskedEdit" X="45" Y="159" Width="250" Height="16" Property="PIDKEY" Text="[PIDTemplate]" />
        <Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Text="戻る(&amp;B)">
          <Publish Event="NewDialog" Value="LicenseAgreementDlg">1</Publish>
        </Control>
        <Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Text="次へ(&amp;N)">
          <Publish Event="DoAction" Value="CheckingPID" Order="1">1</Publish>
          <Publish Event="SpawnWaitDialog" Value="InvalidPidDlg" Order="2"><![CDATA[PIDACCEPTED = "1"]]></Publish>
          <Publish Event="NewDialog" Value="SetupTypeDlg" Order="3"><![CDATA[PIDACCEPTED = "1"]]></Publish>
        </Control>
        <Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="キャンセル">
          <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
        </Control>
        <Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" Text="WixUI_Bmp_Banner" />
        <Control Id="Description" Type="Text" X="25" Y="23" Width="280" Height="15" Transparent="yes" NoPrefix="yes">
          <Text>あなたのユーザー情報を入力して下さい。</Text>
        </Control>
        <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
        <Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15" Transparent="yes" NoPrefix="yes">
          <Text>{WixUI_Font_Title}ユーザー情報</Text>
        </Control>
        <Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
      </Dialog>
      <Dialog Id="InvalidPidDlg" Width="260" Height="85" Title="[ProductName] [Setup]" NoMinimize="yes">
        <Control Id="Icon" Type="Icon" X="15" Y="15" Width="24" Height="24"
                 ToolTip="Information icon" FixedSize="yes" IconSize="32" Text="Exclam.ico" />
        <Control Id="Return" Type="PushButton" X="100" Y="57" Width="56" Height="17"
                 Default="yes" Cancel="yes" Text="戻る(&amp;R)">
          <Publish Event="EndDialog" Value="Return">1</Publish>
        </Control>
        <Control Id="Text" Type="Text" X="48" Y="15" Width="194" Height="30" TabSkip="no">
          <Text>入力されたユーザー・キーは無効です。弊社発行のキーを入力してください。</Text>
        </Control>
      </Dialog>
    </UI>
  </Fragment>
</Wix>

ここで注目していただきたいのは、15行目の[Back]PushButtonと18行目の[Next]PushButtonです。これらの[Control]要素の中にある [Publish]要素は、例えばBackコントロールでいえば、このボタンを押せば、新しいダイアログLicenseAgreementDlgに遷移しろという意味です。

[Next]ボタンを押したら、まず前回ご説明したCheckingPIDを実行しろ、その結果[PIDACCEPTED = 1](キー正常)でなければInvalidPidDlgに遷移しろ、[PIDACCEPTED = 1]ならばSetupTypeDlg画面に遷移しろ、ということになります。

20行目、SpawnWaitDialogは条件が「偽」の場合、Value=”InvalidPidDlg”に遷移しろ、という命令です。注意してください。

さて、このダイアログ・コードをメインプログラムに組み込む方法は二つあります。

一つは本体に対してライブラリー・プロジェクトを作成する方法です。VSのWindows Installer XMLからSetup Library Projectを選択して新規のプロジェクトを作成し、メインプロジェクトからは、他のライブラリー同様参照設定して使用します。

もう一つは、単純にメインプロジェクトに、WiXファイルを新規に追加して、メインプロジェクトの一ファイルとして開発します。

コードは全く同じですから、セットアップ・プロジェクトが大規模かどうか等で選択すればいいと思います。

さて、カスタムアクションと特注のダイアログをどのようにメインコードに組み込むか。

最初はカスタムアクションの定義です。

前々回ご紹介したカスタムアクションをビルドすると、ライブラリーCheckPID.CA.dllが得られます。メイン・プログラムの[Product]要素の直下に以下の文を挿入します。

<Binary Id="MyCustomActionDLL" SourceFile=".CheckPID.CA.dll" />
<CustomAction Id="CheckingPID" BinaryKey="MyCustomActionDLL" DllEntry="CheckPID" Execute="immediate" Return="check" />

少しご説明します。まず[Binary]要素を定義します。SourceFileはセットアップ・プロジェクト開発環境にあるカスタム・アクションのパス付ファイル名を指定します。

次はCustomAction の定義です。BinaryKeyはBinary要素のIdです。DllEntryはライブラリーCheckPID.CA.dllのエントリー関数名です。CustomActionのIdは、上にご紹介したUserRegistrationDlgのNextボタンで起動される、セットアッププロジェクトで使われるCustomAction の一意名です。

次はダイアログの組み込みです。同じくメイン・プログラムのProduct要素の直下に以下の文を挿入します。

まず、ユーザ定義のUI・MyWixUI_Mondoを宣言します。

この中で、WixUI_MondoとWixUI_ErrorProgressTextへの参照を宣言。続いて自作のUserRegistrationDlgを参照。次の「LicenseAgreementDlgの[Next]ボタンが押されたら、新しいダイアログUserRegistrationDlgに遷移しろ」、と「SetupTypeDlgダイアログでBackが押されたら、UserRegistrationDlgダイアログに遷移しろ」
を付け加えます。

    <UI Id="MyWixUI_Mondo">
      <UIRef Id="WixUI_Mondo" />
      <UIRef Id="WixUI_ErrorProgressText" />
      <DialogRef Id="UserRegistrationDlg" />
      <Publish Dialog="LicenseAgreementDlg" Control="Next" Event="NewDialog" Value="UserRegistrationDlg" Order="2">
        LicenseAccepted = "1"
      </Publish>
      <Publish Dialog="SetupTypeDlg" Control="Back" Event="NewDialog" Value="UserRegistrationDlg">
        1
      </Publish>
    </UI>

そのほか、アイコン指定や、グローバル変数(WiXではプロパティといいます)を定義します。以下、カスタムアクション関連のコードを再掲載します。

この中で、PIDTemplateはプロダクトキー入力用MaskedEditのテンプレートで、ここに入力された文字列からPIDKEYの値を取得します。

一番下の[WixVariable Id=”WixUILicenseRtf”] は使用許諾書の設定、
[WixVariable Id=”WixUIBannerBmp” ]はセットアップ画面に使う画像を指定しています。

<Binary Id="Exclam.ico" SourceFile="$(var.ImageFilesPath)warning.ico" />
<Property Id="PIDTemplate"><![CDATA[12345<^^^^ ^^^^ ^^^^ ^^^^>@@@@@]]></Property>
<Property Id="PIDACCEPTED" Value="123" />
<Binary Id="MyCustomActionDLL" SourceFile=".CheckPID.CA.dll" />
<CustomAction Id="CheckingPID" BinaryKey="MyCustomActionDLL" DllEntry="CheckPID" Execute="immediate" Return="check" />
<UI Id="MyWixUI_Mondo">
  <UIRef Id="WixUI_Mondo" />
  <UIRef Id="WixUI_ErrorProgressText" />
  <DialogRef Id="UserRegistrationDlg" />
  <Publish Dialog="LicenseAgreementDlg" Control="Next" Event="NewDialog" Value="UserRegistrationDlg" Order="2">
   LicenseAccepted = "1"
  </Publish>
  <Publish Dialog="SetupTypeDlg" Control="Back" Event="NewDialog" Value="UserRegistrationDlg">
   1
  </Publish>
</UI>
<WixVariable Id="WixUILicenseRtf" Value=".使用許諾契約書.rtf" />
<WixVariable Id="WixUIBannerBmp" Value="$(var.ImageFilesPath)myImage.bmp" />