Python’s msilib module (Windows only, deprecated Python 3.9, removed Python 3.13) wraps the Windows Installer API to create and manipulate .msi packages. import msilib. Open: msilib.OpenDatabase("app.msi", msilib.MSIDBOPEN_DIRECT). Create: db = msilib.CreateDatabase("app.msi", msilib.MSIDBOPEN_CREATE). Initialise: msilib.init_database(db, "AppName", "1.0", "Manufacturer", product_code, upgrade_code). Add tables: msilib.add_tables(db, msilib.schema) — installs the standard MSI schema. Summary: si = db.GetSummaryInformation(0); si.SetProperty(msilib.PID_TITLE, "My App"). Directory: msilib.Directory(db, cab, basedir, logical, physical, default). Feature: msilib.Feature(db, "DefaultFeature", "Title", "Description", display=1, level=1, attributes=0). Add files: dir_obj.start_component(); dir_obj.add_file(filename). CAB: cab = msilib.CAB("cab1"). Commit: db.Commit(). Claude Code generates Windows software installers, silent uninstallers, upgrade packages, per-machine and per-user installers, and CI-generated MSI deployment packages.
CLAUDE.md for msilib
## msilib Stack
- Stdlib: import msilib, msilib.schema, msilib.sequence (3.9 deprecated, 3.13 removed)
- Open: db = msilib.OpenDatabase("app.msi", msilib.MSIDBOPEN_DIRECT)
- Create: db = msilib.CreateDatabase("app.msi", msilib.MSIDBOPEN_CREATE)
- Init: msilib.init_database(db, appname, version, manufacturer, prodcode, upgcode)
- Tables: msilib.add_tables(db, msilib.schema)
- Dir: msilib.Directory(db, cab, basedir, logical, physical, default)
- Feature: msilib.Feature(db, id, title, desc, display, level, attributes)
- CAB: cab = msilib.CAB("cab1")
- Commit: db.Commit()
- Note: Removed 3.13; use pywin32 / WiX Toolset for new MSI builds
msilib MSI Build Pipeline
# app/msilibutil.py — create MSI, add files/feature/dir, summary, validate, helpers
from __future__ import annotations
import os
import uuid
from dataclasses import dataclass, field
from pathlib import Path
_MSILIB_AVAILABLE = False
if os.name == "nt":
try:
import msilib
import msilib.schema
import msilib.sequence
_MSILIB_AVAILABLE = True
except ImportError:
pass
# ─────────────────────────────────────────────────────────────────────────────
# 1. MSI spec dataclass
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class MsiSpec:
"""
Describes an MSI package to build.
Example:
spec = MsiSpec(
app_name="MyTool",
version="1.0.0",
manufacturer="Acme Corp",
output_path="dist/mytool.msi",
source_dir="build/",
product_code=str(uuid.uuid4()).upper(),
upgrade_code="{AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE}",
)
"""
app_name: str
version: str
manufacturer: str
output_path: str
source_dir: str
product_code: str = field(default_factory=lambda: "{" + str(uuid.uuid4()).upper() + "}")
upgrade_code: str = field(default_factory=lambda: "{" + str(uuid.uuid4()).upper() + "}")
install_dir: str = "TARGETDIR"
feature_name: str = "DefaultFeature"
feature_title: str = "Default Feature"
feature_desc: str = "Main application files"
# ─────────────────────────────────────────────────────────────────────────────
# 2. Summary Information helpers
# ─────────────────────────────────────────────────────────────────────────────
def set_summary_info(db: Any, spec: MsiSpec) -> None:
"""
Set the MSI summary information stream properties.
Example:
set_summary_info(db, spec)
"""
if not _MSILIB_AVAILABLE:
return
si = db.GetSummaryInformation(0)
si.SetProperty(msilib.PID_TITLE,
f"{spec.app_name} {spec.version}")
si.SetProperty(msilib.PID_AUTHOR, spec.manufacturer)
si.SetProperty(msilib.PID_SUBJECT,
f"{spec.app_name} Installer")
si.SetProperty(msilib.PID_TEMPLATE, ";1033")
si.SetProperty(msilib.PID_REVNUMBER, spec.product_code)
si.SetProperty(msilib.PID_WORDCOUNT, 2) # per-machine install
si.SetProperty(msilib.PID_PAGECOUNT, 200) # schema version
si.Persist()
from typing import Any
# ─────────────────────────────────────────────────────────────────────────────
# 3. Property table helpers
# ─────────────────────────────────────────────────────────────────────────────
def add_properties(db: Any, spec: MsiSpec) -> None:
"""
Add required MSI Property table entries for the installer.
Example:
add_properties(db, spec)
"""
if not _MSILIB_AVAILABLE:
return
props = [
("Manufacturer", spec.manufacturer),
("ProductName", spec.app_name),
("ProductVersion", spec.version),
("ProductCode", spec.product_code),
("UpgradeCode", spec.upgrade_code),
("ALLUSERS", "1"), # per-machine install
]
view = db.OpenView(
"INSERT INTO Property (Property, Value) VALUES (?, ?)")
for name, value in props:
rec = msilib.CreateRecord(2)
rec.SetString(1, name)
rec.SetString(2, value)
view.Execute(rec)
view.Close()
# ─────────────────────────────────────────────────────────────────────────────
# 4. Minimal MSI builder
# ─────────────────────────────────────────────────────────────────────────────
def build_msi(spec: MsiSpec) -> str | None:
"""
Build a minimal MSI that installs all files from spec.source_dir.
Returns the path to the created MSI, or None if msilib is unavailable.
Example:
spec = MsiSpec(
app_name="HelloTool",
version="1.0.0",
manufacturer="Acme",
output_path="dist/hello.msi",
source_dir="build/",
)
msi_path = build_msi(spec)
print(f"Built: {msi_path}")
"""
if not _MSILIB_AVAILABLE:
print("[msilib] Windows not available — skipping MSI build")
return None
out = str(spec.output_path)
Path(out).parent.mkdir(parents=True, exist_ok=True)
db = msilib.CreateDatabase(out, msilib.MSIDBOPEN_CREATE)
msilib.add_tables(db, msilib.schema)
set_summary_info(db, spec)
add_properties(db, spec)
cab = msilib.CAB("cab1")
feature = msilib.Feature(
db,
spec.feature_name,
spec.feature_title,
spec.feature_desc,
display=1,
level=1,
attributes=0,
)
# Add files from source_dir
src = Path(spec.source_dir)
if src.is_dir():
rootdir = msilib.Directory(
db, cab, None,
spec.app_name, # logical name (used as target dir key)
"TARGETDIR", # parent
spec.app_name, # short name
)
rootdir.start_component(feature=feature)
for f in sorted(src.rglob("*")):
if f.is_file():
rootdir.add_file(str(f))
cab.commit(db)
msilib.add_data(db, "InstallUISequence",
msilib.sequence.InstallUISequence)
msilib.add_data(db, "InstallExecuteSequence",
msilib.sequence.InstallExecuteSequence)
db.Commit()
return out
# ─────────────────────────────────────────────────────────────────────────────
# 5. MSI property reader (works on existing MSI files)
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class MsiInfo:
product_name: str = ""
product_code: str = ""
upgrade_code: str = ""
version: str = ""
manufacturer: str = ""
install_dir: str = ""
def read_msi_info(msi_path: str) -> MsiInfo:
"""
Open an MSI and read its Property table.
Returns MsiInfo with empty fields if msilib is unavailable.
Example:
info = read_msi_info("C:/Downloads/app.msi")
print(info.product_name, info.version)
"""
info = MsiInfo()
if not _MSILIB_AVAILABLE:
return info
try:
db = msilib.OpenDatabase(msi_path, msilib.MSIDBOPEN_READONLY)
view = db.OpenView("SELECT Property, Value FROM Property")
view.Execute(None)
while True:
rec = view.Fetch()
if rec is None:
break
prop = rec.GetString(1)
value = rec.GetString(2)
if prop == "ProductName":
info.product_name = value
elif prop == "ProductCode":
info.product_code = value
elif prop == "UpgradeCode":
info.upgrade_code = value
elif prop == "ProductVersion":
info.version = value
elif prop == "Manufacturer":
info.manufacturer = value
elif prop == "TARGETDIR":
info.install_dir = value
view.Close()
except Exception:
pass
return info
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
import tempfile, shutil
print("=== msilib demo ===")
print(f" msilib available: {_MSILIB_AVAILABLE}")
if not _MSILIB_AVAILABLE:
print(" (Windows + Python <= 3.12 required for msilib)")
print(" For new MSI builds use WiX Toolset or pywin32.")
else:
# ── build_msi ─────────────────────────────────────────────────────────
with tempfile.TemporaryDirectory() as tmpdir:
# Create some dummy files to install
src_dir = os.path.join(tmpdir, "src")
os.makedirs(src_dir)
for fname in ["app.exe", "app.cfg", "README.txt"]:
open(os.path.join(src_dir, fname), "w").write(f"# {fname}\n")
msi_path = os.path.join(tmpdir, "demo.msi")
spec = MsiSpec(
app_name="DemoApp",
version="1.0.0",
manufacturer="Claude Skills 360",
output_path=msi_path,
source_dir=src_dir,
)
print(f"\n--- build_msi ---")
print(f" product_code : {spec.product_code}")
result = build_msi(spec)
if result:
size = os.path.getsize(result)
print(f" created: {result}")
print(f" size : {size} bytes")
# ── read_msi_info ─────────────────────────────────────────────
print(f"\n--- read_msi_info ---")
info = read_msi_info(result)
print(f" product_name : {info.product_name!r}")
print(f" product_code : {info.product_code!r}")
print(f" version : {info.version!r}")
print(f" manufacturer : {info.manufacturer!r}")
else:
print(" build failed")
print("\n=== done ===")
For the WiX Toolset (external) alternative — WiX v4 (dotnet tool install --global wix) compiles .wxs XML source files into professional MSI packages with rollback, elevation, service installation, COM registration, and custom actions — use WiX for all production Windows installer packages requiring the full Windows Installer feature set; WiX is maintained and supports modern packaging standards that msilib never covered. For the pywin32 (PyPI) + win32com alternative — win32com.shell.shell.ShellExecuteEx(lpVerb="runas", lpFile="msiexec", lpParameters=f'/i {msi}') can install or uninstall MSI packages and win32api.MsiQueryProductState(product_code) checks installation status — use pywin32/win32com for programmatic MSI queries and installs when you have an existing .msi file and need to orchestrate installation from Python; use msilib only for building custom MSI files on Python ≤ 3.12. The Claude Skills 360 bundle includes msilib skill sets covering MsiSpec configuration dataclass, set_summary_info() summary stream writer, add_properties() property table setter, build_msi() full MSI builder, and MsiInfo/read_msi_info() property reader. Start with the free tier to try Windows Installer packaging patterns and msilib pipeline code generation.