Configurer un pipeline CI/CD avec un fichier YAML

Le tableau ci-dessous liste les différents arguments MSBuild que vous pouvez définir pour configurer votre pipeline de build.

Argument MSBuild Valeur Description
AppxPackageDir $(Build.ArtifactStagingDirectory)\AppxPackages Définit le dossier de stockage des artefacts générés.
AppxBundlePlatforms $(Build.BuildPlatform) Vous permet de définir les plateformes à inclure dans le bundle.
AppxBundle Toujours Crée un fichier .msixbundle/.appxbundle avec les fichiers .msix/.appx pour la plateforme spécifiée.
UapAppxPackageBuildMode StoreUpload Génère le fichier .msixupload/.appxupload et le dossier _Test pour le chargement indépendant.
UapAppxPackageBuildMode CI Génère uniquement le fichier .msixupload/.appxupload.
UapAppxPackageBuildMode SideloadOnly Génère le dossier _Test pour le chargement indépendant uniquement.
AppxPackageSigningEnabled true Active la signature du package.
PackageCertificateThumbprint Empreinte de certificat Cette valeur doit correspondre à l’empreinte numérique du certificat de signature, ou être une chaîne vide.
PackageCertificateKeyFile Path Chemin du certificat à utiliser. Il est extrait des métadonnées de fichier sécurisé.
PackageCertificatePassword Mot de passe Mot de passe de la clé privée dans le certificat. Nous vous recommandons de stocker votre mot de passe dans Azure Key Vault et de le lier à un groupe de variables. Vous pouvez passer la variable à cet argument.

Avant de générer le projet d’empaquetage comme l’Assistant dans Visual Studio le fait à l’aide de la ligne de commande MSBuild, le processus de génération peut affecter une version au package MSIX produit en modifiant l’attribut Version de l’élément Package dans le fichier Package.appxmanifest. Dans Azure Pipelines, cela peut être fait en utilisant une expression pour définir une variable de compteur qui est incrémentée à chaque build, et un script PowerShell qui utilise la classe System.Xml.Linq.XDocument dans .NET pour changer la valeur de l’attribut.

Exemple de fichier YAML qui définit le pipeline de build MSIX

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'

Vous trouverez ci-dessous le détail des différentes tâches de génération définies dans le fichier YAMl :

Configurer les propriétés de génération du package

La définition ci-dessous définit le répertoire des composants de build, la plateforme, et définit s’il faut générer ou non un bundle.

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

Configurer la signature du package

Pour signer le package MSIX (ou APPX), le pipeline doit récupérer le certificat de signature. Pour ce faire, ajoutez une tâche DownloadSecureFile avant la tâche VSBuild. Vous aurez alors accès au certificat de signature par le biais de signingCert.

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

Ensuite, mettez à jour la tâche MSBuild pour qu’elle référence le certificat de signature :

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

Remarque

Une chaîne vide est affectée intentionnellement à l’argument PackageCertificateThumbprint en guise de précaution. Si l’empreinte numérique est définie dans le projet, mais qu’elle ne correspond pas au certificat de signature, la génération échoue avec l’erreur : Certificate does not match supplied signing thumbprint.

Passer en revue les paramètres

Les paramètres définis avec la syntaxe $() sont des variables définies dans la définition de build. Ils sont différents dans d’autres systèmes de génération.

Pour afficher toutes les variables prédéfinies, consultez Variables de build prédéfinies.

Configurer la tâche Publier des artefacts de build

Le pipeline MSIX par défaut n’enregistre pas les artefacts générés. Pour ajouter les fonctionnalités de publication à votre définition YAML, ajoutez les tâches suivantes.

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

Vous pouvez voir les artefacts générés dans l’option Artefacts de la page de résultats de la build.

Fichier AppInstaller pour la distribution hors magasin

Si vous distribuez votre application en dehors du Store, vous pouvez tirer parti du fichier AppInstaller pour l’installation et les mises à jour de votre package.

Un fichier .appinstaller qui recherche les fichiers mis à jour sur 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>

L’élément UpdateSettings sert à indiquer au système quand il doit rechercher des mises à jour et s’il faut forcer l’utilisateur à effectuer une mise à jour. Les informations de référence complètes sur le schéma, y compris les espaces de noms pris en charge pour chaque version de Windows 10, sont disponibles dans la documentation à l’adresse bit.ly/2TGWnCR.

Si vous ajoutez le fichier .appinstaller au projet d’empaquetage et que vous affectez à sa propriété Action de package la valeur Contenu et à la propriété Copier dans le répertoire de sortie la valeur Copier si plus récent, vous pouvez ajouter une autre tâche PowerShell au fichier YAML qui met à jour les attributs Version de la racine et les éléments MainPackage, et enregistre le fichier mis à jour dans le répertoire intermédiaire :

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

Vous devez ensuite distribuer le fichier .appinstaller à vos utilisateurs finaux et les laisser double-cliquer sur celui-ci plutôt que sur le fichier .msix pour installer l’application empaquetée.

Déploiement continu

Le fichier du programme d’installation de l’application proprement dit est un fichier XML non compilé qui peut être modifié après la génération, si nécessaire. Cela facilite son utilisation quand vous déployez vos logiciels dans plusieurs environnements et quand vous souhaitez séparer le pipeline de build du processus de mise en production.

Si vous créez un pipeline de mise en production dans le portail Azure à l’aide du modèle « Travail vide » et que vous utilisez le pipeline de build récemment configuré comme source de l’artefact à déployer, vous pouvez alors ajouter la tâche PowerShell à la phase de mise en production afin de changer dynamiquement les valeurs des deux attributs d’URI dans le fichier .appinstaller pour qu’ils reflètent l’emplacement vers lequel l’application est publiée.

Une tâche de pipeline de mise en production qui modifie les URI dans le fichier .appinstaller :

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

Dans la tâche ci-dessus, l’URI est défini sur le chemin UNC d’un partage de fichiers Azure. Comme c’est là que le système d’exploitation recherche le package MSIX quand vous installez et mettez à jour l’application, j’ai aussi ajouté un autre script de ligne de commande au pipeline de mise en production qui mappe d’abord le partage de fichiers dans le cloud au lecteur Z:\ local sur l’agent de build avant d’utiliser la commande xcopy pour y copier les fichiers .appinstaller et .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'

Si vous hébergez votre propre serveur Azure DevOps local, vous pouvez bien sûr publier les fichiers sur votre propre partage réseau interne.

Si vous choisissez de publier sur un serveur web, vous pouvez indiquer à MSBuild de générer un fichier .appinstaller avec version et une page HTML qui contient un lien de téléchargement et des informations sur l’application empaquetée en spécifiant quelques arguments supplémentaires dans le fichier YAML :

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

Le fichier HTML généré comprend un lien hypertexte préfixé avec le schéma d’activation de protocole ms-appinstaller indépendant du navigateur :

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

Si vous configurez un pipeline de mise en production qui publie le contenu du dossier cible sur votre intranet ou sur un autre site web, et que le serveur Web prend en charge les requêtes de plages d’octets et est correctement configuré, vos utilisateurs finaux peuvent utiliser ce lien pour installer directement l’application sans télécharger au préalable le package MSIX.