Skip to content

Python 桌面工具打包 MSIX 上架 Microsoft Store 实践

2026-04-27

Tags: 教程 · Python · Windows · MSIX · 打包发布

前言

之前的一篇文章里,我记录了开发 RDP Heartbeat 的动机和过程。那篇文章末尾我提了一句:

后面有时间的话,我打算写一篇文章,详细分享一下这套 Python 到 MSIX 的打包全流程。

现在它来了。

坦白说,把 Python 写的桌面小工具送上 Microsoft Store,比用 C# / WinUI 3 要折腾得多。后者 Visual Studio 基本一键搞定,但如果你和我一样用 Python,那这条路就是:

Python 源码 → PyInstaller → EXE → Inno Setup → 安装包 → MSIX Packaging Tool → MSIX → Store

每一步都可能踩坑,但每一步也都有解法。这篇文章就是踩坑地图和通关攻略。

本文以 RDP Heartbeat 项目为实例,但方法和思路适用于任何用 Python + Tkinter(或 PyQt、wxPython 等)写的 Windows 小工具。

另外提一句:理论上 PyInstaller 打包出的 exe 也许可以直接用 MSIX Packaging Tool 转制,跳过 Inno Setup。我们选择先用 Inno Setup 包一次,主要是为了方便自己分发(比如放个 Setup.exe 在 GitHub Release 上让大家下载),不一定是理论最优解,但这条路我们实实在在走通了,所以分享给需要的人。

参考源码:本文涉及的完整项目代码可在 [email protected] 查看,包含 build_release.pysetup.iss 和所有打包配置。

全景图:四条腿的流水线

在开始之前,先把整条链路摊开来看:

  • PyInstaller.py.exe
  • Inno Setup.exe → 安装包
  • MSIX Packaging Tool:安装包 → .msix
  • Microsoft Store:上架

为什么这么绕?因为 Microsoft Store 最终需要的是 MSIX 格式的包,而 MSIX Packaging Tool 的工作原理是对一个已经能正常安装和运行的传统安装程序做"转制"——它会监视安装过程,捕获所有文件写入、注册表变更、快捷方式创建,然后自动生成 MSIX。所以你首先需要一个干净、完整的传统安装包。

换句话说:PyInstaller 负责"能跑",Inno Setup 负责"能装",MSIX Packaging Tool 负责"能上架"。

下面我们一步一步来。

第一步:PyInstaller —— 把 Python 变成 EXE

1.1 准备你的项目

在动手之前,先确保你的 Python 项目结构清晰、入口明确。以 RDP Heartbeat 为例:

prune-rdp-heartbeat/
├── main.py              # 入口脚本
├── heartbeat_window.py  # 核心窗口逻辑
├── win_utils.py         # Win32 API 封装
├── tray_icon.py         # 系统托盘
├── settings_dialog.py   # 设置面板
├── about_dialog.py      # 关于对话框
├── config_manager.py    # 配置管理
├── i18n.py              # 国际化
├── startup.py           # 开机自启
├── logger.py            # 日志
├── version.py           # 版本号
├── icon.ico             # 程序图标
├── icon.png             # 源图标(用于生成 MSIX 资源)
├── requirements.txt     # 依赖清单
└── packaging/           # MSIX 相关资源(后面会用到)

依赖清单 requirements.txt

pystray
Pillow
pywin32
customtkinter
packaging

1.2 处理 PyInstaller 的"隐式依赖"

PyInstaller 靠静态分析来检测依赖,但有些库(尤其是 pystrayPIL)它检测不到,需要手动声明:

python
# build_release.py 中的关键配置
cmd = [
    sys.executable, "-m", "PyInstaller",
    "--noconsole",           # 不弹出命令行窗口
    "--onefile",             # 打包为单个 exe
    "--name=RDPHeartbeat",
    "--clean",
    "--icon=icon.ico",
    "--add-data=icon.ico;.", # Windows 用分号,Linux/macOS 用冒号
    "--hidden-import=PIL._tkinter_finder",
    "--hidden-import=pystray",
    "main.py"
]

另外,资源文件(如图标)打包后会被解压到临时目录,需要 resource_path() 来兼容:

python
# tray_icon.py 中的做法
def resource_path(relative_path):
    try:
        base_path = sys._MEIPASS   # PyInstaller 解压到的临时目录
    except Exception:
        base_path = os.path.abspath(".")  # 开发模式:当前目录
    return os.path.join(base_path, relative_path)

任何需要从文件系统读取的资源(图标、字体、配置文件模板等),都要走这个函数。

1.3 一键构建脚本

把 PyInstaller 命令封装成 build_release.py

python
# 先读 version.py 获取版本号
from version import APP_VERSION

def run_build():
    # 0. 清理上次构建产物
    if os.path.exists("build"): shutil.rmtree("build")
    if os.path.exists("dist"):  shutil.rmtree("dist")

    # 1. 运行 PyInstaller
    subprocess.check_call([...])

    # 2. 验证产物
    exe_path = os.path.join("dist", "RDPHeartbeat.exe")
    assert os.path.exists(exe_path), "Build failed!"

运行 python build_release.py,如果一切顺利,你会在 dist/ 下得到 RDPHeartbeat.exe

可以先双击跑一下,确认功能正常。

第二步:Inno Setup —— 把 EXE 做成正经安装包

MSIX Packaging Tool 需要一个安装程序作为转制源。Windows 生态里,Inno Setup 是免费、成熟、脚本化的安装包制作工具。

2.1 安装 Inno Setup

jrsoftware.org 下载并安装。建议同时安装 Inno Script Studio(可视化编辑器),会省很多事。

2.2 编写安装脚本

核心文件 setup.iss

ini
#define MyAppName "RDP Heartbeat"
#define MyAppVersion "1.1.3.0"
#define MyAppPublisher "Prune Lab"
#define MyAppExeName "RDPHeartbeat.exe"

[Setup]
AppId={{DDEC247A-5DC0-4393-BB13-466CCAD7F90B}   ; 每个应用唯一的 GUID
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppPublisher={#MyAppPublisher}
DefaultDirName={autopf}\{#MyAppName}             ; 默认装到 Program Files
ArchitecturesAllowed=x64compatible                ; 64 位系统
ArchitecturesInstallIn64BitMode=x64compatible
DisableProgramGroupPage=yes                       ; 不显示"选择程序组"页面
OutputBaseFilename=RDPHeartbeat_Setup             ; 输出文件名
SolidCompression=yes
WizardStyle=modern                                ; 现代安装向导风格
SetupIconFile=icon.ico

[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"

[Files]
Source: "dist\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion

[Icons]
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
Name: "{autodesktop}\{#MyAppName}";  Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon

[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; Flags: unchecked

[Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,...}"; Flags: nowait postinstall skipifsilent

几个值得注意的点:

  • AppId 必须唯一:这是 Windows 识别应用身份的依据,不要和其他软件冲突。Inno Setup IDE 可以一键生成 GUID。
  • 不需要写注册表:MSIX 转制时会自动处理安装过程中的注册表写入,但我们这个应用本身就不需要,所以不写更干净。

2.3 自动化版本号同步

你的版本号可能定义在 Python 代码里(version.py),而 setup.iss 里也有一份。手动同步迟早会翻车。所以在 build_release.py 里加一段自动 patch:

python
def patch_setup_iss():
    with open("setup.iss", 'r', encoding='utf-8') as f:
        content = f.read()
    content = re.sub(
        r'#define MyAppVersion ".*?"',
        f'#define MyAppVersion "{APP_VERSION}"',
        content
    )
    with open("setup.iss", 'w', encoding='utf-8') as f:
        f.write(content)

现在运行 python build_release.py,会自动把最新版本号写进 setup.iss,然后 PyInstaller 打包。

2.4 编译安装包

打开 Inno Setup IDE,加载 setup.iss,点击 Compile。产物是 RDPHeartbeat_Setup.exe

验证一下:在干净的虚拟机或另一台电脑上运行安装包,确认:

  • 能正常安装
  • 程序能启动
  • 卸载后无残留(Inno Setup 的卸载程序在 {app}\unins000.exe,MSIX 会把它也一起包进去,没关系)

第三步:MSIX Packaging Tool —— 从安装包到 Store 格式

如果你打算上架 Microsoft Store,建议先完成 Partner Center 注册并保留应用名称(见第四步),因为 MPT 填写的 Package Name 和 Publisher 需要和 Store 保留的信息一致。如果只是自己测试,可以跳过注册,用任意名称即可。

3.1 准备环境

你需要:

  • MSIX Packaging Tool官方文档
  • 一台干净的环境(建议用虚拟机或单独一台电脑,因为 MPT 会监视整个安装过程,你系统上的其他程序也可能被捕捉到噪音)
  • 签名证书:测试阶段可以用自签名证书。在 PowerShell(管理员)中运行:
powershell
New-SelfSignedCertificate -Type Custom -Subject "CN=YourName" -KeyUsage DigitalSignature -FriendlyName "Your Cert" -CertStoreLocation "Cert:\CurrentUser\My" -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.3", "2.5.29.19={text}")

导出证书备用:

powershell
$password = ConvertTo-SecureString -String "YourPassword" -Force -AsPlainText
Export-PfxCertificate -Cert "Cert:\CurrentUser\My\<Thumbprint>" -FilePath "C:\cert.pfx" -Password $password

正式上架时,Microsoft Store 会用自己的证书重新签名,你不需要购买代码签名证书。但如果要分发到 Store 以外的地方(比如自己网站上提供 MSIX 下载),你需要买正经的代码签名证书。

3.2 运行 MSIX Packaging Tool

  1. 打开 MPT,选择 "Application package""Create a new package"
  2. 在"Select environment"这一步,保持默认,点 Next。
  3. 关键步骤:MPT 会要求你选择安装程序的路径。选中你刚生成的 RDPHeartbeat_Setup.exe
  4. MPT 开始监视系统,此时它会启动安装程序。正常走完安装流程,如果安装程序最后勾选了"Launch RDP Heartbeat",让它启动也无妨。
  5. 安装完成后,点击 "Next",MPT 停止监视。
  6. MPT 会列出它监测到的所有进程。把不属于你应用的勾掉(比如系统进程、杀毒软件的更新进程等)。通常只保留 RDPHeartbeat.exeunins000.exe 这类。
  7. 填写应用信息:
    • Package name:如果打算上架 Store,填 Partner Center 保留应用时分配的名称(如 PruneLab.RDPHeartbeat);如果只是自己测试,可以用任意名称,推荐格式为 YourName.YourApp
    • Package display name:应用的显示名称,如 RDP Heartbeat
    • Publisher display name:发布者名称,如 Prune Lab
    • Publisher:如果上架 Store,填 Partner Center 账号对应的 Publisher 证书主体(如 CN=21C32622-53AF-45A6-8AB1-B35BCD475CAD);如果自己测试,填你创建的自签名证书的 Subject(如 CN=YourName)。
    • Version:会自动从安装程序提取。
  8. 指定输出目录,生成 MSIX 包。

3.3 测试 MSIX 包

生成 .msix 文件后,先别急着改 manifest。直接双击安装,看能不能正常运行。如果用的是自签名证书,安装前需要先把证书导入到系统的"受信任人"存储区(见上一节导出证书的命令,用 Import-PfxCertificate 导入)。

第四步:提交到 Microsoft Store

4.1 注册与保留名称

  1. 注册 Microsoft Partner Center 开发者账号。
  2. 在 Partner Center 中保留应用名称(Products → New Product → 填写名称)。保留后你会得到 Package Name 和 Publisher 信息,这些需要和 MSIX 包中的 AppxManifest.xml 一致。

4.2 上传流程

  1. 进入你的应用 → SubmissionsNew submission
  2. 填写各字段:
    • Packages:上传 .msix 文件
    • Description:应用描述(支持多语言,建议至少中英文)
    • Screenshots:至少一张 1366×768 的应用截图
    • Store listing:图标、宣传图、功能列表
  3. 提交审核。首次审核通常需要 1-3 个工作日。

总结:这条路值得走吗?

如果你想靠一个 Python 小工具赚大钱,答案是"不值得"。用 WinUI 3 / .NET 重写,打包体验会好得多。

但如果你和我一样——手里已经有一个 Python 写的、自己经常用的小工具,想顺便把它分享给更多人,那这套流程虽然绕,但至少能走通

回顾整条链路:

环节工具产出
.py → .exePyInstaller单个 exe
.exe → 安装包Inno SetupSetup.exe
安装包 → MSIXMSIX Packaging Tool.msix
上架Partner CenterStore 上线

希望这篇文章能帮你少走一些弯路。如果你也把 Python 工具送上了 Store,欢迎到 RDP Heartbeat 的 GitHub 来交流。