发布到微软商店

把自己写的软件上架微软商店,是一种怎样的体验

本文记录了把 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)。

所以我们的思路是:

  1. tauri build --no-bundle 产出原始 release exe(前端已内嵌到二进制)
  2. 自己写一份 AppxManifest.xml
  3. 用 Windows SDK 的 MakeAppx.exeexe + 清单 + 图标打包成 .msix
  4. 上传到 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 CenterApps and gamesNew product

产品类型必须选「MSIX 或 PWA 应用」(MSIX or PWA app)。如果选了「EXE 或 MSI app」,上传 MSIX 时会被直接拒绝,且产品类型创建后无法更改,只能删掉重建。

输入产品名「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>

三个 {{...}} 占位符由打包脚本在运行时替换为实际值。

<Properties> 下的子元素顺序有 XML Schema 严格约束:必须是 DisplayName → PublisherDisplayName → Logo → Description。如果 Description 写在 Logo 前面,MakeAppx 会报 C00CEE3B(app manifest XML must be valid),但错误信息是乱码是(????????????????),很难定位。

这是本文踩过的第一个大坑——错误位置在 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

核心流程:

  1. 判定模式:检查 MSSTORE_PACKAGE_IDENTITY_NAMEMSSTORE_PUBLISHER 是否同时存在
    • 都存在 → 商店模式(不签名,产物给 Partner Center)
    • 任一缺失 → 本地测试模式(自签名,本地可安装)
  2. 读取 release exesrc-tauri/target/release/tydora.exe
  3. 暂存目录src-tauri/target/msix-staging/,把 exe + 图标 + 生成的 manifest 放进去
  4. MakeAppx pack:打包成 src-tauri/target/msix/Tydora_<version>_x64.msix
  5. 签名(仅本地模式或显式 -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=<你的发布者显示名>
.env 里的值不要加引号。Tydora 的 loadEnv 解析器只按第一个 = 分割并 trim 两边,不会剥引号。如果写成 MSSTORE_PACKAGE_IDENTITY_NAME="你的Name",引号会被当作值的一部分传给 MakeAppx,导致 Partner Center 校验 Identity 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
本地双击这个未签名 MSIX 会显示「发布者: 未知」并报错 0x800B010A(证书不受信任),这是正常的——商店模式的包本来就不该本地安装,它只应该上传到 Partner Center,由微软用微软自己的证书统一签名后分发给最终用户。

如果你想本地测试安装效果,可以用本地模式(注释掉 .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>)

这时候你需要:

  1. 检查 .env 里的 MSSTORE_PACKAGE_IDENTITY_NAME / MSSTORE_PUBLISHER 是否正确
  2. -SkipBuild 重新打包(秒级)
  3. 在 Packages 页先删除旧包,再上传新包

4.3 填写受限功能说明(runFullTrust)

因为 MSIX 清单里声明了 rescap:Capability Name="runFullTrust",Partner Center 会要求你在「Properties」或「Notes for certification」里解释用途。

这是我填写的内容

Tydora 是一款桌面 Markdown 编辑器,采用 Tauri v2 框架构建,核心工作模式为「本地优先」——所有笔记、文件、配置都保存在用户自己的设备上,不经过任何云端服务器。

runFullTrust 是 Tauri 桌面应用的标准要求,具体用于:

  1. 文件系统读写:用户通过「仓库」机制选择本地文件夹作为笔记存储位置,应用需要直接读取和写入该文件夹下的 .md 文件、图片附件等。
  2. 文件系统监听:当用户使用其他编辑器同时编辑笔记时,应用需要监听本地文件变更(通过 Rust notify crate),以实时同步链接索引。
  3. 系统文件对话框:使用 Tauri plugin-dialog 提供原生打开/保存对话框。
  4. 调用外部程序:用系统默认应用打开文件、在文件管理器中定位文件等。
  5. 发布网站功能:调用本地 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:

  1. 右上角齿轮 → Account settingsOrganizations(或 Entra ID / Azure AD)
  2. 关联你的 Microsoft Entra ID 租户(个人开发者可以用自己的微软账号对应的默认租户)
  3. 在 Entra ID 里注册一个应用:Azure Portal → App registrations → New registration
  4. 给这个应用赋予 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
关键设计:在.github/workflows/msstore.yml 的发布步骤用 if: vars.MSSTORE_PRODUCT_ID 门控。只要不配 MSSTORE_PRODUCT_ID 变量,工作流只会构建 MSIX 并附到 GitHub Release,不会尝试调用 msstore publish。

这意味着你可以现在就推 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 版本信息与技术栈

参考资料