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 來交流。