Preface#
At the end of last year, the Snap Genshin project was archived and replaced by Snap Hutao. In the new generation of applications, the project utilized updated technologies, replacing the original WPF with WinUI3, and was ultimately packaged as an MSIX installation package, making it available in the Microsoft Store. Due to the store's review mechanism, the application can no longer push updates at any time like in the past self-distribution model, making the CI/CD channel version built for developer testing more important. To this end, I created a CI/CD pipeline through Azure DevOps Pipelines by integrating it with GitHub.
Creating a DevOps Account#
-
Go to the Microsoft Azure Pipelines homepage, click Start free with GitHub to log in with your Microsoft account and access the Azure DevOps panel while linking your GitHub account.
-
If you are creating this environment for a team or company, it is recommended to use the team Microsoft account and authorize Azure DevOps to use team repository data during the GitHub linking process.
- At the same time, you need to create a DevOps team name for your team.
- For open-source projects that require free quotas, you need to add this form or send an email to
[email protected]
to apply for a free quota.
-
You will need to wait 1-2 days for Microsoft to add a free quota for your organization to run the CI/CD pipeline. In the meantime, you can continue building your CI/CD pipeline, but they will not run due to insufficient quota.
Creating a Project#
-
Enter the DevOps panel, go to the organization page, and click
New Project
on the right to create a new project. -
After successful creation, select
GitHub
as your repository. -
When selecting the repository, choose
All repositories
in the filter, find the project for which you need to create the CI pipeline, and confirm.
-
The page will then redirect to the GitHub authorization page, where you agree to allow Azure DevOps to edit the repository.
-
After completing the authorization, you will be redirected to a
YAML
file editor, which is the configuration file for Azure Pipelines.
Building the Pipeline#
[tip type="info" title="Compatibility Issues"]
In the official Microsoft documentation, they recommend the Azure Pipelines plugin maintained by Microsoft, but this plugin has compatibility issues with VS 2022 at the time of writing this article. Therefore, when building the Snap Hutao project, I packaged the application via PowerShell in command line mode. Thus, you can also learn about the details of the MSIX packaging process through this article.
[/tip]
Preparation#
Before everything starts, we first set some environment variables for the pipeline environment.
build_date
returns a date format like2023.2.28
by calling PowerShell in the environment, and we will use such a date as the version number of the software in the CI/CD testing channel.
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) ]
We set the CI/CD environment to Windows Server 2022, which already includes Visual Studio 2022 Enterprise Edition.
pool:
vmImage: 'windows-2022'
Compiling the Binary Files#
- Next, we create a packaging environment for the .NET Framework program.
- 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'
Then, we can use MSBuild
included in Visual Studio 2022 to compile the code.
- At this step, in most cases, we only need to set
msbuildLocationMethod
toversion
and setmsbuildVersion
tolatest
or another specified version. However, due to compatibility issues with VS2022, we have to uselocation
and the path ofMSBuild.exe
as packaging parameters.
- 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 Packaging#
Before packaging, we need to prepare the version number of the packaged software. In the compiled binary program, the software version number follows the settings in the code, but we cannot modify it for a temporary CI/CD version, so we need the CI/CD pipeline to prepare this temporary version number for us. When the CI/CD pipeline initially runs, we have already obtained a date format like 2023.2.28
through environment variables, but it lacks the fourth revision number
. Coincidentally, Azure Pipelines creates a version number for each CI/CD task, formatted like 20230228.1
, where the number after the decimal point represents the nth task on that date, making it very suitable for the revision number
, as it ensures that the version number is always incrementally ordered.
In the Azure DevOps Market, we can add a plugin called GetRevision
. By using it, we can add the following script to set the rev_number
variable.
- task: GetRevision@1
displayName: get Pipelines revision number
inputs:
VariableName: 'rev_number'
Unless specified otherwise, the compiled program will be stored in the \bin\x64\Release\net7.0-windows10.0.18362.0\win10-x64\
directory under the project directory.
The Windows system identifies the application uniquely by reading the Identity Name
of the MSIX application, so we need to change some information to distinguish between the CI/CD version application and the official version application. Here, we use MagicChunks
to modify the AppxManifest.xml
file that manages these properties.
transformations
contains the property values we are overriding.Package/Identity/@Name
is the name used by the system to identify the unique identifier of the package.Package/Identity/@Publisher
is the key developer information, and its value must match exactly with your MSIX application package signature, otherwise, a signature error will occur.- If your code signing certificate is purchased, it may sometimes contain multiple pieces of information, such as company name, organization name, and email. In this case, some punctuation may conflict with the
xml
format. Therefore, we need to escape any conflicting symbols in this information and include all of it, not justCN
.
- If your code signing certificate is purchased, it may sometimes contain multiple pieces of information, such as company name, organization name, and email. In this case, some punctuation may conflict with the
"Package/Identity/@Version": "$(build_date).$(rev_number)"
writes the application package version we prepared earlier into the property.
- 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": "Hutao Alpha",
"Package/Properties/PublisherDisplayName":"DGP Studio CI",
"Package/Applications/Application/uap:VisualElements/@DisplayName": "Hutao Alpha"
}
Next, we will also add the static resources used in the application to the binary program directory. These resources are called externally in the code, so they will not be automatically added to the compiled directory during the binary compilation process.
- 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'
Everything is ready, and we can now use makeappx
to package the binary files into an MSIX application package.
- Among them,
$(Build.ArtifactStagingDirectory)
is the default export resource directory of 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'
Signing the MSIX Application Package#
MSIX application packages require all applications to be code-signed; otherwise, the application cannot be installed. Therefore, we will sign the packaged MSIX package next.
Click on the Library
in the Pipelines menu, go to Secure files
, and click the button to upload the password-protected pfx certificate file.
Back in the Pipelines YAML file editor, we need to store the password of the pfx certificate as a variable in this pipeline task. Click on Variables
in the upper right corner and click to add. In the variable addition window, set a name for the variable and fill in the password in Value
, and finally check Keep this value secret
.
Now we can use Microsoft's official MsixSigning
plugin to sign the Msix application package.
- Here,
certificate
is the name of the certificate file you uploaded inSecure Files
. passwordVariable
is the name of the variable where you stored the pfx certificate password.
- 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'
If you wish to use PowerShell or CMD to sign, you can refer to the following command:
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
Publishing the CI/CD Application#
In the Snap Hutao project, I decided to publish the sideload package and the CI/CD sideload certificate as a pre-release
in the main GitHub repository. Among them,
- The
Download Root CA
task downloads the sideload certificate stored inSecure Files
to the CI/CD environment and stores that file ascerFile
in the environment variable. In the GitHub Release publishing process, we can reference that file using$(cerFile.secureFilePath)
. gitHubConnection
is the integrated GitHub account.
- 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: |
## Ordinary users please do not download
This version is an `Alpha` test version automatically packaged by the CI program, **for developer testing use only**.
Ordinary users please [click here](https://github.com/DGP-Studio/Snap.Hutao/releases/latest/) to download the latest stable version.
assets: |
$(Build.ArtifactStagingDirectory)/*
$(cerFile.secureFilePath)
isPreRelease: true
changeLogCompareToRelease: 'lastFullRelease'
changeLogType: 'commitBased'
Results#
Click the save button in the upper right corner of the YAML editor, and your CI/CD configuration file will be added to the GitHub repository and executed immediately. In Azure DevOps, you can see all task records of the Pipelines.
In the GitHub Release, you can see the published CI/CD version.
In the corresponding Commit, you can also see the information of the corresponding CI/CD task.