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