发布到微软商店
把自己写的软件上架微软商店,是一种怎样的体验
本文记录了把 Tydora(一款基于 Tauri v2 的桌面 Markdown 编辑器)发布到微软商店(Microsoft Store)的完整过程,包含 MSIX 打包、Partner Center 配置、GitHub Actions 自动发布的全流程,以及途中踩过的所有坑。
适用于:Tauri v2 桌面应用、希望上架微软商店的个人/小团队开发者。
本文最后更新:2026 年 8 月 8 日。
背景:为什么是 MSIX
Tydora 的默认分发方式是 NSIS(.exe)+ MSI(.msi)+ macOS/Linux 包,通过 GitHub Releases 发布。但微软商店只接受 MSIX 格式,且 Tauri CLI 2.11.2 在 Windows 平台的 --bundles 不支持 msix 目标(只有 msi/nsis)。
所以我们的思路是:
- 用
tauri build --no-bundle产出原始 release exe(前端已内嵌到二进制) - 自己写一份
AppxManifest.xml - 用 Windows SDK 的
MakeAppx.exe把exe+ 清单 + 图标打包成.msix - 上传到
Partner Center,由微软统一签名后上架
整个过程不依赖 Tauri 的 MSIX bundler,可控且能精确对齐商店身份。
全流程概览
┌──────────────────────────────────┐
│ 1. Partner Center 注册账号 │
│ + 保留产品名 + 拿商店身份 │
└────────────┬─────────────────────┘
│
▼
┌──────────────────────────────────┐
│ 2. 配置 .env 里的 MSSTORE_* │
│ Name / Publisher / DisplayName │
└────────────┬─────────────────────┘
│
▼
┌──────────────────────────────────┐
│ 3. npm run tauri build:msix │
│ 生成未签名 MSIX(商店模式) │
└────────────┬─────────────────────┘
│
▼
┌──────────────────────────────────┐
│ 4. 上传到 Partner Center │
│ 填隐私策略 / 支持信息 / 渠道 │
│ 首个版本必须手动上传 │
└────────────┬─────────────────────┘
│
▼
┌──────────────────────────────────┐
│ 5. 等待微软认证(1-3 工作日) │
│ 通过 → 上架 │
└────────────┬─────────────────────┘
│
▼
┌──────────────────────────────────┐
│ 6. 配置 GitHub Actions Secrets │
│ 后续版本走 CI 自动发布 │
└──────────────────────────────────┘
第 1 步:Partner Center 注册与保留产品名
1.1 注册开发者账号
打开 storedeveloper.microsoft.com 用微软账号登录,选「个人开发者」(个人号几分钟过审,免费;公司号需 DUNS 或营业执照,2-5 工作日)。
1.2 新建产品并保留名称
进入 Partner Center → Apps and games → New product。
输入产品名「Tydora」并保留(名字保留后 3 个月内必须发布,否则失效)。
1.3 拿到商店身份
保留成功后进入应用页面 → 顶部 Product identity(产品标识)页:
| 字段 | 示例值 | 用途 |
|---|---|---|
| Package/Identity/Name | <你的Name>(形如 1234567890.Tydora) |
MSIX 清单的 Identity Name |
| Package/Identity/Publisher | <你的Publisher>(形如 CN=XXXX-XXXX-...) |
MSIX 清单的 Identity Publisher |
| PublisherDisplayName | <你的发布者显示名> |
MSIX 清单的 Properties/PublisherDisplayName |
| Store product ID | <你的ProductID>(形如 9WZDNCRFXXXX) |
后续 CI 自动发布用 |
这三个值(Name / Publisher / PublisherDisplayName)必须和你 MSIX 清单里的值一字不差,否则 Partner Center 校验会打回。下面会讲怎么把这三个值注入清单。
第 2 步:MSIX 清单与打包脚本
2.1 AppxManifest.xml 模板
Tauri 不生成 MSIX 清单,我们自己写一份,放在 src-tauri/msix/AppxManifest.xml。关键点:
<Identity Name="{{PACKAGE_IDENTITY_NAME}}"
Publisher="{{PUBLISHER}}"
Version="{{VERSION}}"
ProcessorArchitecture="x64" />
<Properties>
<DisplayName>Tydora</DisplayName>
<PublisherDisplayName>{{PUBLISHER_DISPLAY_NAME}}</PublisherDisplayName>
<Logo>Assets\StoreLogo.png</Logo>
<Description>A modern Markdown editor built with Tauri</Description>
</Properties>
三个 {{...}} 占位符由打包脚本在运行时替换为实际值。
这是本文踩过的第一个大坑——错误位置在 Line 28, Column 15,但真正的问题在 line 25 的元素顺序。
2.2 文件关联(可选)
如果想让应用接管 .md 文件,在 <Applications> 节点加 <Extensions>:
<Extensions>
<uap:Extension Category="windows.fileTypeAssociation">
<uap:FileTypeAssociation Name="markdown">
<uap:SupportedFileTypes>
<uap:FileType>.md</uap:FileType>
<uap:FileType>.markdown</uap:FileType>
<uap:FileType>.mdx</uap:FileType>
</uap:SupportedFileTypes>
</uap:FileTypeAssociation>
</uap:Extension>
</Extensions>
2.3 能力声明(Capabilities)
<Capabilities>
<rescap:Capability Name="runFullTrust" /> <!-- 桌面应用必须 -->
<Capability Name="internetClient" /> <!-- 自动更新检查 -->
</Capabilities>
runFullTrust 是受限能力,提交时 Partner Center 会强制要求你解释用途(见第 4.3 节)。
2.4 打包脚本 build-msix.ps1
核心流程:
- 判定模式:检查
MSSTORE_PACKAGE_IDENTITY_NAME和MSSTORE_PUBLISHER是否同时存在- 都存在 → 商店模式(不签名,产物给 Partner Center)
- 任一缺失 → 本地测试模式(自签名,本地可安装)
- 读取 release exe:
src-tauri/target/release/tydora.exe - 暂存目录:
src-tauri/target/msix-staging/,把 exe + 图标 + 生成的 manifest 放进去 - MakeAppx pack:打包成
src-tauri/target/msix/Tydora_<version>_x64.msix - 签名(仅本地模式或显式
-Sign):用Sign-AppxPackage创建自签名证书并签名
版本号来源:从 VERSION 文件读取,转换为四段式 0.1.4.0(MSIX 要求 Major.Minor.Build.Revision 四段,空位补 0)。
2.5 接入 npm 脚本
scripts/run-tauri.mjs拦截 build:msix 子命令,转发给 PowerShell 脚本:
if (subCommand === 'build:msix') {
const args = process.argv.slice(3).join(' ');
spawnSync('pwsh', ['-File', 'scripts/build-msix.ps1', ...args], { stdio: 'inherit' });
}
在 package.json 里注册:
"scripts": {
"tauri": "node scripts/run-tauri.mjs"
}
这样就能用 npm run tauri build:msix 打包了。
第 3 步:本地打包
3.1 配置 .env
在项目根目录的 .env 里填入商店身份:
# 已有的 Tauri 签名密钥(用于 NSIS/MSI 自动更新签名,不是 MSIX 签名)
TAURI_SIGNING_PRIVATE_KEY=...
TAURI_SIGNING_PRIVATE_KEY_PASSWORD=...
# 微软商店身份(用于 MSIX 打包)
MSSTORE_PACKAGE_IDENTITY_NAME=<你的Name>
MSSTORE_PUBLISHER=<你的Publisher>
MSSTORE_PUBLISHER_DISPLAY_NAME=<你的发布者显示名>
这是本文踩过的第二个坑——无效的软件包标识名称: Tydora (应为: 你的Name),实际上是因为之前 .env 里写成了 = " 你的Name"(带前导空格和引号)。
3.2 执行打包
# 完整打包(含 Rust 编译,首次约 5-10 分钟)
npm run tauri build:msix
# 跳过编译,仅重新打包(已有 release exe 时,秒级)
npm run tauri build:msix -- -SkipBuild
# 跳过签名(商店模式默认就是不签名,加这个只是不显示签名警告)
npm run tauri build:msix -- -SkipBuild -NoSign
3.3 验证产物
打包成功后,控制台会输出:
Identity Name: <你的Name>
Publisher: <你的Publisher>
PublisherDisplayName: <你的发布者显示名>
Mode: Store (unsigned)
Package creation succeeded.
✓ MSIX 已生成: D:\code\Tydora\src-tauri\target\msix\Tydora_0.1.4.0_x64.msix
大小: 6.09 MB
如果你想本地测试安装效果,可以用本地模式(注释掉 .env 里的 MSSTORE_* 三行)打一个自签名的包,本地安装不会报错。
第 4 步:上传到 Partner Center
4.1 开始新提交
进入 Tydora 应用页面 → Start a submission(开始新提交)。需要填的几个块:
| 块 | 内容 |
|---|---|
| Packages | 上传 .msix 文件 |
| Pricing and availability | 免费 / 付费区域 |
| Properties | 应用分类、隐私策略 URL 等 |
| Age ratings | 年龄分级问卷 |
| Store listing | 商店页面文案、截图、图标 |
4.2 上传 Packages
在 Packages 块,把本地生成的 Tydora_0.1.4.0_x64.msix 拖进去。
上传完成后,Partner Center 会自动解析 Identity 信息并校验:
Package identifier: <你的Name>_0.1.4.0_x64__<PFN后缀>
Version: 0.1.4.0
Architecture: x64
如果和你在 Partner Center 注册的 Name/Publisher 一致,就显示绿色 ✓;否则会报类似:
无效的软件包标识名称: Tydora (应为: <你的Name>)
无效的软件包发布者名称: CN=Tydora (应为: <你的Publisher>)
这时候你需要:
- 检查
.env里的MSSTORE_PACKAGE_IDENTITY_NAME/MSSTORE_PUBLISHER是否正确 - 用
-SkipBuild重新打包(秒级) - 在 Packages 页先删除旧包,再上传新包
4.3 填写受限功能说明(runFullTrust)
因为 MSIX 清单里声明了 rescap:Capability Name="runFullTrust",Partner Center 会要求你在「Properties」或「Notes for certification」里解释用途。
这是我填写的内容:
Tydora 是一款桌面 Markdown 编辑器,采用 Tauri v2 框架构建,核心工作模式为「本地优先」——所有笔记、文件、配置都保存在用户自己的设备上,不经过任何云端服务器。
runFullTrust是 Tauri 桌面应用的标准要求,具体用于:
- 文件系统读写:用户通过「仓库」机制选择本地文件夹作为笔记存储位置,应用需要直接读取和写入该文件夹下的
.md文件、图片附件等。- 文件系统监听:当用户使用其他编辑器同时编辑笔记时,应用需要监听本地文件变更(通过 Rust
notifycrate),以实时同步链接索引。- 系统文件对话框:使用 Tauri
plugin-dialog提供原生打开/保存对话框。- 调用外部程序:用系统默认应用打开文件、在文件管理器中定位文件等。
- 发布网站功能:调用本地 Node.js CLI 将 Markdown 构建为静态网站。
应用不收集任何用户个人数据,所有操作均在本地完成;不包含任何第三方分析 SDK 或广告 SDK。
runFullTrust仅用于上述桌面应用场景,不会绕过系统安全机制。
4.4 隐私策略 URL
微软商店强制要求所有应用提供隐私策略 URL。我在文档站专门加了一个页面:隐私策略 · Tydora
隐私策略内容要点(按微软商店审查清单):
- ✅ 明确「不收集个人数据」的本地优先声明
- ✅ 哪些「仅本地」的非个人数据会写入 localStorage
- ✅ 自动更新机制说明(仅 HTTPS 版本检查,无用户数据)
- ✅ MSIX 微软商店版本的 Microsoft 端遥测说明
- ✅ 不使用第三方分析/广告 SDK
- ✅ 数据安全、保留与删除方式
- ✅ 儿童隐私、用户权利、策略更新
4.5 商店页面素材
Store listing 块需要准备:
- 至少 1 张截图(推荐 1920×1080,PNG)
- 应用图标(512×512)
- 简短描述(≤ 200 字符)
- 详细描述(≤ 10000 字符)
- 发行说明(可选)
4.6 提交审核
填完所有块后,点 Submit for certification。状态变化:
Pending → Pre-processing → Certification → Publishing → In the Store
整个认证流程通常 1-3 个工作日。如果被拒,Partner Center 会告诉你具体原因(比如隐私策略 URL 不可访问、runFullTrust 说明不充分等),修改后重新提交即可。
第 5 步:首个版本过审后的 CI 自动发布
微软硬性规定:首个版本必须手动上传,之后版本才能通过 API 自动发布。
5.1 关联 Microsoft Entra ID
在 Partner Center:
- 右上角齿轮 → Account settings → Organizations(或 Entra ID / Azure AD)
- 关联你的 Microsoft Entra ID 租户(个人开发者可以用自己的微软账号对应的默认租户)
- 在 Entra ID 里注册一个应用:Azure Portal → App registrations → New registration
- 给这个应用赋予 Partner Center 的 Manager 角色
5.2 拿到 4 个 Secrets 值
| Secret Name | 在哪里拿 |
|---|---|
AZURE_AD_TENANT_ID |
Entra ID → 应用注册 → 概览 → 目录(租户) ID |
AZURE_AD_APPLICATION_CLIENT_ID |
同上 → 应用程序(客户端) ID |
AZURE_AD_APPLICATION_SECRET |
同上 → 证书和密码 → 新建客户端密码 → 生成的值**(仅显示一次!)** |
SELLER_ID |
Partner Center → 账户设置 → Legal Info → Seller ID |
5.3 配置 GitHub 仓库
进入仓库 Settings → Secrets and variables → Actions:
🔒 Secrets(敏感信息):
| Name | Value |
|---|---|
AZURE_AD_TENANT_ID |
上面的租户 ID |
AZURE_AD_APPLICATION_CLIENT_ID |
上面的客户端 ID |
AZURE_AD_APPLICATION_SECRET |
上面的客户端密码 |
SELLER_ID |
上面的 Seller ID |
📋 Variables(非敏感,控制开关):
| Name | Value |
|---|---|
MSSTORE_PACKAGE_IDENTITY_NAME |
<你的Name>(如 1234567890.Tydora) |
MSSTORE_PUBLISHER |
<你的Publisher>(如 CN=XXXX-XXXX-...) |
MSSTORE_PUBLISHER_DISPLAY_NAME |
<你的发布者显示名> |
MSSTORE_PRODUCT_ID |
Partner Center 概览页的 Store product ID(如 9WZDNCRFXXXX) |
这意味着你可以现在就推 tag 验证 MSIX 打包是否成功,等手动上架后再开启自动发布。
5.4 工作流文件
完整工作流 .github/workflows/msstore.yml,核心步骤:
on:
push:
tags: ['v*']
workflow_dispatch:
jobs:
msix:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
- name: Rust cache
uses: swatinem/rust-cache@v2
- name: Install frontend dependencies
run: npm ci
- name: Build MSIX
shell: pwsh
env:
MSSTORE_PACKAGE_IDENTITY_NAME: ${{ vars.MSSTORE_PACKAGE_IDENTITY_NAME }}
MSSTORE_PUBLISHER: ${{ vars.MSSTORE_PUBLISHER }}
run: ./scripts/build-msix.ps1
- name: Upload MSIX artifact
uses: actions/upload-artifact@v4
with:
name: tydora-msix
path: src-tauri/target/msix/*.msix
- name: Attach MSIX to GitHub Release
if: startsWith(github.ref, 'refs/tags/v')
uses: softprops/action-gh-release@v2
with:
files: src-tauri/target/msix/*.msix
# 以下步骤仅当配置了 MSSTORE_PRODUCT_ID 时执行
- name: Setup Microsoft Store CLI
if: vars.MSSTORE_PRODUCT_ID
uses: microsoft/microsoft-store-apppublisher@v1.1
- name: Reconfigure store credentials
if: vars.MSSTORE_PRODUCT_ID
run: |
msstore reconfigure `
--tenantId ${{ secrets.AZURE_AD_TENANT_ID }} `
--sellerId ${{ secrets.SELLER_ID }} `
--clientId ${{ secrets.AZURE_AD_APPLICATION_CLIENT_ID }} `
--clientSecret ${{ secrets.AZURE_AD_APPLICATION_SECRET }}
- name: Publish package to Store
if: vars.MSSTORE_PRODUCT_ID
run: |
$msix = Get-ChildItem src-tauri/target/msix/*.msix | Select-Object -First 1
msstore publish $msix.FullName -id ${{ vars.MSSTORE_PRODUCT_ID }}
5.5 触发发布
# 打 tag
git tag v0.1.5
git push origin v0.1.5
推送后 GitHub Actions 自动跑:构建 → 打包 → 上传 Release → (配了变量时)发布到商店。整个过程约 10-15 分钟。
踩过的坑汇总
坑 1:MakeAppx 报 C00CEE3B 且错误信息是乱码
现象:
MakeAppx : error: Error info: error C00CEE3B: App manifest validation error:
The app manifest XML must be valid: Line 28, Column 15, Reason: ???????????????????????????
根因:<Properties> 下子元素顺序不符合 XML Schema。正确顺序必须是 DisplayName → PublisherDisplayName → Logo → Description,我原来把 Description 写在 Logo 前面了。
修复:调整AppxManifest.xml的元素顺序。
坑 2:中文变乱码导致闭合标签被吃掉
现象:MakeAppx 仍然报 C00CEE3B,但这次是真正的 XML 解析错误。
根因:PowerShell 5.1 的 Get-Content -Raw 在文件无 BOM 时,会按系统默认编码(中文 Windows = GBK/936)解码 UTF-8 字节。具体破坏点:
- 「左瑞宁」的「宁」UTF-8 编码是
E5 AE 81 - 紧跟其后的
<是3C - GBK 把
81 3C当作一个合法双字节汉字 → 吃掉了< - 导致
</PublisherDisplayName>变成?/PublisherDisplayName>,XML 闭合标签失效
修复:在 build-msix.ps1 里把 Get-Content -Raw 改成 [System.IO.File]::ReadAllText(path, [System.Text.Encoding]::UTF8),并给 AppxManifest.xml 加 UTF-8 BOM。
坑 3:Identity Name 不匹配
现象:
无效的软件包标识名称: Tydora (应为: <你的Name>)
无效的软件包发布者名称: CN=Tydora (应为: <你的Publisher>)
根因:.env 里的值带了引号和前导空格:
MSSTORE_PACKAGE_IDENTITY_NAME = " <你的Name>"
Tydora 的 loadEnv 解析器不剥引号,导致实际传给 MakeAppx 的 Name 是带引号带空格的字符串,触发 Partner Center 校验失败。
修复:.env 里去掉引号和 = 两边的空格:
MSSTORE_PACKAGE_IDENTITY_NAME=<你的Name>
坑 4:PublisherDisplayName 不匹配
现象:
应用清单中的 PublisherDisplayName 元素是 Tydora,该值与发布者显示名称: <你的发布者显示名> 不匹配
根因:Partner Center 注册时填的发布者显示名(个人账号默认用真实姓名),但 manifest 模板里硬编码了「Tydora」。
修复:把 PublisherDisplayName 改成占位符 {{PUBLISHER_DISPLAY_NAME}},由脚本从 .env 注入。
坑 5:本地双击未签名 MSIX 报 0x800B010A
现象:商店模式打的包,本地双击显示「发布者: 未知」,安装按钮灰色,报错 0x800B010A。
根因:不是坑,是预期行为。商店模式的包本来就不签名,只应该上传到 Partner Center,由微软统一签名后分发。
解决:如果只是想本地测试安装效果,用本地模式(注释掉 .env 里的 MSSTORE_* 三行)打一个自签名的包即可。
常用命令速查
# 完整打包(含 Rust 编译)
npm run tauri build:msix
# 跳过编译,仅重新打包
npm run tauri build:msix -- -SkipBuild
# 跳过签名
npm run tauri build:msix -- -SkipBuild -NoSign
# 强制在商店模式下也签名(用于本地安装商店身份的包做视觉验证)
npm run tauri build:msix -- -SkipBuild -Sign
# 本地测试模式(不配 MSSTORE_* 变量)
Remove-Item Env:MSSTORE_PACKAGE_IDENTITY_NAME, Env:MSSTORE_PUBLISHER, Env:MSSTORE_PUBLISHER_DISPLAY_NAME -ErrorAction SilentlyContinue
npm run tauri build:msix -- -SkipBuild
相关文档
- [[01-开始使用/隐私策略]] —— 微软商店要求的隐私策略页
- [[08-高级功能/01-发布网站]] —— Tydora 内置的静态网站发布功能
- [[08-高级功能/04-自动更新配置]] —— GitHub Releases 自动更新签名配置
- [[01-开始使用/02-关于]] —— Tydora 版本信息与技术栈