概要
WiX Toolset を使えばMSI形式のインストーラを作成できますが、最新の Ver.6 では「収益をあげている団体が使用する場合、保守料金を支払う必要がある」と謳われています。そこで、WiX Toolset を使わずにMSI形式のインストーラを作成する方法を模索しています。そんななか、Python Ver3.13 で廃止された msilib に興味が湧いたので、これを使ってMSI形式のインストーラを作成してみました。
最新のPythonでmsilibを利用する
msilib は Python 本体から切り離され、以下のリポジトリに保管されています。
https://github.com/TrevorHamm/msilib
そして、これ単独でパッケージ化されPyPIにアップされているので、以下のpipコマンドで導入できます。
pip install pymsilib
パッケージ名がmsilibからpymsilibに変わっているので、利用の際には注意が必要です。また、メンテナンスが止まったパッケージですので、時が経つにつれ色々と問題が出てくる可能性があります。私が気付いたところでは、tempfile モジュールの mktemp() 関数が使われており、これは現在非推奨になっています。いずれ廃止されるかもしれないので、運用を考えるなら自分でメンテナンスしていく必要があります。
最小のインストーラを実装する
Program Filesフォルダに製品名でフォルダを作りファイルを一つ配置する、シンプルな「インストーラビルドスクリプト」を作成しました。
import os
import argparse
import pymsilib as msilib
from pymsilib import schema
from pymsilib import sequence
def make_msi(source_path, msi_path, product_name, version, manufacturer):
# sanity
source_path = os.path.abspath(source_path)
out_dir = os.path.dirname(os.path.abspath(msi_path))
if not os.path.exists(source_path):
raise FileNotFoundError(f"source file not found: {source_path}")
os.makedirs(out_dir, exist_ok=True)
# ProductCode
product_code = msilib.gen_uuid() # 新規 UUID を生成
# データベース初期化
db = msilib.init_database(msi_path, schema, product_name, product_code, version, manufacturer)
# シーケンスを追加
msilib.add_data(db, 'InstallExecuteSequence', sequence.InstallExecuteSequence)
msilib.add_data(db, 'InstallUISequence', sequence.InstallUISequence)
msilib.add_data(db, 'AdminExecuteSequence', sequence.AdminExecuteSequence)
msilib.add_data(db, 'AdminUISequence', sequence.AdminUISequence)
msilib.add_data(db, 'AdvtExecuteSequence', sequence.AdvtExecuteSequence)
# CAB を作ってファイルを追加する準備
cab = msilib.CAB('data1.cab') # 一時 CAB 名(最終的には MSI に埋め込まれる)
# Directory ツリーを作る
# ルート(TARGETDIR / SourceDir)
targetdir = msilib.Directory(db, cab, None, "TARGETDIR", "TARGETDIR", "SourceDir")
# ProgramFilesFolder の下に Product 用ディレクトリを作る
programfiles = msilib.Directory(db, cab, targetdir, "ProgramFiles64Folder", "ProgramFiles64Folder", ".")
app_dir = msilib.Directory(db, cab, programfiles, product_name, "INSTALLDIR", product_name)
# Feature を作る(1つの Feature に Component を割り当てる)
feature = msilib.Feature(db, "DefaultFeature", product_name, f"Install {product_name}", 1, directory=app_dir.logical)
# Component にファイルを追加
app_dir.start_component(feature=feature, keyfile=os.path.basename(source_path), flags=0)
# ここでは絶対パスを渡してソースファイルを CAB に入れる
app_dir.add_file(os.path.basename(source_path), src=source_path)
# CAB をコミット(CAB を作成して MSI に組み込む)
cab.commit(db)
# Summary information を埋めて保存
si = db.GetSummaryInformation(20)
si.SetProperty(msilib.PID_TITLE, product_name)
si.SetProperty(msilib.PID_AUTHOR, manufacturer)
si.Persist()
# データベースをコミットして閉じる
db.Commit()
db.Close()
print(f"Created MSI: {msi_path}")
if __name__ == "__main__":
p = argparse.ArgumentParser(description="Create a minimal MSI using msilib")
p.add_argument("--source", required=True, help="File to package (single file)")
p.add_argument("--msi", required=True, help="Output MSI path")
p.add_argument("--product", default="MyProduct", help="Product name")
p.add_argument("--version", default="0.0.1", help="Product version")
p.add_argument("--manufacturer", default="Manufacturer", help="Manufacturer")
args = p.parse_args()
make_msi(args.source, args.msi, args.product, args.version, args.manufacturer)
このスクリプトと同じフォルダにファイルmyapp.exeを置いて、以下のコマンドを実行すると、MyAppSetup.msiが得られます。
py make_msi.py --source myapp.exe --msi out/MyAppSetup.msi --product "MyApp" --version "1.0.0" --manufacturer "YourName"
感想
ここまでくるのは、決して簡単ではありませんでした。msilib のソースを読みつつ、既存のちゃんと動くMSIのテーブルを確認しつつ、print 文デバッグを駆使しつつ、ようやく動くものになりました。msilib は Windows Installer の知識がないと使いこなせないでしょうね。とはいえ、このアプローチでツールやライブラリを自作するのも不可能ではないな、という感触を得ることができました。