用 YAML 文件配置 CI/CD 管道

下表列出了可以定义用来设置生成管道的各个 MSBuild 参数。

MSBuild 参数 描述
AppxPackageDir $(Build.ArtifactStagingDirectory)\AppxPackages 定义要存储生成的项目的文件夹。
AppxBundlePlatforms $(Build.BuildPlatform) 用于定义要包含在捆绑包中的平台。
AppxBundle 始终 使用 .msix/.appx 文件为指定的平台创建 .msixbundle/.appxbundle。
UapAppxPackageBuildMode StoreUpload 生成要旁加载的 .msixupload/.appxupload 文件和 _Test 文件夹。
UapAppxPackageBuildMode CI 仅生成 .msixupload/.appxupload 文件。
UapAppxPackageBuildMode SideloadOnly 仅生成要旁加载的 _Test 文件夹。
AppxPackageSigningEnabled true 启用包签名。
PackageCertificateThumbprint 证书指纹 此值必须与签名证书中的指纹匹配,或者为空字符串。
PackageCertificateKeyFile 路径 要使用的证书的路径。 此值是从安全文件元数据中检索的。
PackageCertificatePassword 密码 证书中私钥的密码。 建议将密码存储在 Azure Key Vault 中,并将密码链接到变量组。 可将变量传递到此参数。

在使用 MSBuild 命令行生成打包项目(就像在 Visual Studio 中使用向导生成项目一样)之前,生成过程可以通过编辑 Package.appxmanifest 文件中 Package 元素的 Version 属性,来对生成的 MSIX 包进行版本控制。 在 Azure Pipelines 中,可以使用某个表达式来实现此目的,该表达式可以设置每次生成都会递增的计数器变量。也可以通过某个 PowerShell 脚本来实现此目的,该脚本使用 .NET 中的 System.Xml.Linq.XDocument 类来更改该属性的值。

用于定义 MSIX 生成管道的示例 YAML 文件

pool: 
  vmImage: windows-2019
  
variables:
  buildPlatform: 'x86'
  buildConfiguration: 'release'
  major: 1
  minor: 0
  build: 0
  revision: $[counter('rev', 0)]
  
steps:
- powershell: |
     # Update appxmanifest. This must be done before the build.
     [xml]$manifest= get-content ".\Msix\Package.appxmanifest"
     $manifest.Package.Identity.Version = "$(major).$(minor).$(build).$(revision)"    
     $manifest.save("Msix/Package.appxmanifest")
  displayName: 'Version Package Manifest'
  
- task: MSBuild@1
  inputs:
    solution: Msix/Msix.wapproj
    platform: $(buildPlatform)
    configuration: $(buildConfiguration)
    msbuildArguments: '/p:OutputPath=NonPackagedApp
     /p:UapAppxPackageBuildMode=SideLoadOnly  /p:AppxBundle=Never /p:AppxPackageOutput=$(Build.ArtifactStagingDirectory)\MsixDesktopApp.msix /p:AppxPackageSigningEnabled=false'
  displayName: 'Package the App'
  
- task: DownloadSecureFile@1
  inputs:
    secureFile: 'certificate.pfx'
  displayName: 'Download Secure PFX File'
  
- script: '"C:\Program Files (x86)\Windows Kits\10\bin\10.0.17763.0\x86\signtool"
    sign /fd SHA256 /f $(Agent.TempDirectory)/certificate.pfx /p secret $(
    Build.ArtifactStagingDirectory)/MsixDesktopApp.msix'
  displayName: 'Sign MSIX Package'
  
- task: PublishBuildArtifacts@1
  displayName: 'Publish Artifact: drop'

下面是 YAMl 文件中定义的不同生成任务的细分:

配置包生成属性

以下定义设置生成组件的目录、平台,并定义是否生成捆绑包。

/p:AppxPackageDir="$(Build.ArtifactStagingDirectory)\AppxPackages\"
/p:UapAppxPackageBuildMode=SideLoadOnly
/p:AppxBundlePlatforms="$(Build.BuildPlatform)"
/p:AppxBundle=Never

配置包签名

若要为 MSIX(或 APPX)包签名,管道需要检索签名证书。 为此,请在 VSBuild 任务的前面添加 DownloadSecureFile 任务。 这样,就可以通过 signingCert 访问签名证书。

- task: DownloadSecureFile@1
  name: signingCert
  displayName: 'Download CA certificate'
  inputs:
    secureFile: '[Your_Pfx].pfx'

接下来,更新 MSBuild 任务以引用签名证书:

- task: MSBuild@1
  inputs:
    platform: 'x86'
    solution: '$(solution)'
    configuration: '$(buildConfiguration)'
    msbuildArgs: '/p:AppxBundlePlatforms="$(buildPlatform)" 
                  /p:AppxPackageDir="$(appxPackageDir)" 
                  /p:AppxBundle=Never 
                  p:UapAppxPackageBuildMode=SideLoadOnly 
                  /p:AppxPackageSigningEnabled=true
                  /p:PackageCertificateThumbprint="" 
                  /p:PackageCertificateKeyFile="$(signingCert.secureFilePath)"'

注意

PackageCertificateThumbprint 参数被有意设置为空字符串,目的是引以注意。 如果在项目中设置指纹,但该指纹与签名证书不匹配,则生成将会失败并出现错误:Certificate does not match supplied signing thumbprint

查看参数

使用 $() 语法定义的参数是在生成定义中定义的变量,在其他生成系统中将会更改。

若要查看所有预定义的变量,请参阅预定义的生成变量

配置“发布生成项目”任务

默认的 MSIX 管道不会保存生成的项目。 若要将发布功能添加到 YAML 定义,请添加以下任务。

- task: CopyFiles@2
  displayName: 'Copy Files to: $(build.artifactstagingdirectory)'
  inputs:
    SourceFolder: '$(system.defaultworkingdirectory)'
    Contents: '**\bin\$(BuildConfiguration)\**'
    TargetFolder: '$(build.artifactstagingdirectory)'

- task: PublishBuildArtifacts@1
  displayName: 'Publish Artifact: drop'
  inputs:
    PathtoPublish: '$(build.artifactstagingdirectory)'

可以在生成结果页的“项目”选项中查看生成的项目。

非 Store 分发内容的 AppInstaller 文件

如果在 Store 的外部分发应用程序,可以利用 AppInstaller 文件来安装和更新包

一个可在 \server\foo 上查找更新文件的 .appinstaller 文件

<?xml version="1.0" encoding="utf-8"?>
<AppInstaller xmlns="http://schemas.microsoft.com/appx/appinstaller/2018"
              Version="1.0.0.0"
              Uri="\\server\foo\MsixDesktopApp.appinstaller">
  <MainPackage Name="MyCompany.MySampleApp"
               Publisher="CN=MyCompany, O=MyCompany, L=Stockholm, S=N/A, C=Sweden"
               Version="1.0.0.0"
               Uri="\\server\foo\MsixDesktopApp.msix"
               ProcessorArchitecture="x86"/>
  <UpdateSettings>
    <OnLaunch HoursBetweenUpdateChecks="0" />
  </UpdateSettings>
</AppInstaller>

UpdateSettings 元素用于告知系统何时检查更新,以及是否强制用户更新。 可以在 bit.ly/2TGWnCR 上的文档中找到完整的架构参考,包括每个 Windows 10 版本支持的命名空间。

如果将 .appinstaller 文件添加到打包项目,并将其“打包操作”属性设置为“内容”,将“复制到输出目录”属性设置为“如果较新则复制”,则可以将另一个 PowerShell 任务添加到 YAML 文件,以便更新 root 和 MainPackage 元素的 Version 属性,并将更新的文件保存到暂存目录:

- powershell: |
  [Reflection.Assembly]::LoadWithPartialName("System.Xml.Linq")
  $doc = [System.Xml.Linq.XDocument]::Load(
    "$(Build.SourcesDirectory)/Msix/Package.appinstaller")
  $version = "$(major).$(minor).$(build).$(revision)"
  $doc.Root.Attribute("Version").Value = $version;
  $xName =
    [System.Xml.Linq.XName]
      "{http://schemas.microsoft.com/appx/appinstaller/2018}MainPackage"
  $doc.Root.Element($xName).Attribute("Version").Value = $version;
  $doc.Save("$(Build.ArtifactStagingDirectory)/MsixDesktopApp.appinstaller")
displayName: 'Version App Installer File'

然后,将 .appinstaller 文件分发给最终用户,并让他们双击此文件而不是 .msix 文件来安装打包的应用。

持续部署

应用安装程序文件本身是未编译的 XML 文件,生成后可按需编辑该文件。 这样,在将软件部署到多个环境,以及想要将生成管道与发布过程区分开来时,就可以轻松地使用该文件。

如果在 Azure 门户中使用“空作业”模板创建发布管道,并使用最近设置的生成管道作为要部署的项目的源,则可以将 PowerShell 任务添加到发布阶段,以动态更改 .appinstaller 文件中两个 Uri 属性的值,来反映应用发布到的位置。

一个用于修改 .appinstaller 文件中的 URI 的发布管道任务

- powershell: |
  [Reflection.Assembly]::LoadWithPartialName("System.Xml.Linq")
  $fileShare = "\\filesharestorageccount.file.core.windows.net\myfileshare\"
  $localFilePath =
    "$(System.DefaultWorkingDirectory)\_MsixDesktopApp\drop\MsixDesktopApp.appinstaller"
  $doc = [System.Xml.Linq.XDocument]::Load("$localFilePath")
  $doc.Root.Attribute("Uri").Value = [string]::Format('{0}{1}', $fileShare,
    'MsixDesktopApp.appinstaller')
  $xName =
    [System.Xml.Linq.XName]"{http://schemas.microsoft.com/appx/appinstaller/2018}MainPackage"
  $doc.Root.Element($xName).Attribute("Uri").Value = [string]::Format('{0}{1}',
    $fileShare, 'MsixDesktopApp.appx')
  $doc.Save("$localFilePath")
displayName: 'Modify URIs in App Installer File'

在上述任务中,URI 设置为 Azure 文件共享的 UNC 路径。 由于在安装和更新应用时,OS 将在此位置查找 MSIX 包,因此,我还将另一个命令行脚本添加到了发布管道,该脚本首先将云中的文件共享映射到生成代理上的本地 Z:\ 驱动器,然后使用 xcopy 命令将 .appinstaller 和 .msix 文件复制到该驱动器:

- script: |
  net use Z: \\filesharestorageccount.file.core.windows.net\myfileshare
    /u:AZURE\filesharestorageccount
    3PTYC+ociHIwNgCnyg7zsWoKBxRmkEc4Aew4FMzbpUl/
    dydo/3HVnl71XPe0uWxQcLddEUuq0fN8Ltcpc0LYeg==
  xcopy $(System.DefaultWorkingDirectory)\_MsixDesktopApp\drop Z:\ /Y
  displayName: 'Publish App Installer File and MSIX package'

如果你托管了自己的本地 Azure DevOps Server,当然可以将文件发布到自己的内部网络共享。

如果选择发布到 Web 服务器,可以通过在 YAML 文件中提供一些附加的参数,来告知 MSBuild 生成版本受控的 .appinstaller 文件和一个 HTML 页面,该页面包含下载链接,以及一些有关打包的应用的信息:

- task: MSBuild@1
  inputs:
    solution: Msix/Msix.wapproj
    platform: $(buildPlatform)
    configuration: $(buildConfiguration)
    msbuildArguments: '/p:OutputPath=NonPackagedApp /p:UapAppxPackageBuildMode=SideLoadOnly  /p:AppxBundle=Never /p:GenerateAppInstallerFile=True
/p:AppInstallerUri=http://yourwebsite.com/packages/ /p:AppInstallerCheckForUpdateFrequency=OnApplicationRun /p:AppInstallerUpdateFrequency=1 /p:AppxPackageDir=$(Build.ArtifactStagingDirectory)/'
  displayName: 'Package the App'

生成的 HTML 文件包含一个超链接,该链接以不区分浏览器的 ms-appinstaller 协议激活方案为前缀:

<a href="ms-appinstaller:?source=
  http://yourwebsite.com/packages/Msix_x86.appinstaller ">Install App</a>

如果设置发布管道用于将放置文件夹的内容发布到 Intranet 或任何其他网站,且 Web 服务器支持字节范围请求并已正确配置,则最终用户可以使用此链接直接安装应用,而无需先下载 MSIX 包。