Упаковка десктопного инструмента на 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 в качестве примера, но методы и подходы применимы к любому Windows-инструменту, написанному на Python + Tkinter (или PyQt, wxPython и т.д.).
Замечание: теоретически можно конвертировать exe, сгенерированный PyInstaller, напрямую через MSIX Packaging Tool, минуя Inno Setup. Мы выбрали сначала Inno Setup в основном для удобства самостоятельной дистрибуции (например, разместить Setup.exe на GitHub Releases для скачивания). Это может быть не теоретически оптимальным решением, но этот путь мы реально прошли.
Исходный код для справки: Полный код проекта доступен на [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 — за «публикацию в Store».
Давайте пройдём шаг за шагом.
Шаг 1: 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. Если всё пройдёт успешно, вы получите RDPHeartbeat.exe в dist/.
Можно дважды кликнуть, чтобы убедиться, что всё работает.
Шаг 2: 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. Не конфликтуйте с другим ПО. IDE Inno Setup может сгенерировать GUID одним кликом.
- Не нужно писать в реестр: конвертация MSIX автоматически обрабатывает записи в реестр при установке, но нашему приложению они и не нужны.
2.3 Автоматическая синхронизация номера версии
Ваш номер версии может быть определён в коде Python (version.py), а также есть копия в setup.iss. Ручная синхронизация рано или поздно приведёт к ошибкам. Добавьте автоматический патч в build_release.py:
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 Компиляция установщика
Откройте IDE Inno Setup, загрузите setup.iss и нажмите Compile. Результат — RDPHeartbeat_Setup.exe.
Проверка: Запустите установщик на чистой ВМ или другом компьютере и убедитесь:
- Установка проходит корректно
- Программа запускается
- После удаления нет остатков (деинсталлятор Inno Setup находится в
{app}\unins000.exe, MSIX включит его в пакет — это нормально)
Шаг 3: MSIX Packaging Tool — От установщика к формату Store
Если вы планируете публиковать в Microsoft Store, рекомендуется сначала зарегистрироваться в Partner Center и зарезервировать имя приложения (см. Шаг 4), так как Package Name и Publisher, заполняемые в MPT, должны соответствовать информации, зарезервированной в 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 используйте Subject сертификата Publisher из вашего аккаунта Partner Center (напр.:
CN=21C32622-53AF-45A6-8AB1-B35BCD475CAD). Для локального тестирования используйте Subject вашего самоподписанного сертификата (напр.:CN=YourName). - Version: Извлекается автоматически из установщика.
- Package name: Для Store используйте имя, назначенное при резервировании в Partner Center (напр.:
- Укажите выходной каталог и сгенерируйте пакет MSIX.
3.3 Тестирование пакета MSIX
После создания файла .msix не спешите редактировать манифест. Дважды кликните для установки и проверьте, всё ли работает корректно. Если используется самоподписанный сертификат, его нужно сначала импортировать в хранилище «Доверенные лица» системы (см. команду экспорта в предыдущем разделе, используйте Import-PfxCertificate).
Шаг 4: Отправка в Microsoft Store
4.1 Регистрация и резервирование имени
- Зарегистрируйте аккаунт разработчика в Microsoft Partner Center.
- В Partner Center зарезервируйте имя приложения (Products → New Product → введите имя). После резервирования вы получите Package Name и информацию Publisher, которые должны совпадать с
AppxManifest.xmlв вашем пакете MSIX.
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, добро пожаловать для обмена опытом на GitHub RDP Heartbeat.