点击关注我的Telegram群组和微信公众号

MENU

使用 Azure Pipelines 为 C# .NET MSIX 应用构建 CI/CD 管道

2023 年 02 月 28 日 • 阅读: 1219 • 技术,云服务,教程

前言

去年年底,Snap Genshin 项目被归档并被 Snap Hutao 替代。在新一代的应用中,项目使用了更新式的技术,使用winui3 替代了原始的 WPF,并最终打包为 msix 安装包,使其上架到了微软商店。由于商店的审核机制,应用不能再像过去自分发模式一样随时推送更新,因此构建为开发人员测试的 CI/CD 通道版本变得更加重要了。为此,我使用 Azure DevOps 中的 Pipelines 通过其和 GitHub 的集成创建了 CI/CD 管道。

创建 DevOps 帐号

  • 来到微软 Azure Pipelines 主页,点击 Start free with GitHub 来通过登录你的微软帐号来进入 Azure DevOps 面板并绑定 GitHub 帐号。
  • 如果你是为团队或公司创建这个环境,建议使用团队微软帐号并在绑定 GitHub 过程中授权 Azure DevOps 使用团队库数据

    • 同时,你需要为你的团队创建一个 DevOps 团队名称
    • 对于需要免费额度的开源项目而言,需要添加这个表格或发送邮件给azpipelines-freetier@microsoft.com来申请免费额度
  • 你需要等待1-2天等待微软官方为你的组织添加免费额度以运行 CI/CD 管道。在此之前,你仍然可以继续构建你的 CI/CD 管道,只是它们会因为额度不足而无法实际运行

创建项目

  • 进入 DevOps 面板,进入组织页面,点击右侧的 New Project 来创建一个新的项目
  • 创建成功后选择 GitHub 作为你的代码库

    GitHub as repo

  • 在选择代码库时,在筛选中选择 All repositories 后找到你需要创建 CI 管道的项目并确认
    Select GitHub Repository
  • 之后页面会跳转到 GitHub 授权页面,在这里同意 Azure DevOps 编辑该库
  • 完成授权后,你会被跳转到一个 YAML 文件编辑器上,这个就是 Azure Pipelines 的配置文件

构建管道

兼容性问题

微软官方文档中,他们推荐了由微软官方维护的 Azure Pipelines 插件,但该插件在写本文时对 VS 2022 存在兼容性问题。所以在构建 Snap Hutao 项目时,我通过 PowerShell 以命令行的方式打包了应用程序。因此,你也可以通过本文了解 MSIX 打包的细节流程。

准备工作

在一切开始之前,我们先为管道环境设置好一些环境变量

  • build_date 通过调用环境中的 PowerShell 返回了一个形如 2023.2.28的时间格式,我们将这样的日期作为 CI/CD 测试通道中软件的版本号
variables:
  DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
  solution: '$(Build.SourcesDirectory)/src/Snap.Hutao/Snap.Hutao.sln'
  project: $(Build.SourcesDirectory)/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj'
  buildPlatform: 'x64'
  buildConfiguration: 'Release'
  build_date: $[ format('{0:yyyy}.{0:M}.{0:d}', pipeline.startTime) ]

我们将 CI/CD 环境设置为 Windows Server 2022,该环境下已包含 Visual Studio 2022 企业版

pool:
  vmImage: 'windows-2022'

编译二进制文件

  • 接下来我们为 .NET Framework 程序创建打包环境
 - task: UseDotNet@2
  displayName: Install dotNet
  inputs:
    packageType: 'sdk'
    version: '7.x'
    includePreviewVersions: true

 - task: NuGetToolInstaller@1
  name: 'NuGetToolInstaller'
  displayName: 'NuGet Installer'

 - task: NuGetCommand@2
  displayName: NuGet restore
  inputs:
    command: 'restore'
    restoreSolution: '$(solution)'
    feedsToUse: 'select'

随后,我们就可以使用 Visual Studio 2022 中包含的 MSBuild 来编译代码了

  • 在这一步,大多数情况下我们都只需要msbuildLocationMethod设置为version并将msbuildVersion设置为lastest或其它指定版本。但是,由于对 VS2022 的兼容性问题,我们不得不使用 locationMSBuild.exe 的路径作为打包参数
 - task: MsixPackaging@1
  displayName: Build binary package
  inputs:
    outputPath: '$(Build.ArtifactStagingDirectory)/'
    solution: '$(solution)'
    clean: false
    generateBundle: false
    buildConfiguration: 'Release'
    buildPlatform: 'x64'
    updateAppVersion: false
    appPackageDistributionMode: 'SideloadOnly'
    msbuildLocationMethod: 'location'
    msbuildLocation: 'C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Msbuild\Current\Bin\MSBuild.exe'

MSIX 打包

在打包之前,我们要准备好打包后的软件包版本号。在已编译的二进制程序中,软件版本号是跟随代码中的设置的,但我们是不可能会为了一个 CI/CD 的临时版本而修改它的,所以我们需要让 CI/CD 管道为我们准备这个临时的版本号。在 CI/CD 管道最初运行时,我们已经通过环境变量获得了一个格式形为2023.2.28这样的日期,但缺少第四位的 revision number。非常巧合的是, Azure Pipelines 会为每一个 CI/CD 任务创建一个版本号,其格式形如20230228.1,其实小数点后的数字代表该日期下的第几次任务,这个数字就非常适合用于 revision number,因为它可以保证版本号一定是有序递增的。

在 Azure DevOps Market 中我们可以添加一款名为 GetRevision 的插件。使用它,我们就可以添加下面这段脚本将 revision number 设置到 rev_number 变量上。

 - task: GetRevision@1
  displayName: get Pipelines revision number
  inputs:
    VariableName: 'rev_number'

除非额外指定参数,编译后的程序会被储存在项目目录下的\bin\x64\Release\net7.0-windows10.0.18362.0\win10-x64\目录中

Windows 系统会通过读取 MSIX 应用的 Identity Name 来作为应用的唯一标识,因此我们需要更改一些信息以让系统和我们自己来区分 CI/CD 版本应用和正式版应用。在这里,我们使用 MagicChunks来修改管理这些属性的AppxManifest.xml文件

  • transformations中包含了我们覆盖的属性数值
  • Package/Identity/@Name就是系统用于分辨软件包唯一标识的名称
  • Package/Identity/@Publisher是关键的开发者信息,它的值必须和你MSIX应用包签名完全一致,否则会产生签名错误

    • 如果你的代码签名证书是购买来的,有时候它会包含多重信息,比如公司名称、组织名称以及邮箱。这种情况下,一些标点符号就会和 xml 格式发生冲突。因此,我们需要对这些信息中有冲突的符号进行转义,并且需要包含这些全部信息,而不仅仅是CN
  • "Package/Identity/@Version": "$(build_date).$(rev_number)"这一行将我们之前准备好的应用包版本写入属性
 - task: MagicChunks@2
  inputs:
    sourcePath: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.18362.0\win10-x64\AppxManifest.xml'
    fileType: 'Xml'
    targetPathType: 'source'
    transformationType: 'json'
    transformations: |
      {
        "Package/Identity/@Name": "7f0db578-026f-4e0b-a75b-d5d06bb0a74c",
        "Package/Identity/@Publisher": "CN=DGP Studio CI",
        "Package/Identity/@Version": "$(build_date).$(rev_number)",
        "Package/Properties/DisplayName": "胡桃 Alpha",
        "Package/Properties/PublisherDisplayName":"DGP Studio CI",
        "Package/Applications/Application/uap:VisualElements/@DisplayName": "胡桃 Alpha"
      }

接下来,我们将应用程序中使用的静态资源也加入二进制程序目录下。这些资源在代码中是一种外部调用,所以在二进制编译过程中,它们是不会被自动添加到编译后的目录中的。

 - task: CmdLine@2
  displayName: Create resources folder
  inputs:
    script: |
      mkdir Assets
      
      mkdir Resource
    workingDirectory: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.18362.0\win10-x64'
      

 - task: CopyFiles@2
  displayName: Copy Assets Folder
  inputs:
    SourceFolder: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\Assets'
    Contents: '**'
    TargetFolder: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.18362.0\win10-x64\Assets'

 - task: CopyFiles@2
  displayName: Copy Resource Folder
  inputs:
    SourceFolder: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\Resource'
    Contents: '**'
    TargetFolder: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.18362.0\win10-x64\Resource'

一切就绪,我们现在可以通过makeappx将二进制文件打包为 MSIX 应用包了

  • 其中,$(Build.ArtifactStagingDirectory)是 Azure Pipelines 默认的导出资源目录
 - task: CmdLine@2
  displayName: Build MSIX
  inputs:
    script: '"C:\Program Files (x86)\Windows Kits\10\bin\10.0.22000.0\x64\makeappx.exe" pack /d $(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.18362.0\win10-x64 /p $(Build.ArtifactStagingDirectory)/Snap.Hutao.Alpha-$(build_date).$(rev_number).msix'

签名 MSIX 应用包

MSIX 应用包要求所有应用都需要代码签名,否则应用无法安装。所以接下来,我们将要为已打包的 MSIX 包签名。

点击 Pipelines 菜单中的 Library,进入 Secure files,点击按钮上传设有密码的 pfx 证书文件
Upload secure files to libary

回到 Pipelins YAML 文件编辑器,我们需要将 pfx 证书的密码作为变量储存在这个管道任务中。点击右上角的 Variables,点击添加。在添加变量的窗口中,为变量设置一个名称,并在Value中填写密码,最后勾选 Keep this value secret
pfx password variable

现在我们就可以使用微软官方的MsixSigning插件来签名 Msix 应用包

  • 其中certificate为你在 Secure Files中上传的证书文件名
  • passwordVariable为你储存 pfx 证书密码的变量名称
- task: MsixSigning@1
  name: signMsix
  displayName: Sign MSIX package
  inputs:
    package: '$(Build.ArtifactStagingDirectory)/Snap.Hutao.Alpha-$(build_date).$(rev_number).msix'
    certificate: 'DGP_Studio_CI.pfx'
    passwordVariable: 'pw'

如果你希望使用 PowerShell 或 CMD 来签名可以参考以下的命令

SignTool sign /fd SHA256 /a /f C:\Users\i\Documents\GitHub\Snap.Hutao.Output\Snap.Hutao_TemporaryKey.pfx /p defaultpassword C:\Users\i\Documents\GitHub\Snap.Hutao.Output\Snap.Hutao.signed.msix

发布 CI/CD 应用

在 Snap Hutao 项目中,我决定将侧载包和 CI/CD 侧载证书一起以 pre-release 的方式在 GitHub 主项目库中发布。其中,

  • Download Root CA任务将储存在 Secure Files中的侧载证书下载到 CI/CD 环境中,并将该文件以cerFile名称储存在环境变量中。在 GitHub Release 发布流程中,我们可以通过$(cerFile.secureFilePath)来引用该文件
  • gitHubConnection是集成的 GitHub 帐号
- task: DownloadSecureFile@1
  name: cerFile
  displayName: Download Root CA
  inputs:
    secureFile: 'Snap.Hutao.CI.cer'

- task: GitHubRelease@1
  inputs:
    gitHubConnection: 'github.com_Masterain'
    repositoryName: 'DGP-Studio/Snap.Hutao'
    action: 'create'
    target: '$(Build.SourceVersion)'
    tagSource: 'userSpecifiedTag'
    tag: '$(build_date).$(rev_number)'
    title: '$(build_date).$(rev_number)'
    releaseNotesSource: 'inline'
    releaseNotesInline: |
      ## 普通用户请勿下载
      该版本是由 CI 程序自动打包生成的 `Alpha` 测试版本,**仅供开发者测试使用**

      普通用户请[点击这里](https://github.com/DGP-Studio/Snap.Hutao/releases/latest/)下载最新的稳定版本

    assets: |
      $(Build.ArtifactStagingDirectory)/*
      $(cerFile.secureFilePath)
    isPreRelease: true
    changeLogCompareToRelease: 'lastFullRelease'
    changeLogType: 'commitBased'

成果

点击 YAML 编辑器右上角的保存,你的 CI/CD 配置文件就会被添加到 GitHub 代码库中,并立刻执行一次。在 Azure DevOps 中你可以看到 Pipelines 的全部任务记录。
Azure Pipelines history

在 GitHub Release 中你可以看到已发布的 CI/CD 版本
GitHub release

在对应的 Commit 中,你也可以看到对应 CI/CD 任务的信息
Commit information

返回文章列表 文章二维码 打赏
本页链接的二维码
打赏二维码