Open7

WIXを使ったWindows Installerの作成(ver3) ver5に移行予定

yunayuna

WiX (Windows Installer XML)ツールセットとは

WiX(Windows Installer XML)は、Windowsアプリケーションのインストールパッケージを作成するためのオープンソースツールセットです。XMLベースのソースファイルを使用して、Windows Installer(MSI)パッケージや、アプリケーションのアップデートやパッチを配布するためのパッチ(MSP)や、インストール前の条件チェックを行うためのセットアップバンドル(EXE)を生成します。

windowsアプリの開発経験が少ない筆者が、rustでwindowsアプリ開発するにあたって、インストーラの作成をどうするか調べていく中で、このツールを使うに至り調査した結果をメモしていきます。

本サイト

https://wixtoolset.org/

参考になるサイト

cliベースでmsiを構築するのはここが一番わかりやすかった
https://subscription.packtpub.com/book/web-development/9781784393212/1/ch01lvl1sec13/building-a-wix-installer-from-the-command-line

tauri(rustによるwebviewを用いたデスクトップアプリ実装。 https://tauri.app/ )のテンプレートサンプル

https://github.com/tauri-apps/tauri/blob/89602cdce34f3ea5b4a9b8921dc04a197f2d7de8/tooling/bundler/src/bundle/windows/templates/main.wxs

rustでの実装に役立ちそうな rust ツール(cargoに統合できる?)

https://github.com/volks73/cargo-wix

その他参考サイト

https://wix-tutorial-ja.github.io/
https://qiita.com/hiro_t/items/2b51ec2d495eb31a07b0
https://sorceryforce.net/ja/tips/wix-installer-create1
https://sorceryforce.net/ja/tips/wix-installer-update

yunayuna

上記サンプルサイトにあった例の要約

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
  <Product Id="*" 
           Name="My Software"
           Language="1033"
           Manufacturer="My Company"
           Version="1.0.0.0" 
           UpgradeCode="8c7d85db-b0d1-4a9a-85ea-130836aeef67">
    
    <Package InstallerVersion="200" 
               Compressed="yes" 
               InstallScope="perMachine" />

    <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
    <MediaTemplate EmbedCab="yes" />

    <Feature Id="ProductFeature" 
               Title="The main feature" 
               Level="1">
      <ComponentGroupRef Id="ProductComponents" />
    </Feature>
  </Product>

  <Fragment>
    <Directory Id="TARGETDIR" Name="SourceDir">
      <Directory Id="ProgramFilesFolder">
        <Directory Id="INSTALLFOLDER" 
                       Name="My Software" />
      </Directory>
    </Directory>
  </Fragment>

  <Fragment>
    <ComponentGroup Id="ProductComponents" 
                      Directory="INSTALLFOLDER">
      <Component Id="cmpMyTextFileTXT" 
                    Guid="{A4540658-09B6-46DA-8880-0B1962E06642}">
        <File Source="MyTextFile.txt" />
      </Component>
    </ComponentGroup>
  </Fragment>
</Wix>

要約

このWiX (Windows Installer XML) コードは、"My Software" というアプリケーションのインストールパッケージを定義しています。

このコードは、"My Software" という名前のアプリケーションをインストールする際、ファイル "MyTextFile.txt" をProgram Filesフォルダ内の "My Software" フォルダに配置するインストールパッケージを作成します。
アプリケーションは、システムのProgram Filesフォルダに "My Software" という名前のフォルダを作成し、その中に"MyTextFile.txt"ファイルを配置します。

Windows Installer(MSI)パッケージが生成され、アプリケーションをインストールする際に使用できます。

主な要素

<Product>: アプリケーションの基本情報を定義しています。
<Package>: インストーラの設定を指定しています。
<MajorUpgrade>: ダウングレード時のエラーメッセージを設定しています。
<MediaTemplate>: メディア(インストールファイル)の設定を定義しています。
<Feature>: アプリケーションの主要機能を定義しています。
<Directory>: アプリケーションのディレクトリ構造を定義しています。
<ComponentGroup>: 関連するコンポーネント(ファイル、レジストリキー、ショートカットなど)の集合を定義しています。

<Fragment>: WiXプロジェクト内の独立した部分を表します。フラグメントは、再利用可能なコードの断片を作成するのに役立ちます。この例では、ディレクトリ構造とコンポーネントグループがフラグメントとして定義されています。

<Directory Id="INSTALLFOLDER" Name="My Software">: インストールフォルダを定義しており、Program Filesフォルダ内に"My Software"という名前のフォルダが作成されます。

<Component Id="cmpMyTextFileTXT" Guid="{A4540658-09B6-46DA-8880-0B1962E06642}">: コンポーネントを定義しており、このコンポーネントは"MyTextFile.txt"ファイルを含みます。

<File Source="MyTextFile.txt">: インストールするファイルを指定しています。この例では、"MyTextFile.txt"ファイルがインストール対象です。

yunayuna

日本語で、以下の要件を満たす設定ファイルを作成してみる

アプリ名:"AssetCare"
実行ファイル(exe)は、"it_agent.exe","update.exe"の2つです。

1.install時、フォルダにある以下3つのファイル、"it_agent.exe","update.exe",".env" の3つのファイルについて、フォルダ"Program Files\AssetCare"の直下に展開する
2.install時、次に、展開された"it_agent.exe"について、"it_agent.exe --mode install" を実行する

3.uninstall時、まず"it_agent.exe --mode uninstall"を実行する
4.uninstall時、次に"Program Files\AssetCare"を削除する

前提知識

・日本語のID:1041

言語IDは、NSIS (Nullsoft Scriptable Install System) : 昔からあるオープンソースのbundlerシステム で規定されている、以下のJapanese.nlfファイルのIDを利用している。こちらを参照。
https://github.com/kichik/nsis/tree/9465c08046f00ccb6eda985abbdbf52c275c6c4d/Contrib/Language files

最終版

Product.wxs
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
  <Product Id="*" Name="AssetCare"
           Language="1033"
           Manufacturer="YourCompanyName"
           Version="1.0.0.0"  UpgradeCode="9c57f086-33f1-47f1-91a8-613bccdc88ba">
    <Package InstallerVersion="200" 
               Compressed="yes" 
               InstallScope="perMachine" />

    <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
    <MediaTemplate EmbedCab="yes" />

    <Feature Id="MainFeature" Title="Main Feature" Level="1">
      <ComponentGroupRef Id="AssetCareComponents" />
    </Feature>

    <Directory Id="TARGETDIR" Name="SourceDir">
      <Directory Id="ProgramFilesFolder">
        <Directory Id="INSTALLFOLDER" Name="AssetCare">
          <Component Id="it_agent.exe" Guid="{41f1f9b0-0315-42fa-90ce-aac00e9fe81f}">
            <File Id="it_agent.exe" Source="it_agent.exe" KeyPath="yes" />
          </Component>
          <Component Id="update.exe" Guid="{731d5b59-c7d2-4ec2-8435-45abdf9ec036}">
            <File Id="update.exe" Source="update.exe" KeyPath="yes" />
          </Component>
          <Component Id="dotEnv" Guid="{ea9645a6-6099-4de0-98d2-7a2b1a931a50}">
            <File Id="dotEnvFile" Source=".env" KeyPath="yes" />
          </Component>
        </Directory>
      </Directory>
    </Directory>

    <ComponentGroup Id="AssetCareComponents">
      <ComponentRef Id="it_agent.exe" />
      <ComponentRef Id="update.exe" />
      <ComponentRef Id="dotEnv" />
    </ComponentGroup>

    <CustomAction Id="Install_it_agent" Directory="INSTALLFOLDER" ExeCommand='"[SystemFolder]cmd.exe" /C "it_agent.exe" --mode install > C:\temp\test.txt' Execute="deferred" Impersonate="no" Return="check" />
    <InstallExecuteSequence>
      <Custom Action="Install_it_agent" Before="InstallFinalize">NOT Installed</Custom>
    </InstallExecuteSequence>


    <CustomAction Id="Uninstall_it_agent" Directory="INSTALLFOLDER" ExeCommand='"[SystemFolder]cmd.exe" /C "it_agent.exe" --mode uninstall > C:\temp\test.txt' Execute="deferred" Impersonate="no" Return="check" />
    <InstallExecuteSequence>
      <Custom Action="Uninstall_it_agent" After="InstallInitialize">Installed AND REMOVE="ALL"</Custom>
    </InstallExecuteSequence>

  </Product>
</Wix>
yunayuna

wxs仕様 memo

[SystemFolder]について

[SystemFolder]は、Windowsのシステムフォルダを指します。システムフォルダは、通常、以下の場所にあります。

32ビットのWindows:C:\Windows\System32
64ビットのWindows:C:\Windows\SysWOW64(32ビットアプリケーション用)およびC:\Windows\System32(64ビットアプリケーション用)
ただし、WiXで[SystemFolder]を使うと、自動的に32ビットまたは64ビットのシステムフォルダに適切に展開されます。つまり、32ビット版のWindows上で実行される場合はC:\Windows\System32が使われ、64ビット版のWindows上で32ビットアプリケーションとして実行される場合はC:\Windows\SysWOW64が使われます。

その他の暗黙的なpath (by AI 未検証)

[WindowsFolder]:Windowsのインストールディレクトリ(通常はC:\Windows)。
[ProgramFilesFolder]:プログラムファイルのディレクトリ。32ビットシステムではC:\Program Files、64ビットシステムではC:\Program Files (x86)(32ビットアプリ用)。
[ProgramFiles64Folder]:64ビットシステムでのプログラムファイルのディレクトリ(C:\Program Files)。
[CommonFilesFolder]:共有プログラムファイルのディレクトリ。32ビットシステムではC:\Program Files\Common Files、64ビットシステムではC:\Program Files (x86)\Common Files(32ビットアプリ用)。
[CommonFiles64Folder]:64ビットシステムでの共有プログラムファイルのディレクトリ(C:\Program Files\Common Files)。
[TempFolder]:一時ファイル用のディレクトリ(通常はC:\Users[Username]\AppData\Local\Temp)。
[AppDataFolder]:現在のユーザーのアプリケーションデータディレクトリ(通常はC:\Users[Username]\AppData\Roaming)。
[LocalAppDataFolder]:現在のユーザーのローカルアプリケーションデータディレクトリ(通常はC:\Users[Username]\AppData\Local)。
[DesktopFolder]:現在のユーザーのデスクトップディレクトリ。
[MyPicturesFolder]:現在のユーザーのマイピクチャディレクトリ。
[MyDocumentsFolder]:現在のユーザーのドキュメントディレクトリ。

Impersonate 属性について

    <CustomAction Id="Install_it_agent" Directory="INSTALLFOLDER" ExeCommand="it_agent.exe --mode install" Execute="deferred" Impersonate="no" Return="check" />
    <InstallExecuteSequence>
      <Custom Action="Install_it_agent" After="InstallFiles">NOT Installed</Custom>
    </InstallExecuteSequence>

Impersonate属性は、カスタムアクションが実行される際のユーザーコンテキストに関する設定を制御します。Impersonate属性の値がyes(デフォルト)の場合、カスタムアクションは、インストールを実行しているユーザーの権限で実行されます。これは、通常のユーザーコンテキストで実行されるアクションや操作に適しています。

一方、Impersonate属性の値がnoの場合、カスタムアクションはシステムアカウント(LocalSystemアカウント)で実行されます。これは、管理者権限が必要な操作や、他のユーザーアカウントに影響を与える操作に適しています

yunayuna

msi実行時のエラーに対応する

msi実行時、以下のようなエラーが出て進まない事がある。
英語の場合

There is a problem with this Windows Installer package. A program required for this install to complete could not be run. Contact your support personnel or package vendor.

これを検証するには、msi実行時にログを出力するようにする

msiexec /i "Installer.msi" /l*v "log.txt"

エラー発生時の実際のログ:

MSI (s) (C0:44) [17:15:07:368]: File: C:\Program Files (x86)\AssetCare\it_agent.exe;	To be installed;	Won't patch;	No existing file
MSI (s) (C0:44) [17:15:07:368]: Source for file 'it_agent.exe' is compressed
MSI (s) (C0:44) [17:15:07:543]: Executing op: FileCopy(SourceName=update.exe,SourceCabKey=update.exe,DestName=update.exe,Attributes=512,FileSize=6053888,PerTick=65536,,VerifyMedia=1,,,,,CheckCRC=0,,,InstallMode=58982400,HashOptions=0,HashPart1=-1536252112,HashPart2=-1634207995,HashPart3=1633814810,HashPart4=2119473254,,)
MSI (s) (C0:44) [17:15:07:543]: File: C:\Program Files (x86)\AssetCare\update.exe;	To be installed;	Won't patch;	No existing file
MSI (s) (C0:44) [17:15:07:543]: Source for file 'update.exe' is compressed
MSI (s) (C0:44) [17:15:07:656]: Executing op: CacheSizeFlush(,)
MSI (s) (C0:44) [17:15:07:656]: Executing op: ActionStart(Name=Install_it_agent,,)
MSI (s) (C0:44) [17:15:07:667]: Executing op: CustomActionSchedule(Action=Install_it_agent,ActionType=3106,Source=C:\Program Files (x86)\AssetCare\,Target=it_agent.exe --mode install,)
MSI (s) (C0:44) [17:15:07:676]: Note: 1: 1721 2: Install_it_agent 3: C:\Program Files (x86)\AssetCare\ 4: it_agent.exe --mode install 
MSI (s) (C0:44) [17:15:07:676]: Note: 1: 2205 2:  3: Error 
MSI (s) (C0:44) [17:15:07:676]: Note: 1: 2228 2:  3: Error 4: SELECT `Message` FROM `Error` WHERE `Error` = 1721 
MSI (c) (54:9C) [17:15:07:723]: Font created.  Charset: Req=0, Ret=0, Font: Req=, Ret=Arial

Error 1721. There is a problem with this Windows Installer package. A program required for this install to complete could not be run. Contact your support personnel or package vendor. Action: Install_it_agent, location: C:\Program Files (x86)\AssetCare\, command: it_agent.exe --mode install 
MSI (s) (C0:44) [17:15:09:184]: Note: 1: 2205 2:  3: Error 
MSI (s) (C0:44) [17:15:09:184]: Note: 1: 2228 2:  3: Error 4: SELECT `Message` FROM `Error` WHERE `Error` = 1709 
MSI (s) (C0:44) [17:15:09:184]: Product: AssetCare -- Error 1721. There is a problem with this Windows Installer package. A program required for this install to complete could not be run. Contact your support personnel or package vendor. Action: Install_it_agent, location: C:\Program Files (x86)\AssetCare\, command: it_agent.exe --mode install 

Action ended 17:15:09: InstallFinalize. Return value 3.
MSI (s) (C0:44) [17:15:09:210]: Note: 1: 2265 2:  3: -2147287035 
MSI (s) (C0:44) [17:15:09:214]: User policy value 'DisableRollback' is 0
MSI (s) (C0:44) [17:15:09:214]: Machine policy value 'DisableRollback' is 0
MSI (s) (C0:44) [17:15:09:217]: Note: 1: 2318 2:  
MSI (s) (C0:44) [17:15:09:218]: Executing op: Header(Signature=1397708873,Version=500,Timestamp=1451461092,LangId=1033,Platform=0,ScriptType=2,ScriptMajorVersion=21,ScriptMinorVersion=4,ScriptAttributes=1)
MSI (s) (C0:44) [17:15:09:218]: Executing op: DialogInfo(Type=0,Argument=1033)
MSI (s) (C0:44) [17:15:09:219]: Executing op: DialogInfo(Type=1,Argument=AssetCare)
MSI (s) (C0:44) [17:15:09:220]: Executing op: RollbackInfo(,RollbackAction=Rollback,RollbackDescription=Rolling back action:,RollbackTemplate=[1],CleanupAction=RollbackCleanup,CleanupDescription=Removing backup files,CleanupTemplate=File: [1])
MSI (s) (C0:44) [17:15:09:225]: Executing op: ActionStart(Name=Install_it_agent,,)
MSI (s) (C0:44) [17:15:09:226]: Executing op: ProductInfo(ProductKey={7CA75E4C-C0CF-4297-B7CF-229A2CE29FC9},ProductName=AssetCare,PackageName=CommandLineInstaller.msi,Language=1033,Version=16777216,Assignment=1,ObsoleteArg=0,,,PackageCode={832BF33B-CAFF-4128-A57B-F0E5AD0C5002},,,InstanceType=0,LUASetting=0,RemoteURTInstalls=0,ProductDeploymentFlags=3)
MSI (s) (C0:44) [17:15:09:228]: Executing op: ActionStart(Name=InstallFiles,Description=Copying new files,Template=File: [1],  Directory: [9],  Size: [6])
MSI (s) (C0:44) [17:15:09:230]: Executing op: SetTargetFolder(Folder=C:\Program Files (x86)\AssetCare\)
MSI (s) (C0:44) [17:15:09:231]: Executing op: FileRemove(,FileName=C:\Program Files (x86)\AssetCare\update.exe,,)
MSI (s) (C0:44) [17:15:09:243]: Note: 1: 2318 2:  
MSI (s) (C0:44) [17:15:09:245]: Executing op: FileRemove(,FileName=C:\Program Files (x86)\AssetCare\it_agent.exe,,)
MSI (s) (C0:44) [17:15:09:258]: Note: 1: 2318 2:  
MSI (s) (C0:44) [17:15:09:260]: Executing op: FileRemove(,FileName=C:\Program Files (x86)\AssetCare\.env,,)
MSI (s) (C0:44) [17:15:09:271]: Note: 1: 2318 2:  
MSI (s) (C0:44) [17:15:09:284]: Executing op: ActionStart(Name=ProcessComponents,Description=Updating component registration,)
MSI (s) (C0:44) [17:15:09:285]: Executing op: ComponentUnregister(ComponentId={EA9645A6-6099-4DE0-98D2-7A2B1A931A50},ProductKey={7CA75E4C-C0CF-4297-B7CF-229A2CE29FC9},BinaryType=0,)
MSI (s) (C0:44) [17:15:09:287]: Executing op: ComponentUnregister(ComponentId={731D5B59-C7D2-4EC2-8435-45ABDF9EC036},ProductKey={7CA75E4C-C0CF-4297-B7CF-229A2CE29FC9},BinaryType=0,)
MSI (s) (C0:44) [17:15:09:288]: Executing op: ComponentUnregister(ComponentId={41F1F9B0-0315-42FA-90CE-AAC00E9FE81F},ProductKey={7CA75E4C-C0CF-4297-B7CF-229A2CE29FC9},BinaryType=0,)
MSI (s) (C0:44) [17:15:09:289]: Executing op: End(Checksum=0,ProgressTotalHDWord=0,ProgressTotalLDWord=0)
MSI (s) (C0:44) [17:15:09:289]: Error in rollback skipped.	Return: 5
MSI (s) (C0:44) [17:15:09:299]: Note: 1: 2318 2:  
MSI (s) (C0:44) [17:15:09:311]: Note: 1: 2318 2:  
yunayuna

WIX ver3→ver4 migration

migrationに関する参考URL
https://wixtoolset.org/docs/fourthree/
https://wixtoolset.org/docs/fourthree/faqs/

ver4では、nugetのみのdownloadになったので、nugetのダウンロードページのDownloadPackageからダウンロード。nugetファイルはzip圧縮ファイルなので、解凍ソフトで解凍しておく。

https://www.nuget.org/packages/wix
▼download
https://www.nuget.org/api/v2/package/wix/4.0.0

ver3で使っていた設定ファイルのwxsを、ver4用にconvert

"wix.4.0.0\tools\net6.0\any\wix.exe" build -o product.msi product.wxs

bundleは、ver3と異なり、コマンド1発でOK

"wix.4.0.0\tools\net6.0\any\wix.exe" build -o bin\product.msi product.wxs