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.py、setup.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
packaging1.2 处理 PyInstaller 的"隐式依赖"
PyInstaller 靠静态分析来检测依赖,但有些库(尤其是 pystray、PIL)它检测不到,需要手动声明:
# 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() 来兼容:
# 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:
# 先读 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:
#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:
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(管理员)中运行:
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}")导出证书备用:
$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
- 打开 MPT,选择 "Application package" → "Create a new package"。
- 在"Select environment"这一步,保持默认,点 Next。
- 关键步骤:MPT 会要求你选择安装程序的路径。选中你刚生成的
RDPHeartbeat_Setup.exe。 - MPT 开始监视系统,此时它会启动安装程序。正常走完安装流程,如果安装程序最后勾选了"Launch RDP Heartbeat",让它启动也无妨。
- 安装完成后,点击 "Next",MPT 停止监视。
- MPT 会列出它监测到的所有进程。把不属于你应用的勾掉(比如系统进程、杀毒软件的更新进程等)。通常只保留
RDPHeartbeat.exe、unins000.exe这类。 - 填写应用信息:
- 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:会自动从安装程序提取。
- Package name:如果打算上架 Store,填 Partner Center 保留应用时分配的名称(如
- 指定输出目录,生成 MSIX 包。
3.3 测试 MSIX 包
生成 .msix 文件后,先别急着改 manifest。直接双击安装,看能不能正常运行。如果用的是自签名证书,安装前需要先把证书导入到系统的"受信任人"存储区(见上一节导出证书的命令,用 Import-PfxCertificate 导入)。
第四步:提交到 Microsoft Store
4.1 注册与保留名称
- 注册 Microsoft Partner Center 开发者账号。
- 在 Partner Center 中保留应用名称(Products → New Product → 填写名称)。保留后你会得到 Package Name 和 Publisher 信息,这些需要和 MSIX 包中的
AppxManifest.xml一致。
4.2 上传流程
- 进入你的应用 → Submissions → New submission。
- 填写各字段:
- Packages:上传
.msix文件 - Description:应用描述(支持多语言,建议至少中英文)
- Screenshots:至少一张 1366×768 的应用截图
- Store listing:图标、宣传图、功能列表
- Packages:上传
- 提交审核。首次审核通常需要 1-3 个工作日。
总结:这条路值得走吗?
如果你想靠一个 Python 小工具赚大钱,答案是"不值得"。用 WinUI 3 / .NET 重写,打包体验会好得多。
但如果你和我一样——手里已经有一个 Python 写的、自己经常用的小工具,想顺便把它分享给更多人,那这套流程虽然绕,但至少能走通。
回顾整条链路:
| 环节 | 工具 | 产出 |
|---|---|---|
| .py → .exe | PyInstaller | 单个 exe |
| .exe → 安装包 | Inno Setup | Setup.exe |
| 安装包 → MSIX | MSIX Packaging Tool | .msix |
| 上架 | Partner Center | Store 上线 |
希望这篇文章能帮你少走一些弯路。如果你也把 Python 工具送上了 Store,欢迎到 RDP Heartbeat 的 GitHub 来交流。