你的项目有一个安装器生成器,用户可以选择任意 .msix 文件,生成包含该 msix 的自安装包。
但是生成器依赖 InstallerUI.zip(安装器界面的压缩包),这个文件需要:
- 手动构建 InstallationSolution
- 发布并打包成 zip
- 放到
InstallerGuard/Payload/目录
这个流程繁琐且容易出错。
生成器在运行时自动将自己打包成 InstallerUI.zip,实现真正的"自安装包生成器"。
InstallationSolution (生成器)
↓ 运行时
↓ 复制自己的文件
↓ 打包成 InstallerUI.zip
↓
InstallerGuard (自安装包)
↓ 嵌入 InstallerUI.zip
↓ 嵌入用户的 .msix
↓
最终产物:YourApp_Installer.exe
InstallationSolution/
├── Services/
│ └── SelfBuildService.cs ← 自举构建服务
├── Pages/
│ └── GeneratorPage.xaml.cs ← 使用自举服务
└── InstallationSolution.csproj ← 无需嵌入资源配置
InstallerGuard/
├── Program.cs
├── InstallerGuard.csproj
└── Payload/ ← 运行时动态生成
├── InstallerUI.zip ← 自动创建
└── YourApp.msix ← 用户选择
- 打开生成器
- 选择
.msix文件 - 点击"生成"
[1] SelfBuildService.GetOrBuildInstallerUIZipAsync()
├─ 获取当前应用目录
├─ 复制所有文件到临时目录(排除 GuardSource)
├─ 压缩成 InstallerUI.zip
└─ 缓存到 %TEMP%\InstallationSolution_SelfBuild\
[2] SelfBuildService.CopyToPayloadAsync(payloadDir)
└─ 复制 InstallerUI.zip 到 GuardSource/Payload/
[3] 复制用户的 .msix 到 Payload/
[4] dotnet publish InstallerGuard.csproj
└─ 生成 YourApp_Installer.exe
[5] 完成!
public static class SelfBuildService
{
private static string? _cachedZipPath;
public static async Task<string> GetOrBuildInstallerUIZipAsync()
{
// 检查缓存
if (_cachedZipPath != null && File.Exists(_cachedZipPath))
return _cachedZipPath;
// 获取当前应用目录
var installDir = Package.Current.InstalledLocation.Path;
var tempDir = Path.Combine(Path.GetTempPath(), "InstallationSolution_SelfBuild");
var uiDir = Path.Combine(tempDir, "InstallerUI");
// 复制文件(排除 GuardSource)
await Task.Run(() => CopyDirectory(installDir, uiDir, excludeDir: "GuardSource"));
// 压缩
var zipPath = Path.Combine(tempDir, "InstallerUI.zip");
ZipFile.CreateFromDirectory(Path.GetDirectoryName(uiDir)!, zipPath,
CompressionLevel.Optimal, includeBaseDirectory: true);
_cachedZipPath = zipPath;
return zipPath;
}
public static async Task CopyToPayloadAsync(string payloadDir)
{
var zipPath = await GetOrBuildInstallerUIZipAsync();
Directory.CreateDirectory(payloadDir);
File.Copy(zipPath, Path.Combine(payloadDir, "InstallerUI.zip"), overwrite: true);
}
}private string RunBuild()
{
var payloadDir = Path.Combine(guardSrc, "Payload");
Directory.CreateDirectory(payloadDir);
// 自动构建 InstallerUI.zip
SelfBuildService.CopyToPayloadAsync(payloadDir).GetAwaiter().GetResult();
// 复制用户的 msix
var msixFileName = Path.GetFileName(_msixPath!);
File.Copy(_msixPath!, Path.Combine(payloadDir, msixFileName), overwrite: true);
// 编译 Guard
// dotnet publish ...
}| 特性 | 传统方式 | 自举式构建 |
|---|---|---|
| 手动构建 zip | ✅ 需要 | ❌ 不需要 |
| 修改 UI 后 | 重新打包 | 自动生效 |
| 构建步骤 | 3-4 步 | 1 步 |
| 维护成本 | 高 | 低 |
| 出错风险 | 高 | 低 |
✅ 完全自动化 - 无需任何手动操作
✅ 始终最新 - 修改代码后立即生效
✅ 简化流程 - 只需构建一次项目
✅ 真正自举 - 生成器用自己生成安装包
✅ 缓存机制 - 首次构建后使用缓存,几乎无延迟
- 首次构建:1-2 秒(复制 + 压缩)
- 后续构建:< 100ms(使用缓存)
- zip 大小:约 10-20 MB(取决于应用大小)
# 1. 构建项目
dotnet build InstallationSolution/InstallationSolution.csproj -c Debug -p:Platform=x64
# 2. 运行生成器
# 直接启动应用,导航到生成器页面
# 3. 选择 msix 并生成
# 完全自动,无需其他操作# 构建 Release 版本
dotnet build InstallationSolution/InstallationSolution.csproj -c Release -p:Platform=x64
# 打包成 MSIX
# 使用 Visual Studio 或 dotnet publish确保 CopyDirectory 正确排除 GuardSource,避免递归包含。
生成的 InstallerUI.zip 必须包含 InstallerUI 父目录:
InstallerUI.zip
└── InstallerUI/
├── InstallerUI.exe
├── *.dll
└── Assets/
缓存在 %TEMP%\InstallationSolution_SelfBuild\,可以手动清理:
SelfBuildService.ClearCache();检查:
- InstallationSolution 是否正确构建
Package.Current.InstalledLocation.Path是否有效- 临时目录是否有写权限
检查:
- InstallerUI.zip 的目录结构是否正确
- 是否包含所有必需的 DLL
- GuardSource 是否被正确排除
优化:
- 检查是否使用了缓存
- 减小应用体积(移除不必要的文件)
- 使用
CompressionLevel.Fastest而非Optimal
SOLUTION_SUMMARY.md- 方案简要说明TESTING_GUIDE.md- 测试指南INSTALLER_GENERATOR_SOLUTIONS.md- 详细方案对比
通过自举式构建,你的安装器生成器现在可以:
- 🚀 自动化整个构建流程
- 🔄 实时反映代码变更
- 🎯 简化开发和维护
- ✨ 提供更好的用户体验
无需手动维护任何 zip 文件,一切都是自动的!