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

WiX Editををダウンロード、インストール。
WiX Edit ページ:http://wixedit.sourceforge.net/
ダウンロードページ:https://sourceforge.net/projects/wixedit/files/wixedit/

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パラメータ設定