设置 CI/CD 管道以自动完成 MSIX 生成和部署Set up a CI/CD pipeline to automate your MSIX builds and deployments

可以使用 Azure Pipelines 为 MSIX 项目创建自动化生成。You can use Azure Pipelines to create automated builds for your MSIX project. 本文介绍如何在 Azure DevOps 中执行此操作。This article takes a look at how to do so in Azure DevOps. 此外,介绍如何使用命令行执行这些任务,以便可与任何其他生成系统相集成。We’ll also show you how to perform these tasks by using the command line so that you can integrate with any other build system.

创建新的 Azure 管道Create a new Azure Pipeline

首先注册 Azure Pipelines(如果尚未这样做)。Begin by signing up for Azure Pipelines if you haven't done so already.

接下来,创建一个可用于生成源代码的管道。Next, create a pipeline that you can use to build your source code. 有关生成一个用于生成 GitHub 存储库的管道的教程,请参阅创建第一个管道For a tutorial about building a pipeline to build a GitHub repository, see Create your first pipeline. Azure Pipelines 支持此文中列出的存储库类型。Azure Pipelines supports the repository types listed in this article.

若要设置实际生成管道,请浏览到 Azure DevOps 门户 (dev.azure.com/<organization>),并创建一个新项目。To set up the actual build pipeline, you browse to the Azure DevOps portal at dev.azure.com/<organization> and create a new project. 如果没有帐户,可以免费创建一个。If you don’t have an account, you can create one for free. 登录并创建项目后,可将源代码推送到系统设置的 Git 存储库 (https://<organization>@dev.azure.com/<组织>/<project>/_git/<project>),或者使用任何其他提供程序,例如 GitHub。Once you’ve signed in and created a project, you can either push the source code to the Git repository that’s set up for you at https://<organization>@dev.azure.com/<organization>/<project>/_git/<project>, or use any other provider, such as GitHub. 在门户中依次单击“管道”按钮和“新建管道”来创建新管道时,需要选择存储库的位置 。You’ll get to choose the location of your repository when you create a new pipeline in the portal by clicking first on the Pipelines button and then on New Pipeline.

在接下来的“配置”屏幕上,应选择“现有 Azure Pipelines YAML 文件”选项,然后选择存储库中签入的 YAML 文件的路径 。On the Configure screen that comes next, you should select the Existing Azure Pipelines YAML file option and select the path to the checked-in YAML file in your repository.

将项目证书添加到安全文件库Add your project certificate to the Secure files library

备注

应尽量避免将证书提交到存储库,Git 默认会忽略这些证书。You should avoid submitting certificates to your repo if at all possible, and git ignores them by default. 为了管理敏感文件(例如证书)的安全处理,Azure DevOps 支持安全文件功能。To manage the safe handling of sensitive files like certificates, Azure DevOps supports the secure files feature.

若要为自动生成上传证书:To upload a certificate for your automated build:

  1. 在 Azure Pipelines 中,展开导航窗格中的“管道”并单击“库”。 In Azure Pipelines, expand Pipelines in the navigation pane and click Library.
  2. 依次单击“安全文件”选项卡、“+ 安全文件”。 Click the Secure files tab and then click + Secure file.
  3. 浏览到证书文件并单击“确定”。Browse to the certificate file and click OK.
  4. 上传证书后,选择该证书以查看其属性。After you upload the certificate, select it to view its properties. 在“管道权限”下,将“授权在所有管道中使用”切换开关置于启用状态。 Under Pipeline permissions, enable the Authorize for use in all pipelines toggle.
  5. 如果证书中的私钥包含密码,则我们建议将密码存储在 Azure Key Vault 中,然后将密码链接到某个变量组If the private key in the certificate has a password, we recommend that you store your password in Azure Key Vault and then link the password to a variable group. 可以使用该变量来从管道访问密码。You can use the variable to access the password from the pipeline. 请注意,只有私钥支持密码;当前不支持使用本身受密码保护的证书文件。Note that a password is only supported for the private key; using a certificate file that is itself password-protected is not currently supported.

备注

从 Visual Studio 2019 开始,不再在 MSIX 项目中生成临时证书。Starting in Visual Studio 2019, a temporary certificate is no longer generated in MSIX projects. 若要创建或导出证书,请使用此文中所述的 PowerShell cmdlet。To create or export certificates, use the PowerShell cmdlets described in this article.

在 YAML 文件中配置生成Configure the Build in your YAML file

下表列出了可以定义用来设置生成管道的各个 MSBuild 参数。The table below lists the different MSBuild arguments you can define to setup your build pipeline.

MSBuild 参数MSBuild argument Value 描述Description
AppxPackageDirAppxPackageDir $(Build.ArtifactStagingDirectory)\AppxPackages$(Build.ArtifactStagingDirectory)\AppxPackages 定义要存储生成的项目的文件夹。Defines the folder to store the generated artifacts.
AppxBundlePlatformsAppxBundlePlatforms $(Build.BuildPlatform)$(Build.BuildPlatform) 用于定义要包含在捆绑包中的平台。Enables you to define the platforms to include in the bundle.
AppxBundleAppxBundle 始终Always 使用 .msix/.appx 文件为指定的平台创建 .msixbundle/.appxbundle。Creates an .msixbundle/.appxbundle with the .msix/.appx files for the platform specified.
UapAppxPackageBuildModeUapAppxPackageBuildMode StoreUploadStoreUpload 生成要旁加载的 .msixupload/.appxupload 文件和 _Test 文件夹。Generates the .msixupload/.appxupload file and the _Test folder for sideloading.
UapAppxPackageBuildModeUapAppxPackageBuildMode CICI 仅生成 .msixupload/.appxupload 文件。Generates the .msixupload/.appxupload file only.
UapAppxPackageBuildModeUapAppxPackageBuildMode SideloadOnlySideloadOnly 仅生成要旁加载的 _Test 文件夹。Generates the _Test folder for sideloading only.
AppxPackageSigningEnabledAppxPackageSigningEnabled truetrue 启用包签名。Enables package signing.
PackageCertificateThumbprintPackageCertificateThumbprint 证书指纹Certificate Thumbprint 此值必须与签名证书中的指纹匹配,或者为空字符串。This value must match the thumbprint in the signing certificate, or be an empty string.
PackageCertificateKeyFilePackageCertificateKeyFile 路径Path 要使用的证书的路径。The path to the certificate to use. 此值是从安全文件元数据中检索的。This is retrieved from the secure file metadata.
PackageCertificatePasswordPackageCertificatePassword 密码Password 证书中私钥的密码。The password for the private key in the certificate. 建议将密码存储在 Azure Key Vault 中,并将密码链接到变量组We recommend that you store your password in Azure Key Vault and link the password to variable group. 可将变量传递到此参数。You can pass the variable to this argument.

在使用 MSBuild 命令行生成打包项目(就像在 Visual Studio 中使用向导生成项目一样)之前,生成过程可以通过编辑 Package.appxmanifest 文件中 Package 元素的 Version 属性,来对生成的 MSIX 包进行版本控制。Before building the packaging project the same way the wizard in Visual Studio does using the MSBuild command line, the build process can version the MSIX package that’s being produced by editing the Version attribute of the Package element in the Package.appxmanifest file. 在 Azure Pipelines 中,可以使用某个表达式来实现此目的,该表达式可以设置每次生成都会递增的计数器变量。也可以通过某个 PowerShell 脚本来实现此目的,该脚本使用 .NET 中的 System.Xml.Linq.XDocument 类来更改该属性的值。In Azure Pipelines, this can be achieved by using an expression for setting a counter variable that gets incremented for every build, and a PowerShell script that uses the System.Xml.Linq.XDocument class in .NET to change the value of the attribute.

用于定义 MSIX 生成管道的示例 YAML 文件Sample YAML File that defines the MSIX Build Pipeline

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 文件中定义的不同生成任务的细分:Below are breakdowns of the different Build tasks defined in the YAMl file:

配置包生成属性Configure package generation properties

以下定义设置生成组件的目录、平台,并定义是否生成捆绑包。The definition below sets the directory of Build components, the platform and defines whether to build a bundle or not.

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

配置包签名Configure package signing

若要为 MSIX(或 APPX)包签名,管道需要检索签名证书。To sign the MSIX (or APPX) package the pipeline needs to retrieve the signing certificate. 为此,请在 VSBuild 任务的前面添加 DownloadSecureFile 任务。To do this, add a DownloadSecureFile task prior to the VSBuild task. 这样,就可以通过 signingCert 访问签名证书。This will give you access to the signing certificate via signingCert.

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

接下来,更新 MSBuild 任务以引用签名证书:Next, update the MSBuild task to reference the signing certificate:

- 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 参数被有意设置为空字符串,目的是引以注意。The PackageCertificateThumbprint argument is intentionally set to an empty string as a precaution. 如果在项目中设置指纹,但该指纹与签名证书不匹配,则生成将会失败并出现错误:Certificate does not match supplied signing thumbprintIf the thumbprint is set in the project but does not match the signing certificate, the build will fail with the error: Certificate does not match supplied signing thumbprint.

查看参数Review parameters

使用 $() 语法定义的参数是在生成定义中定义的变量,在其他生成系统中将会更改。The parameters defined with the $() syntax are variables defined in the build definition, and will change in other build systems.

若要查看所有预定义的变量,请参阅预定义的生成变量To view all predefined variables, see Predefined build variables.

配置“发布生成项目”任务Configure the Publish Build Artifacts task

默认的 MSIX 管道不会保存生成的项目。The default MSIX pipeline does not save the generated artifacts. 若要将发布功能添加到 YAML 定义,请添加以下任务。To add the publish capabilities to your YAML definition, add the following tasks.

- 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)'

可以在生成结果页的“项目”选项中查看生成的项目。You can see the generated artifacts in the Artifacts option of the build results page.

非 Store 分发内容的 AppInstaller 文件AppInstaller file for non-store distribution

如果在 Store 的外部分发应用程序,可以利用 AppInstaller 文件来安装和更新包If you're distributing your application outside the Store you can take advantage of the AppInstaller file for your package install and updates

一个可在 \server\foo 上查找更新文件的 .appinstaller 文件An .appinstaller File That Will Look for Updated Files on \server\foo

<?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 元素用于告知系统何时检查更新,以及是否强制用户更新。The UpdateSettings element is used to tell the system when to check for updates and whether to force the user to update. 可以在 bit.ly/2TGWnCR 上的文档中找到完整的架构参考,包括每个 Windows 10 版本支持的命名空间。The full schema reference, including the supported namespaces for each version of Windows 10, can be found in the docs at bit.ly/2TGWnCR.

如果将 .appinstaller 文件添加到打包项目,并将其“打包操作”属性设置为“内容”,将“复制到输出目录”属性设置为“如果较新则复制”,则可以将另一个 PowerShell 任务添加到 YAML 文件,以便更新 root 和 MainPackage 元素的 Version 属性,并将更新的文件保存到暂存目录:If you add the .appinstaller file to the packaging project and set its Package Action property to Content and the Copy to Output Directory property to Copy if newer, you can then add another PowerShell task to the YAML file that updates the Version attributes of the root and MainPackage elements and saves the updated file to the staging directory:

- 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 文件来安装打包的应用。You’d then distribute the .appinstaller file to your end users and let them double-click on this one instead of the .msix file to install the packaged app.

持续部署Continuous Deployment

应用安装程序文件本身是未编译的 XML 文件,生成后可按需编辑该文件。The app installer file itself is an uncompiled XML file that can be edited after the build, if required. 这样,在将软件部署到多个环境,以及想要将生成管道与发布过程区分开来时,就可以轻松地使用该文件。This makes it easy to use when you deploy your software to multiple environments and when you want to separate the build pipeline from the release process.

如果在 Azure 门户中使用“空作业”模板创建发布管道,并使用最近设置的生成管道作为要部署的项目的源,则可以将 PowerShell 任务添加到发布阶段,以动态更改 .appinstaller 文件中两个 Uri 属性的值,来反映应用发布到的位置。If you create a release pipeline in the Azure Portal using the “Empty job” template and use the recently set up build pipeline as the source of the artifact to be deployed, you can then add the PowerShell task to the release stage in order to dynamically change the values of the two Uri attributes in the .appinstaller file to reflect the location to which the app is published.

一个用于修改 .appinstaller 文件中的 URI 的发布管道任务A Release Pipeline Task That Modifies the Uris in the .appinstaller File

- 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 路径。In the task above, the URI is set to the UNC path of an Azure file share. 由于在安装和更新应用时,OS 将在此位置查找 MSIX 包,因此,我还将另一个命令行脚本添加到了发布管道,该脚本首先将云中的文件共享映射到生成代理上的本地 Z:\ 驱动器,然后使用 xcopy 命令将 .appinstaller 和 .msix 文件复制到该驱动器:Because this is where the OS will look for the MSIX package when you install and update the app, I’ve also added another command-line script to the release pipeline that first maps the file share in the cloud to the local Z:\ drive on the build agent before it uses the xcopy command to copy the .appinstaller and .msix files there:

- 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,当然可以将文件发布到自己的内部网络共享。If you host your own on-premises Azure DevOps Server, you may of course publish the files to your own internal network share.

如果选择发布到 Web 服务器,可以通过在 YAML 文件中提供一些附加的参数,来告知 MSBuild 生成版本受控的 .appinstaller 文件和一个 HTML 页面,该页面包含下载链接,以及一些有关打包的应用的信息:If you choose to publish to a Web server, you can tell MSBuild to generate a versioned .appinstaller file and an HTML page that contains a download link and some information about the packaged app by supplying a few additional arguments in the YAML file:

- 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 协议激活方案为前缀:The generated HTML file includes a hyperlink prefixed with the browser-agnostic ms-appinstaller protocol activation scheme:

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

如果设置发布管道用于将放置文件夹的内容发布到 Intranet 或任何其他网站,且 Web 服务器支持字节范围请求并已正确配置,则最终用户可以使用此链接直接安装应用,而无需先下载 MSIX 包。If you set up a release pipeline that publishes the contents of the drop folder to your intranet or any other Web site, and the Web server supports byte-range requests and is configured properly, your end users can use this link to directly install the app without downloading the MSIX package first.