Comment automatiser l’installation de MSI avec Azure DevOps, et Release Manager ?

Automatisé le déploiement de MSI, n’a jamais été une tâche triviale. Aujourd’hui, je vous propose d’effectuer celle-ci avec Azure DevOps.

Pour l’exemple, j’utilise un pipeline de Release, et j’y ai ajouté une tâche powerShell :

Tâche PowerShell d'un pipeline de release

Dans la propriété Inline, il suffit de coller le contenu du script qui suit, et le tour est joué :


$msi = Get-ChildItem $(System.ArtifactsDirectory)\*.msi -Recurse
Write-Host "Msi trouvé : $msi"

$log = "D:\Applications\MyApp\installation.log"
if (Test-Path $log) {
        Remove-Item $log -verbose
}
 
$arguments = @( "/i", $msi, "INSTALLFOLDER=""D:\Applications\MyApp""", "/quiet","/lv",$log)
 Write-Host $arguments 
 
Start-Process msiexec.exe -ArgumentList $arguments -Wait

Ce script va piloter msiexec en mode silencieux (c’est-à-dire sans interface), et attendre la fin du déploiement. Pour diagnostiquer les éventuels problèmes qui pourraient survenir lors du déploiement, un fichier de log est créé. Ce fichier est conservé jusqu’à la prochaine exécution.

Bien évidemment, il faudra adapter la variable $arguments en fonction de vos besoins, et les différents chemins.

Ce script peut aussi être utilisé avec un pipeline de type multistage, et un job de déploiement.

Jérémy Jeanson

Comment adapter le nom d'un job d'Azure Pipeline, en fonction d'un paramètre de build ?

Avec Azure Pipeline, changer le nom d'un job à la voler, ou du moins afficher un libellé propre n'est pas toujours de tout repos. Certaines syntaxes peuvent fonctionner avec la version Cloud d'Azure DevOps, et ne pas être prises en charge on-premise. D'autres ne sont pas possibles sur certaines propriétés.

Afin d'arriver à mes fins avec Azure DevOps, j'ai mis en place une stratégie simple, et efficace : passer par des variables.

Quelle que soit la plateforme :

  • On peut conditionner la création d'une variable.
  • On peut conditionner l'affectation d'une variable (via une tache powershell par exemple).
  • Toutes les propriétés acceptent des variables.

Voici un petit exemple avec la propriété displayName d'un job (un vrai cauchemar à conditionner). Le texte affiché pour présenter le job change en fonction d'un paramètre choisi par l'utilisateur au moment de lancer la build. Pour effectuer cette opération, je passe par une variable jobDisplayName dont la définition change en fonction du paramètre jobType.


parameters:
  - name: jobType
    displayName: Type de build
    type: string
    default: evolution
    values:
      - correctif
      - evolution

variables:
  ${{ if eq(parameters.jobType,'correctif') }}:
    jobDisplayName: "Compilation d'un correctif"
  ${{ else }}:
    jobDisplayName: "Compilation d'une nouvelle version"

jobs:
- job: Build
  displayName: ${{ variables.jobDisplayName }}

Simple, efficace, et déclinable à volonté.

Attention : le else utilisé ici ne fonctionnera pas sur une instance Azure DevOps Server qui n'est pas à jour. Il faudra alors utiliser un second if.

Jérémy Jeanson

Dans quelles situations faut-il utiliser les options de Clean des pipelines d’Azure DevOps ?

Diverses tâches d’Azure Pipeline disposent d’options de Clean permettant de supprimer les fichiers inutiles (artefacts, binaires des précédentes builds, dossiers bin/obj et...). Ces options sont disponibles sur les tâches de compilations, les tâches de copies de fichiers, et dossiers.

Des développeurs un peu trop zélés auraient tendance à activer ces options à défaut. Malheureusement, celles-ci peuvent augmenter inutilement la durée d’exécution de la build. Il convient donc de prendre un peu de temps pour réfléchir à l’utilité de telles options.

Pour savoir si l’on peut avoir besoin d’utiliser des options de Clean, il convient de répondre à quelques questions simples :

  • Où s’exécutent les builds ?
  • Quel gestionnaire de code source est utilisé ?
  • Les builds utilisent elles les dossiers de binaires en plus du staging des artefacts (BinariesDirectory) ?

L’usage des options de clean est conditionné par la combinaison de vos réponses à ces trois questions.

Voulant faire simple, j’ai choisi de construire un tableau indiquant les situations pour lesquelles, un Clean est utile.

Source control BinariesDirectory
Agent de build Git TFVC Oui Non
Microsoft hosted Non Non Non Non
Self hosted
/ Azure VMs Scale Set
Non Oui Oui Non

Pour résumer simplement les raisons qui ont conduit à apporter ces réponses :

  • Les agents de builds hébergés par Microsoft sont détruits après chaque exécution. Un agent ne conserve donc aucune trace des précédentes builds, contrairement aux VM Azure, et agents que l’on héberge soit même.
  • Avant de télécharger, du code versionné via git, un pipeline lance la commande git clean -ffdx (par défaut). Celle-ci effectue un clean du repository local. Le répertoire contant le code source est donc propre, quoi qu’il arrive (par défaut).
  • Le Workspace TFVC n’est pas nettoyé entre les builds. Il en est de même pour les dossiers des binaires, et des tests uhnitaires.

Conclusion

Aucun mécanisme de Clean n’est nécessaire pour un usage 100% Cloud d’Azure DevOps, sauf si on utilise des VM Azure. Il en est de même pour un usage hybride avec Azure DevOps Server, et des agents Microsoft hosted.

Un usage 100% on-premise demandera donc un peu plus de réflexion.

Jérémy Jeanson

Pourquoi winget affiche parfois des Id étranges ?

Par le passé, j'ai eu à expliquer à des développeurs qu'il ne fallait pas utiliser un Guid pour piloter winget. Car l'Id d’une application peut parfois prendre des formes étranges.

Sachant qu'un bon exemple vaut mieux que de longues explications, voici une petite démo de winget retournant une liste contenant un id hors norme :


PS C:\Users\jeremy> winget list "app"
Nom ID Version
-----------------------------------------------------------
App {A0B1C63D-F1E2-4306-4A58-A8140C917516} 1.0.0.9
App2 a8cf55b6c07a823e 1.0.0.1


Dans le cas présent, la première application a été déployée via un msi, la seconde ClickOnce.

Voici donc la raison pour laquelle il ne faut pas penser que winget n'utilise que des Guid ;)

Jérémy Jeanson

Rendre visibles les classes, et méthodes internes, d'un assembly signé

Dans un précédent article, j'avais présenté la nouvelle manière de rendre accessibles les éléments internes d'un projet pour les tests unitaire. Aujourd'hui, je vous propose d'effectuer la même opération, avec un projet signé. L'approche est très similaire, il suffit d'ajouter l'attribut Key à InternalsVisibleTo. Seul petit problème, la documentation manque légèrement de détails sur la démarche à suivre (comment trouver la clé demandée, et comment la mettre en forme?).

Voici les étapes à suivre :

  1. Signer le projet de test

À ce niveau, la documentation est très bien faite. Si l'on suit la méthode présentée ici, Visual Studio ajoute des nœuds similaires à ceux-ci au projet de tests:


<SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>..\strong-name-key.snk</AssemblyOriginatorKeyFile>

Note : Une bonne habitude pour se simplifier la vie consiste à réutiliser le même fichier snk pour signer l'ensemble des projets. Un seul fichier signifie qu'il n'y a qu'une seule clé à utiliser pour la totalité des projets. Ainsi, il est plus facile de s'y retrouver, et l'opération qui suit n'a besoin d'être effectuée une seule foi.

  1. Obtenir le hash de la clé publique

Après compilation du projet de tests, on peut demander à Visual Studio d'afficher la totalité des fichiers. Il suffit alors de dérouler l'arborescence jusqu'aux dossiers de destination des binaires du projet de tests. Ensuite, un simple click droit sur le répertoire permet d'afficher le menu contextuel suivant. Il reste alors à choisir Open in Terminal.

clip_image002

Via le terminal intégré à Visual Studio, il ne reste plus qu'à utiliser la commande sn en lui fournissant le nom de l'assembly produit par le projet de tests unitaires.


PS C:\xxx\bin\x64\Debug\net8.0> sn -Tp .\MonProjet.Tests.dll
Microsoft (R) .NET Framework Strong Name Utility Version 4.0.30319.0
Copyright (c) Microsoft Corporation. All rights reserved.
Public key (hash algorithm: sha1):
xxx1
xxx2
xxx3
xxx4
xxx5
Public key token is yyy

La clé qui nous intéresse se trouve derrière Public key (hash algorithm: sha1).

  1. Utiliser le hash de la clé publique

La clé récupérée via la précédente étape peut alors être utilisée pour renseigner l'attribut Key de InternalVisibleTo qui se trouve dans le projet à tester. La clé doit être retranscrite sans saut de ligne.


<ItemGroup>
<InternalsVisibleTo Include="$(AssemblyName).Tests" Key="xxx1xxx2xxx3xxx4xxx5" />
</ItemGroup>

Conclusion

Simple, et efficace. Par contre, il ne s'agit pas du genre d'opération que l'on a envie de faire chaque matin. D'où mon précédent conseil d'utiliser le même fichier snk pour signer l'ensemble des projets d'une même solution.

Jérémy Jeanson