Building MSIX packages in Azure Pipelines

If you need to build and deploy your MSIX packages by configuring Continuous Integration (CI) and Continuous Deployment (CD), Azure Pipelines can help you. In this article, we will describe how to set up a pipeline from scratch using this technology.

A brief introduction to Azure Pipelines

As the documentation says, Azure Pipelines is a cloud service that you can use to automatically build and test your code project and make it available to other users. It supports configuration through the use of YAML files and provides a cloud-hosted build agent that comes with all the software required to create MSIX packages pre-installed (more details in the official documentation).

I assume that you have basic knowledge of how to create an account on the Azure portal and that you know what a YAML file is, so let’s start configuring our pipeline.

Set up a CI/CD pipeline

First, you must Create your pipeline by following the next steps:

  1. Go to Azure DevOps -> Pipelines -> Pipelines
  1. Select Create Pipeline
  2. Select where your source code is stored, we used GitHub:
  1. Then select the repository where you want to run the pipeline
  2. The configure section doesn’t have an MSIX template, so you must add first a new blank .yml file to the selected repository, then select the “Select an existing YAML file” option and choose the previously added.

The pipeline explained

When we create a package for our application, we run a process that restores the references of our solution. Then we build our solution, and we run the tests, and finally, if everything worked, we create and publish the package of our application.

To configure a pipeline, we write a YAML file. This file will contain tasks that will perform the steps we have named above. All the available YAML tasks can be found here, including a description of how to use them.

We used the Microsoft UWP Systray Sample to configure our pipeline. It is worth mentioning that the sample does not have a packaging project included, so we had to add one (you can check this article if you need help).

The tasks we perform to mimic the steps we described above are the following:

  • Run the MSIX tests
####
#With VSBuild you can build and sign the application in one step.
- task: VSBuild@1
inputs:
solution: '$(Agent.BuildDirectory)\s\$(folderRepository)\UWP.Test\UWP.Test.csproj'
platform: $(buildPlatform)
configuration: $(buildConfiguration)
msbuildArguments: '/p:AppxBundlePlatforms="$(buildPlatform)" /p:AppxPackageDir="$(appxPackageDir)" /p:AppxBundle=Always /p:UapAppxPackageBuildMode=StoreUpload'
displayName: 'Building UWP Test App'
####
#This step run the tests.
- task: VSTest@2
inputs:
platform: $(buildPlatform)
testSelector: 'testAssemblies'
testAssemblyVer2: |
**\Release\UWP.Test.build.appxrecipe
!**\*TestAdapter.dll
!**\obj\**
searchFolder: '$(System.DefaultWorkingDirectory)'
codeCoverageEnabled: true
publishRunAttachments: true
displayName: 'Running UWP Tests'
####
#This step publish the test results.
- task: PublishTestResults@2
inputs:
testResultsFormat: 'VSTest'
testResultsFiles: '**/TEST-*.trx'
displayName: 'Publishing UWP tests results'

This is a three steps process. First, you need to build the MSIX test project, then run the tests, and finally, you create the tests-results report. At this point, if everything worked, the pipeline will continue executing the following steps. If not, the pipeline execution will fail.

  • Restore the NuGet packages of our solution
- task: NuGetCommand@2
displayName: 'Restore NuGet packages'
inputs:
command: 'restore'
restoreSolution: '$(Agent.BuildDirectory)\s\$(folderRepository)\SystrayExtension.sln'
feedsToUse: 'select'
  • Download and install a certificate file
#This certificate will be used to sign the application. 
- task: DownloadSecureFile@1
inputs:
secureFile: '$(signingCert.secureFilePath)'
displayName: 'Download Secure PFX File'
name: mySecureFile
####
#We're using the variables $(mySecureFile.secureFilePath) and
#$(signingCert.password) defined in a variable group.
- task: PowerShell@2
inputs:
targetType: 'inline'
script: |
Write-Host "Start adding the PFX file to the certificate store."
$pfxpath = '$(mySecureFile.secureFilePath)'
$password = '$(signingCert.password)'
Add-Type -AssemblyName System.Security
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$cert.Import($pfxpath, $password, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]"PersistKeySet")
$store = new-object system.security.cryptography.X509Certificates.X509Store -argumentlist "MY", CurrentUser
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]"ReadWrite")
$store.Add($cert)
$store.Close()
displayName: 'Install PFX File'
  • Build the companion app and create the package
- task: VSBuild@1
inputs:
solution: '$(Agent.BuildDirectory)\s\$(folderRepository)\SystrayComponent\SystrayComponent.csproj'
platform: $(buildPlatform)
configuration: $(buildConfiguration)
displayName: 'Building Win32 App'
- task: VSBuild@1
inputs:
solution: $(Agent.BuildDirectory)\s\$(folderRepository)\PackageManager\PackageManager.wapproj
platform: $(buildPlatform)
configuration: $(buildConfiguration)
msbuildArguments: '/p:AppxBundlePlatforms="$(buildPlatform)" /p:AppxPackageDir="$(appxPackageDir)" /p:AppxBundle=Never /p:UapAppxPackageBuildMode=StoreAndSideload /p:AppxPackageSigningEnabled=true /p:PackageCertificateKeyFile="$(signingCert.secureFilePath)" /p:PackageCertificatePassword="$(signingCert.password)"'
displayName: 'Package the App'

In our example, we used XAML island, so we needed to build this application before building the main one. During our tests, we discovered that the best YAML task to build an MSIX package was the VSBuild. This task allowed us to build and sign the package in one step, and the last two parameters store the certificate’s location and password.

  • Copy and publish your package
- 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)'

The full YAML file including all the steps can be found here.

Additional information

There is additional useful information and concepts that can be handy in your pipeline learning journey. We will describe what a variable group is and how to configure it, secure files, and we’ll add a brief comment on how you can deal with relative paths.

To avoid having sensitive data on the YAML file, you can create a Variable Group in Azure DevOps: go to Pipelines -> Library -> + Variable group.

It is very important to enable the “Allow access to all pipelines” option to be able to use it later in any pipeline.

To use those variables in the YAML file, you can add the group name in the variables section and then you can use it using this convention $(VariableName) .

You can find more information about how to add and use variable groups here.

A secure file can be a signing certificate, an Android Keystore file, or an SSH key. To protect this information, you can secure these files in the Library tab in Azure pipelines.

To add secure files, like the certificate we used in the sample, you have to go to Pipelines -> Library -> Secure files -> Secure file.

After you’ve added the file, edit it and enable the “Authorize for use in all pipelines” option.

By default, references in Visual Studio projects are added with relative paths. When dealing with external references, we recommend using absolute paths because the solution can be placed in a different folder other than the local one. Do this to avoid missing projects or DLLs references. Visual Studio provides some reserved properties that you can use in your .csprojs files.

Summing Up

Azure Pipelines is an extremely useful tool that will help you in your daily CI and CD tasks. We´ve included in this article useful information that we hope will help you at the moment of creating an Azure pipeline from scratch. Moreover, the sample YAML file shows you good examples of how to configure and use some of the most important tasks, so this is also a perfect starting point.