Comment résoudre l'erreur WIX0217 avec une solution .net 8 ?

Suite à la mise à jour de serveurs de build, je me suis retrouvé avec une bonne grosse erreur de Wix Toolset :

##[error]wix.exe(0,0): Error WIX0217: Error executing ICE action 'ICE02'. The following string format was not expected by the external UI message logger: "Impossible d'accéder au service Windows Installer. Ceci peut se produire si le programme d'installation de Windows n'est pas bien installé. Contactez votre support technique pour assistance.".

Celle-ci se produit lors de l'usage de la tâche DotNetCoreCLI d'Azure Pipeline. Cette tâche cherche à compiler une solution Wix Toolset 4 avec le SDK de .net 8.

Petite étrangeté : la même opération fonctionne très bien dans Visual Studio, ou via la CLI de .net 8.

Quand on connait un peu wix toolset, et que l'on consulte les logs, on se rend vite compte que le problème se produit lors de la validation du msi. La tentation est grande de désactiver cette validation. Surtout, ne faites pas cela. Ne touchez pas à votre projet. Même pas pour ajouter un RunWixToolsOutOfProc (cela ne sert à rien). Le problème n'est pas là. Le problème est lié au serveur de build, et au contexte dans lequel celui-ci s'exécute.

Historiquement, l'erreur 217 se produisait avec wix toolset 3. Celui-ci utilisait un bon vieux vbscript pour exécuter la validation. Aujourd'hui, vbscript n'est plus utilisé, mais le code d'erreur reste le même. J'ai jugé bon de tenter la solution que j'avais déjà documentée il y a quelques années ici.

Il se trouve que le changement du compte du service Windows de l'agent en Compte système local avec l'autorisation d'interagir avec le Bureau résout le problème.

Affectation du Compte système local dans le gestionnaire des services de Windows

Important : ne pas oublier de redémarrer le service après changement de ce compte.

Il s'agit certainement d'un problème pour accéder à une ressource de type PATH, DLL, ou un enregistrement de ressource qui ne peuvent pas être utilisés en dehors de ce contexte.

Jérémy Jeanson

Comment résoudre les problèmes d'accès à Azure Artefact via une build utilisant le SDK de .net 8 ?

Voici un problème qui a un petit goût de réchauffé. Il y a quelques moi j'expliquais ici même la démarche à suivre quand Azure Pipeline dit qu’il est impossible de charger l'index nuget d’Azure Artefact. Aujourd'hui, je reviens avec un sujet très similaire.

Suite à l'installation du SDK .net 8 sur un serveur de build, je me suis retrouvé avec quelques problèmes de builds. Des pipelines hébergés via Azure DevOps Server indiquent avoir des problèmes pour charger l'index d'un repository Azure Artefact.

L'erreur se produit sur une tâche DotNetCoreCLI se trouvant pourtant derrière une tâche NugetCommand(restore).

Si on regarde le log (que je n'ai malheureusement plus sous la main), on constate que la tâche tente d'accéder à Azure Artefact malgré la présence des packages fraichement téléchargés via nuget. La présence du SDK de .net 8 semble changer le comportement par défaut de la CLI.

Pour résoudre ce problème une bonne fois pour toutes, il suffit donc de dire à DotNetCoreCLI de ne pas restaurer les packages nuget. Pour cela, on peut utiliser sa propriété argument et lui passer l'argument --no-restore.

Voici un exemple complet :


steps:
  - task: NuGetToolInstaller@1
  - task: NuGetCommand@2
    displayName: 'NuGet restore'
    inputs:
      restoreSolution: $(solution)
      command: restore
      feedsToUse: config
      nugetConfigPath: 'Sources/nuget.config'
          
  - task: DotNetCoreCLI@2
    displayName: 'Compilation'
    inputs:
      command: build
      projects: '$(solution)'
      arguments: '--no-restore --configuration $(buildConfiguration) --property:Platform=$(buildPlatform)'

Moralité

Les paramètres par défaut, c'est mal. Surtout si vos pipelines reposent dessus.

Jérémy Jeanson

Comment résoudre la System.Resources.MissingManifestResourceException pouvant se produire après un Migration .net Framework ?

Dans le cadre d'une migration d'un projet .net Framework, je me suis retrouvé avec des projets ne respectant que très peu les conventions liées aux namespaces. Plusieurs dossiers pourtant bien présents n'apparaissent pas dans les namespaces. Pour compléter le tableau, Visual Studio doit gérer des fichiers C# pour des Ressources, et formulaires WinForm présents dans ces dossiers.

Après migration de ces projets du vieux format csproj / vbproj vers le format actuel (SDK project style), les tests unitaires font remonter une erreur bien étrange :

System.Resources.MissingManifestResourceException : Could not find any resources appropriate for the specified culture or the neutral culture. Make sure "xxxForm.resources" was correctly embedded or linked into assembly "xxx" at compile time, or that all the satellite assemblies required are loadable and fully signed.

Pour saisir l’origine du problème, je vérifie les propriétés de Ressources. Tout est à sa place. Je fais un petit tour dans dotPeek pour comparer les deux DLL avant, et après migration. Je me rends alors compte que les ressources WinFrom ne sont pas nommées de la même manière avant et après migration du projet. Les projets de tests unitaires étant encore à l’ancien format, il s’attend à ce que les ressources respectent un certain nommage.

Un petit tour dans Microsoft Learn (après une bonne heure de recherche infructueuse ici et là), je trouve le Satin Graal : How MSBuild generates manifest file names - .NET | Microsoft Learn

Cette page de documentation fait état d'un paramètre qui rétablit le comportement des namespace pour les applications .net Framework :


<EmbeddedResourceUseDependentUponConvention>true</EmbeddedResourceUseDependentUponConvention>

Après ajout de celui-ci dans le fichier vsproj / vbproj, les ressources sont correctement nommée et l'erreur ne se produit plus.

Moralité

Encore une preuve que les bonnes pratiques ne sont pas juste là pour nous embeter. Si les conventions de nommage des namespaces étaint respectées, l'exception ne se serait jamais produite.

Jérémy Jeanson

Comment identifier, et résoudre les problèmes d'une migration vers PackageReference ratée ?

En temps normal, la migration des références vers PackageReference se passe en douceur. Il suffit de faire un click droit sur le dossier References, et de profiter du menu contextuel qui s'ouvre pour lancer la migration. Quelques secondes plus tard, le fichier package.config disparait.

Mais il arrive que les choses ne se passent pas bien. Ce peut être au moment de la migration, ou après la récupération du code d'un collègue.

Une migration peut être refusée, pour des raisons d'incompatibilité. Le motif est noté dans la fenêtre de sortie de Visual Studio. Il suffit de résoudre le problème indiqué.

Dans d'autres cas, la migration est allée au bout. Mais ce n'est pas pour autant que tout va bien. Cela se matérialise par des références apparaissant en double. Certaines ont l'icône nuget, d'autres non.

Projet avec des références en double

Cela est dû au fait que lors de la migration, l'assistant n'a pas supprimé les liens vers le dossier ..\packages. Pour corriger ce projet, il suffit donc d'éditer le fichier csproj, ou vbproj, et de supprimer les nœuds XML de type Reference dont le HnitPath pointe vers le dossier ..\packages.


<Reference Include="AutoFixture, Version=4.17.0.0, Culture=neutral, PublicKeyToken=b24654c590009d4f, processorArchitecture=MSIL">
  <HintPath>..\packages\AutoFixture.4.17.0\lib\net452\AutoFixture.dll</HintPath>
</Reference>

Voilà, vous savez maintenant corriger un projet dont la migration vers PackageReference n'a pas réussi du premier coup ;)

Jérémy Jeanson

Gérer le warning "Cannot convert null literal to non-nullable reference type" avec Moq

Quand on utilise Moq avec de vieux projets qui migrent de .net Framework vers .net, il y a un petit Warning qui devient vite irritant :

Warning CS8625 Cannot convert null literal to non-nullable reference type.

Celui-ci se produit quand une méthode doit retourner null.

Voici un exemple d'interface avec une opération asynchrone (histoire de reproduire un cas proche de la réalité).


public interface IMyRepository {
  ValueTask<MaClass?> MethodeReturnMaClassNullableAsync(...);
}

Pour utiliser Moq, il faut ajouter un Cast en plus du ValueTask.FromResult.


var repository = new Mock<IMyRepository>();
repository
  .Setup(c => c.MethodeReturnMaClassNullableAsync(...))
  .Returns(ValueTask.FromResult((MaClass?)null));

Toute implémentation sans le Cast et le ValueTask.FromResult feront apparaitre un Waring. Cette écriture n'est pas nouvelle, mais il semble qu'elle perturbe encore nombre de développeurs.

Jérémy Jeanson