WiX によるインストーラの作成

2019年11月16日

はじめに

WiX によるインストーラ作成メモ。

環境

  • Windows 10 64 bit
  • WiX v3.11.2

セットアップ

ここ からダウンロードしてインストールする。

インストール先の bin フォルダを PATH に設定する。

C:\Program Files (x86)\WiX Toolset v3.11\bin

コマンドプロンプトで "candle" と打って何か出てくれば OK。

インストーラの作成

たとえば、次のようなファイルセットがあるとする。

  • window.bat
  • window.py

window.py は Python スクリプトで、window.bat で実行するものとする。

window.bat

call %USERPROFILE%\AppData\Local\Continuum\miniconda3\Scripts\activate.bat
python window.py

window.py

import sys
from PySide2.QtWidgets import QMainWindow, QApplication, QDesktopWidget


class Main(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("window")

        self.resize(320, 240)
        self.centerOnScreen()
        self.show()

    def centerOnScreen(self):
        res = QDesktopWidget().screenGeometry()
        self.move((res.width()/2) - (self.frameSize().width()/2),
                  (res.height()/2) - (self.frameSize().height()/2))


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = Main()
    sys.exit(app.exec_())

インストーラの作成の流れは、次のようになる。

  • window.wsx を書く。
  • candle で wsx ファイルを wixobj ファイルに変換。
  • light で wixobj ファイルから msi ファイル (インストーラ) を作成する。

wsx ファイルの作成

ファイルセットと同じ場所に window.wsx を作る。

window.wsx

<?xml version="1.0" encoding="windows-1252"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
    <Product Name="Window 1.0"
            Id="{GUID}"
            UpgradeCode="{GUID}"
            Language="1033" Codepage="1252"
            Version="1.0.0" Manufacturer="me">

        <Package Id="*" Keywords="Installer"
                Description="Window 1.0 Installer"
                Manufacturer="me" InstallerVersion="100"
                Languages="1033" Compressed="yes" SummaryCodepage="1252" />

        <MediaTemplate EmbedCab="yes" />

        <Directory Id="TARGETDIR" Name="SourceDir">
            <Directory Id="ProgramFilesFolder">
                <Directory Id="INSTALLDIR" Name="Window 1.0">
                    <Component Id="MainExecutable"
                            Guid="{GUID}">
                        <File Id="window.bat"
                                Name="window.bat"
                                Source="window.bat" KeyPath="yes">
                            <Shortcut Id="startmenu"
                                    Directory="ProgramMenuDir"
                                    Name="Window 1.0"
                                    WorkingDirectory="INSTALLDIR"
                                    Icon="window.bat" IconIndex="0"
                                    Advertise="yes" />
                            <Shortcut Id="desktop"
                                Directory="DesktopFolder"
                                Name="Window 1.0"
                                WorkingDirectory="INSTALLDIR"
                                Icon="window.bat" IconIndex="0"
                                Advertise="yes" />
                        </File>
                    </Component>
                    <Component Id="MainExecutableBase"
                            Guid="{GUID}">
                        <File Id="window.py"
                                Name="window.py"
                                Source="window.py" KeyPath="yes">
                        </File>
                    </Component>
                </Directory>
            </Directory>

            <Directory Id="ProgramMenuFolder" Name="Programs">
                <Directory Id="ProgramMenuDir" Name="Window 1.0">
                    <Component Id="ProgramMenuDir"
                            Guid="{GUID}">
                        <RemoveFolder Id="ProgramMenuDir" On="uninstall" />
                        <RegistryValue Root="HKCU"
                                Key="Software\[Manufacturer]\[ProductName]"
                                Type="string" Value="" KeyPath="yes" />
                    </Component>
                </Directory>
            </Directory>

            <Directory Id="DesktopFolder" Name="Desktop" />
        </Directory>

        <Feature Id="Complete" Title="Window 1.0" Display="expand"
                Level="1" ConfigurableDirectory="INSTALLDIR">
            <Feature Id="MainProgram" Title="Program">
                <ComponentRef Id="MainExecutable" />
                <ComponentRef Id="MainExecutableBase" />
                <ComponentRef Id="ProgramMenuDir" />
            </Feature>
        </Feature>

        <WixVariable Id="WixUILicenseRtf" Value="license.rtf" />

        <Property Id="WIXUI_INSTALLDIR" Value="INSTALLDIR" />
        <UIRef Id="WixUI_InstallDir" />
        <UIRef Id="WixUI_ErrorProgressText" />

        <Icon Id="window.bat" SourceFile="window.bat" />
    </Product>
</Wix>

{GUID} となっているところは、GUID を発行して書く。これは Visual Studio に付属する uuidgen で生成できる。または、PowerShell で以下のようにしてもよい。

> [Guid]::NewGuid()

内容を見てみる。

<?xml version="1.0" encoding="windows-1252"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">

おまじない。

    <Product Name="Window 1.0"
            Id="{GUID}"
            UpgradeCode="{GUID}"
            Language="1033" Codepage="1252"
            Version="1.0.0" Manufacturer="me">

製品情報。Manufacturer は作成者名や会社名など。日本語が使いたい場合は、次のように encoding、Language、Codepage を変更する。

<?xml version='1.0' encoding='utf-8'?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">

    <Product Name="Window 1.0"
            Id="{GUID}"
            UpgradeCode="{GUID}"
            Language='1041' Codepage='932'
            Version="1.0.0" Manufacturer="私">

続き。

        <Package Id="*" Keywords="Installer"
                Description="Window 1.0 Installer"
                Manufacturer="me" InstallerVersion="100"
                Languages="1033" Compressed="yes" SummaryCodepage="1252" />

インストーラの情報。

        <MediaTemplate EmbedCab="yes" />

インストーラを一つにまとめる (cab ファイルを msi に埋め込む)。

        <Directory Id="TARGETDIR" Name="SourceDir">
            <Directory Id="ProgramFilesFolder">
                <Directory Id="INSTALLDIR" Name="Window 1.0">

ディレクトリの設定。ProgramFilesFolder は規定の ID で、"C:\Program Files" を指す。ドライブ直下がよければ 2 段目を削除すればよい。3 段目はインストールディレクトリ。

                    <Component Id="MainExecutable"
                            Guid="{GUID}">
                        <File Id="window.bat"
                                Name="window.bat"
                                Source="window.bat" KeyPath="yes">
                            <Shortcut Id="startmenu"
                                    Directory="ProgramMenuDir"
                                    Name="Window 1.0"
                                    WorkingDirectory="INSTALLDIR"
                                    Icon="window.bat" IconIndex="0"
                                    Advertise="yes" />
                            <Shortcut Id="desktop"
                                Directory="DesktopFolder"
                                Name="Window 1.0"
                                WorkingDirectory="INSTALLDIR"
                                Icon="window.bat" IconIndex="0"
                                Advertise="yes" />
                        </File>
                    </Component>

コンポーネントの設定。基本的には 1 コンポーネントにつき 1 ファイルを対応させる。上の例ではショートカットも作っている。

                    <Component Id="MainExecutableBase"
                            Guid="{GUID}">
                        <File Id="window.py"
                                Name="window.py"
                                Source="window.py" KeyPath="yes">
                        </File>
                    </Component>
                </Directory>
            </Directory>

コンポーネント 2 つめ。インストールディレクトリのための Directory タグを閉じる。

            <Directory Id="ProgramMenuFolder" Name="Programs">
                <Directory Id="ProgramMenuDir" Name="Window 1.0">
                    <Component Id="ProgramMenuDir"
                            Guid="{GUID}">
                        <RemoveFolder Id="ProgramMenuDir" On="uninstall" />
                        <RegistryValue Root="HKCU"
                                Key="Software\[Manufacturer]\[ProductName]"
                                Type="string" Value="" KeyPath="yes" />
                    </Component>
                </Directory>
            </Directory>

            <Directory Id="DesktopFolder" Name="Desktop" />
        </Directory>

ショートカットを置くためのプログラムメニューおよびデスクトップのフォルダのための設定。

        <Feature Id="Complete" Title="Window 1.0" Display="expand"
                Level="1" ConfigurableDirectory="INSTALLDIR">
            <Feature Id="MainProgram" Title="Program">
                <ComponentRef Id="MainExecutable" />
                <ComponentRef Id="MainExecutableBase" />
                <ComponentRef Id="ProgramMenuDir" />
            </Feature>
        </Feature>

インストール項目の設定。このあたりでカスタムインストールの項目設定などを行える。上の例では必ず全部インストールされる。

        <WixVariable Id="WixUILicenseRtf" Value="license.rtf" />

ライセンスファイルの指定。ワードパットでリッチテキストとして用意する。

        <Property Id="WIXUI_INSTALLDIR" Value="INSTALLDIR" />
        <UIRef Id="WixUI_InstallDir" />
        <UIRef Id="WixUI_ErrorProgressText" />

インストーラの構成。ここでは WixUI_InstallDir でインストール先だけ指定できる形式にしている。他には以下のものがある。

  • WixUI_Mondo
  • WixUI_FeatureTree
  • WixUI_Minimal
  • WixUI_Advanced

標準、カスタム、完全などのセットアップタイプを選択できるようにしたい場合は WixUI_Mono を、カスタム形式のセットアップタイプにしたい場合は WixUI_FeatureTree を選択する。

        <Icon Id="window.bat" SourceFile="window.bat" />
    </Product>
</Wix>

アイコンの設定。ここでは便宜上 bat ファイルを指定しているが、本当は exe ファイルや ico ファイルなどを指定する。設定終了。

インストーラの作成

コマンドプロンプトで以下のようにする。

>candle window.wsx
>light -ext WixUIExtension window.wixobj

window.msi ファイルができる。インストール・アンインストールは、普通にやってもよいが、テストにはコマンドで実行したほうが早い。

インストール

>msiexec /i window.msi

アンインストール

>msiexec /x window.msi

ログを取りたい場合は、次のようにする。

>msiexec /i window.msi /l*vx log.txt

管理者にだけ実行させる

Package タグの後ろに以下のように入れる。

        <Condition Message="You need to be an administrator to install this product.">
            Privileged
        </Condition>

カスタム形式でのセットアップ

カスタム形式でのセットアップを行うには、WixUI_FeatureTree を使う。

<UIRef Id="WixUI_FeatureTree" />
<UIRef Id="WixUI_ErrorProgressText" />

Feature タグを次のように設定する。

        <Feature Id="Complete" Title="Window 1.0" Display="expand"
                Level="1" ConfigurableDirectory="INSTALLDIR"
                AllowAdvertise="no" Absent="disallow">
            <Feature Id="MainProgram" Title="lux-win"
                    AllowAdvertise="no" Absent="disallow">
                <ComponentRef Id="MainExecutable" />
                <ComponentRef Id="MainExecutableBase" />
                <ComponentRef Id="ProgramMenuDir" />
            </Feature>

            <Feature Id="Document" Title="Document" Display="expand"
                    AllowAdvertise="no" Absent="disallow">
                <Feature Id="Manual" Title="Manual"
                        AllowAdvertise="no" Absent="disallow">
                    <ComponentRef Id="Manual" />
                </Feature>

                <Feature Id="Reference" Title="Reference"
                        AllowAdvertise="no" Absent="disallow">
                    <ComponentRef Id="Reference" />
                </Feature>

                <ComponentRef Id="Dummy" />
            </Feature>

            <ComponentRef Id="Dummy" />
        </Feature>

ここで、Feature タグの属性 AllowAdvertise、Absent は、項目のメニューの表示/非表示のためのものである。 AllowAdvertise="no" は "必要なときにインストールされる" を非表示にするものである。Absent="disallow" は "インストールしない" を非表示にする (つまり必ずインストールされる)。ComponentRef で "Dummy" を指定しているのは、コンポーネントを指定しない Feature だと項目のメニューに "ネットワークからインストールする" という選択肢が出てきてしまうためである。"Dummy" は以下のようなものである。

                    <Component Id="Dummy"
                            Guid="{GUID}">
                        <RegistryValue Root="HKCU"
                                Key="Software\[Manufacturer]\[ProductName]"
                                Type="string" Value="" KeyPath="yes" />
                    </Component>

ライセンス表示をスキップする

UIRef タグのところを以下のようなものに置き換える。

WiUI_InstallDir の場合

        <UI>
            <UIRef Id="WixUI_InstallDir" />
            <UIRef Id="WixUI_ErrorProgressText" />
            <Publish Dialog="WelcomeDlg"
                    Control="Next"
                    Event="NewDialog"
                    Value="InstallDirDlg"
                    Order="3">1</Publish>
            <Publish Dialog="InstallDirDlg"
                    Control="Back"
                    Event="NewDialog"
                    Value="WelcomeDlg"
                    Order="3">1</Publish>
        </UI>

WiUI_FeatureTree の場合

        <UI>
            <UIRef Id="WixUI_FeatureTree" />
            <UIRef Id="WixUI_ErrorProgressText" />
            <Publish Dialog="WelcomeDlg"
                    Control="Next"
                    Event="NewDialog"
                    Value="CustomizeDlg"
                    Order="3">1</Publish>
            <Publish Dialog="CustomizeDlg"
                    Control="Back"
                    Event="NewDialog"
                    Value="WelcomeDlg"
                    Order="3">1</Publish>
        </UI>

インストールフォルダの SDK\wixui にある "WixUI_InstallDir.wxs" や "WixUI_FeatureTree.wxs" などを参考にする。

画像の入れ替え

ライセンスファイルを指定しているところで、以下のように書くとインストーラの画像を入れ替えられる。

        <WixVariable Id="WixUIBannerBmp" Value="banner.bmp" />
        <WixVariable Id="WixUIDialogBmp" Value="dialog.bmp" />

ショートカットのリンク先の編集

ショートカットで引数を指定したい場合などは、リンク先を編集するが、それは Shortcut タグの Arguments で指定できる。ただし、ショートカットを Advertise="no" で作る必要がある。そのまま Advertise="no" にしただけではうまくいかないので、以下のようにする。

                    <Component Id="MainExecutable"
                            Guid="{GUID}">
                        <File Id="window.bat"
                                Name="window.bat"
                                Source="window.bat" KeyPath="no">
                            <Shortcut Id="startmenu"
                                    Directory="ProgramMenuDir"
                                    Name="Window 1.0"
                                    WorkingDirectory="INSTALLDIR"
                                    Icon="window.bat" IconIndex="0"
                                    Advertise="no" />
                            <Shortcut Id="desktop"
                                Directory="DesktopFolder"
                                Name="Window 1.0"
                                WorkingDirectory="INSTALLDIR"
                                Icon="window.bat" IconIndex="0"
                                Advertise="no" />
                        </File>
                        <RegistryValue Root="HKCU"
                                Key="Software\[Manufacturer]\[ProductName]"
                                Type="string" Value="" KeyPath="yes" />
                    </Component>

File タグの KeyPath を "no" にし、RegistryValue タグを追加している。

ショートカットで実行時に最小化する。

Shortcut タグで Show="minimized" を指定する。

フォルダの中身をコンポーネントにする

コンポーネント 1 つにファイル 1 つが原則だが、フォルダにたくさんのファイルやフォルダが入っているものをインストーラに含めようとするとき、1 つずつコンポーネントタグを記述していくのは骨が折れる。そのため、自動作成ツールがある。たとえば、doc フォルダがあるとき、以下のように実行する。

>heat dir doc -var var.SourceDir -dr INSTALLDIR -ke -gg -cg Doc -out doc.wxs

オプション "-ke" は空のフォルダも含める指定、"-gg" は GUID を生成する指定である。

wxs ファイルで Feature タグに次のように追加する。

        <Feature Id="Complete" Title="Window 1.0" Display="expand"
                Level="1" ConfigurableDirectory="INSTALLDIR">
            <Feature Id="MainProgram" Title="Program">
                <ComponentRef Id="MainExecutable" />
                <ComponentRef Id="MainExecutableBase" />
                <ComponentRef Id="ProgramMenuDir" />
                <ComponentGroupRef Id="Doc" />
            </Feature>
        </Feature>

インストーラへの変換は次のようにする。

>candle -dSourceDir=doc window.wxs doc.wxs
>light -o window.msi -ext WixUIExtension window.wixobj doc.wixobj

フォルダが複数ある場合は、変数を var.SourceDir2、var.SouceDir3... などと増やしていって対応できる。

※注意: 現状、長過ぎるパスは受け付けないようである。

参考

インストール後の処理

インストール後などに処理を実行したい場合は、次のようにする。

        <CustomAction Id='LaunchFile' FileKey='proc.exe'
                ExeCommand="" Return="asyncNoWait" />

        <InstallExecuteSequence>
            <Custom Action="LaunchFile"
                    After="InstallFinalize">NOT Installed</Custom>
        </InstallExecuteSequence>

CustomAction タグの FileKey でコンポーネントで指定したうちの実行ファイルを指定できる。ExeCommand で引数も渡せる。Return には asyncWait、asyncNoWait が指定できる。Custom タグの "NOT Installed" というのは、インストーじのみ実行するという意味である。

処理の実行ファイルをコンポーネントに含めたくない場合は、Binary タグを使う。

        <Binary Id="proc" SourceFile="proc.exe" />

        <CustomAction Id="LaunchFile" BinaryKey="proc"
                ExeCommand="" Return="asyncNoWait" />

管理者権限で実行したい場合は、インストール中に実行する必要がある。

        <Binary Id="proc" SourceFile="proc.exe" />

        <CustomAction Id="LaunchFile" BinaryKey="proc"
                ExeCommand="" Return="asyncWait"
                Execute="deferred" Impersonate="no"/>

        <InstallExecuteSequence>
            <Custom Action="LaunchFile"
                    Before="InstallFinalize">NOT Installed</Custom>
        </InstallExecuteSequence>

コンポーネントを圧縮ファイルにする

コンポーネントを圧縮ファイルにして、インストール後に展開するには、7-Zip の自己展開アーカイブを用いる。7-Zip で自己展開アーカイブを作り、それをオプション '-o"[INSTALLDIR]" -y' 付きで実行すればよい。

        <Binary Id="archive" SourceFile="archive.exe" />

        <CustomAction Id="ExtractArchive" BinaryKey="archive"
                ExeCommand='-o"[INSTALLDIR]" -y' Return="asyncWait"
                Execute="deferred" Impersonate="no"/>

        <InstallExecuteSequence>
            <Custom Action="ExtractArchive"
                    Before="InstallFinalize">NOT Installed</Custom>
        </InstallExecuteSequence>

インストールはこれでよいのだが、展開されたファイルはアンインストール時に削除されない。少し強引なやり方としては、以下のようにやる方法がある 。アンインストール時にファイルが削除される前に、cmd で rd コマンドや del コマンドを発行して、展開されたファイルを削除する方法である。

        <Binary Id="cmd" SourceFile="c:\windows\system32\cmd.exe" />
        ...

        <CustomAction Id="RemoveFile" BinaryKey="cmd"
                ExeCommand='/c rd /s /q "[INSTALLDIR]\archive"' Return="asyncWait"
                Execute="deferred" Impersonate="no"/>

        <InstallExecuteSequence>
            ...
            <Custom Action="RemoveFile"
                Before="RemoveFiles">REMOVE="ALL"</Custom>
        </InstallExecuteSequence>

圧縮ファイルのサイズが大きすぎるとインストーラが固まる (固まって見える?) ので、その場合は小さく分けたほうがよい。

カスタム形式のセットアップ

カスタム形式のセットアップで、インストールが選択されたものだけを展開するようにしたい場合は、次のようにする。Feature で項目を追加。

            <Feature Id="Archive" Title="Archive">
                <ComponentRef Id="Archive" />
            </Feature>

Custom Action に条件を付ける。

        <InstallExecuteSequence>
            <Custom Action="ExtractArchive"
                    Before="InstallFinalize">
                NOT Installed AND <![CDATA[(&Archive=3)]]>
            </Custom>
        </InstallExecuteSequence>

空のコンポーネントにサイズを指定するには、ReserveCost タグを用いればよい。

                    <Component Id="Archive"
                            Guid="{GUID}">
                        <ReserveCost Id="Archive" RunLocal="1000000"
                                RunFromSource="0" />
                        <RegistryValue Root="HKCU"
                                Key="Software\[Manufacturer]\[ProductName]"
                                Type="string" Value="" KeyPath="yes" />
                    </Component>
RunLocal にバイト単位でサイズを指定する。問題は、32 bit 整数を要求するため、2 GB 程度までしか対応できないことである。

参考