Claude Code for msilib: Python Windows Installer Package Creation — Claude Skills 360 Blog
Blog / AI / Claude Code for msilib: Python Windows Installer Package Creation
AI

Claude Code for msilib: Python Windows Installer Package Creation

Published: January 22, 2029
Read time: 5 min read
By: Claude Skills 360

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.

Keep Reading

AI

Claude Code for email.contentmanager: Python Email Content Accessors

Read and write EmailMessage body content with Python's email.contentmanager module and Claude Code — email contentmanager ContentManager for the class that maps content types to get and set handler functions allowing EmailMessage to support get_content and set_content with type-specific behaviour, email contentmanager raw_data_manager for the ContentManager instance that handles raw bytes and str payloads without any conversion, email contentmanager content_manager for the standard ContentManager instance used by email.policy.default that intelligently handles text plain text html multipart and binary content types, email contentmanager get_content_text for the handler that returns the decoded text payload of a text-star message part as a str, email contentmanager get_content_binary for the handler that returns the raw decoded bytes payload of a non-text message part, email contentmanager get_data_manager for the get-handler lookup used by EmailMessage get_content to find the right reader function for the content type, email contentmanager set_content text for the handler that creates and sets a text part correctly choosing charset and transfer encoding, email contentmanager set_content bytes for the handler that creates and sets a binary part with base64 encoding and optional filename Content-Disposition, email contentmanager EmailMessage get_content for the method that reads the message body using the registered content manager handlers, email contentmanager EmailMessage set_content for the method that sets the message body and MIME headers in one call, email contentmanager EmailMessage make_alternative make_mixed make_related for the methods that convert a simple message into a multipart container, email contentmanager EmailMessage add_attachment for the method that attaches a file or bytes to a multipart message, and email contentmanager integration with email.message and email.policy and email.mime and io for building high-level email readers attachment extractors text body accessors HTML readers and policy-aware MIME construction pipelines.

5 min read Feb 12, 2029
AI

Claude Code for email.charset: Python Email Charset Encoding

Control header and body encoding for international email with Python's email.charset module and Claude Code — email charset Charset for the class that wraps a character set name with the encoding rules for header encoding and body encoding describing how to encode text for that charset in email messages, email charset Charset header_encoding for the attribute specifying whether headers using this charset should use QP quoted-printable encoding BASE64 encoding or no encoding, email charset Charset body_encoding for the attribute specifying the Content-Transfer-Encoding to use for message bodies in this charset such as QP or BASE64, email charset Charset output_codec for the attribute giving the Python codec name used to encode the string to bytes for the wire format, email charset Charset input_codec for the attribute giving the Python codec name used to decode incoming bytes to str, email charset Charset get_output_charset for returning the output charset name, email charset Charset header_encode for encoding a header string using the charset's header_encoding method, email charset Charset body_encode for encoding body content using the charset's body_encoding, email charset Charset convert for converting a string from the input_codec to the output_codec, email charset add_charset for registering a new charset with custom encoding rules in the global charset registry, email charset add_alias for adding an alias name that maps to an existing registered charset, email charset add_codec for registering a codec name mapping for use by the charset machinery, and email charset integration with email.message and email.mime and email.policy and email.encoders for building international email senders non-ASCII header encoders Content-Transfer-Encoding selectors charset-aware message constructors and MIME encoding pipelines.

5 min read Feb 11, 2029
AI

Claude Code for email.utils: Python Email Address and Header Utilities

Parse and format RFC 2822 email addresses and dates with Python's email.utils module and Claude Code — email utils parseaddr for splitting a display-name plus angle-bracket address string into a realname and email address tuple, email utils formataddr for combining a realname and address string into a properly quoted RFC 2822 address with angle brackets, email utils getaddresses for parsing a list of raw address header strings each potentially containing multiple comma-separated addresses into a list of realname address tuples, email utils parsedate for parsing an RFC 2822 date string into a nine-tuple compatible with time.mktime, email utils parsedate_tz for parsing an RFC 2822 date string into a ten-tuple that includes the UTC offset timezone in seconds, email utils parsedate_to_datetime for parsing an RFC 2822 date string into an aware datetime object with timezone, email utils formatdate for formatting a POSIX timestamp or the current time as an RFC 2822 date string with optional usegmt and localtime flags, email utils format_datetime for formatting a datetime object as an RFC 2822 date string, email utils make_msgid for generating a globally unique Message-ID string with optional idstring and domain components, email utils decode_rfc2231 for decoding an RFC 2231 encoded parameter value into a tuple of charset language and value, email utils encode_rfc2231 for encoding a string as an RFC 2231 encoded parameter value, email utils collapse_rfc2231_value for collapsing a decoded RFC 2231 tuple to a Unicode string, and email utils integration with email.message and email.headerregistry and datetime and time for building address parsers date formatters message-id generators header extractors and RFC-compliant email construction utilities.

5 min read Feb 10, 2029

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free