Déployer une application .NET avec Squirrel.Windows

En conséquence à la COVID 19, le nombre de collaborateur en télétravail à exploser. Nous avons dû adapter nos applications pour les déployer sur la flotte grandissante d'ordinateur portable. Dans ce cadre, nous avons opté pour Squirrel.Windows pour gérer l'installation et les mises à jour.

Deux chemins, un choix

Quésaco?

Squirrel est un outil facilitant l'installation et la mise à jour d'une application bureau sur Windows. Squirrel.Windows est maintenue dans GitHub sous la licence MIT.

Obsolète?

L'avenir de Squirrel est incertain. Comme il est indiqué dans le ticket DEPRECATED, sad... #1469, la principale mainteneuse du projet ne semble pas pouvoir continuer à le maintenir. Malgré cela, nous avons décidés d'utiliser Squirrel.Windows.

Squirrel.Windows est un projet stable et fonctionnel. Mais le plus convaincant, c'est que le projet est sous la licence MIT, une licence libre très permissive. Ainsi, nous pouvions très bien faire un fork pour appliquer des correctifs ou adapter l'outil à notre besoin.

De même, il est fort probable qu'une autre société reprenne le flambeau, car la majorité des applications développées avec Electron utilise Squirrel pour installer et mettre à jour sous Windows. Notament des applications bien connues comme Discord, Slack, Teams, ...

1) Adapter l'application de burreau .NET

Contrairement à de nombreux outils similaires où un programme auxiliaire (launcher) qui s'occupe des mises à jour Squirrel.Windows a la particularité que c'est l'application elle-même qui va se mettre à jour. En avantage, on peut adapter le processus de mise à jour à notre besoin. En contrepartie, il faut modifier le code de l'application pour implémenter la mise à jour. Hereusement, Squirrel.Windows par son package NuGet nous facilite le travail.

Pour cette démonstration, nous allons créer une nouvelle application WPF en .NET Framework :

Création d'un projet WPF avec .NET Framework
Lorsque Squirrel.Windows va générer le raccourcis bureau, les exécutables où le nom contient "Squirrel" seront ignorés. Je déconseille fortement d'utiliser le terme "Squirrel" dans le nom du projet. Le ticket GitHub suivant approfondit ce point.

Ensuite, il faut ajouter le package NuGet squirrel.windows:

PM> Install-Package squirrel.windows

Généralement, les applications disposent d'un bouton pour vérifier qu'une mise à jour est disponible. Mais il est aussi possible d'utiliser un timer qui va régulièrement vérifier si une mise à jour est disponible.

Pour simplifier la démonstration, la mise à jour sera effectuée au démarrage de l'application, donc dans App.xaml.cs :

public partial class App : Application
{
    protected override async void OnStartup(StartupEventArgs e)
    {
        using (var updateManager = new UpdateManager(@"C:\SquiDemoReleases"))
        {
            var releaseEntry = await updateManager.UpdateApp();
        }
        var main = new MainWindow();
        main.Show();
    }
}
Généralement, l'application et les mises à jour sont déployées sur un serveur web et le chemin d'accès est une URL. Dans l'exemple, les mises à jour seront stockées en local dans le dossier "C:\SquiDemoReleases".

La méthode UpdateApp effectue les actions suivantes :

Il s'agit de la manière la plus simple pour effectuer la mise à jour. Pour les scénarios plus avancées, UpdateManager dispose d'autres méthodes pour décomposer chaque étape.

Une fois l'application mis à jour, il faut que l'application redémarre pour exécuter la dernière version. Pour cela, la méthode UpdateApp retourne comme information la version installée ou nulle si aucune nouvelle version était disponible. Ensuite, la méthode RestartApp permet de redémarrer l'application en dernière version. Ainsi, la méthode OnStartup devient :

protected override async void OnStartup(StartupEventArgs e)
{
    ReleaseEntry releaseEntry;
    using (var updateManager = new UpdateManager(@"C:\SquiDemoReleases"))
    {
        releaseEntry = await updateManager.UpdateApp();
    }
    // Restart after UpdateManager is disposed
    if (releaseEntry != null)
    {
        UpdateManager.RestartApp();
    }

    var main = new MainWindow();
    main.Show();
}
Il est nécessaire de libérer les instances UpdateManager avant de redémarrer l'application. Le ticket GitHub suivant approfondit ce point.

2) Générer l'application

Pour générer l'application et les mises à jour, il faut dans un premier temps générer un package NuGet intermédiaire contenant l'application. Le plus simple étant d'ajouter au projet un fichier .nuspec :

<?xml version="1.0"?>
    <package>
    <metadata>
        <id>SquiDemoId</id>
        <title>SquiDemo Title</title>
        <version>1.0.0</version>
        <description>Demo with Squirrel.Windows</description>
        <authors>Space Monkey</authors>
    </metadata>
    <files>
        <file src="*.*" target="lib\net45\" exclude="*.pdb;*.vshost.*"/>
    </files>
</package>

La balise "id" sert à définir plusieurs paramètres comme le dossier d'installation. Pour ce paramètre, il faut utiliser uniquement des caractères alphanumériques (même l'espace est proscrit). Par la suite, "[NuGet Id]" indiquera que la valeur provient de ce paramètre.

La balise "title" sert à définir plusieurs paramètres comme le nom de l'application dans Windows. Par la suite, "[NuGet Title]" indiquera que la valeur provient de ce paramètre.

La balise "file" indique les fichiers de l'application. Il est nécessaire de placer les fichiers dans "lib\net45", car Squirrel.Windows récupère les fichiers uniquement dans ce dossier.

D'autres balises nuspec sont utilisées par Squirrel.Windows, vous pouvez retrouver la liste au lien suivant : NuGet Package Metadata

Ensuite, il faut compiler le projet en Release, puis exécuter la commande suivante pour générer le package intermédiaire (dans l'exemple "SquiDemoId.1.0.0.nupkg") :

PM> nuget pack SquiDemo.nuspec -BasePath .\bin\Release

Finalement, la commande suivante permet de générer les fichiers pour déployer et mettre à jour l'application :

PM> Squirrel --releasify SquiDemoId.1.0.0.nupkg --releaseDir="C:\SquiDemoReleases"
L'instruction Squirrel provient du package NuGet Squirrel.Windows. Elle est donc disponible depuis Package Manager Console dans Visual Studio. Il est aussi possible de télécharger le package NuGet Squirrel.Windows et d'extraire l'utilitaire.

Cela va générer les fichiers suivants :

3) Déployer l'application (optionnel)

Le fichier Setup.msi permet de déployer l'application à l'ensemble des utilisateurs du poste. Il n'installe pas l'application, mais lorsqu'un utilisateur ouvrira sa session l'installation démarrera automatiquement. C'est la méthode à privilégier pour déployer par Group Policy ou avec un outil de gestion de parc comme Microsoft Intune.

Il déploie le fichier "Setup.exe" en :

C:\Program Files (x86)\[NuGet Title] Deployment\[NuGet Id]DeploymentTool.exe

Ce qui donne dans notre exemple :

C:\Program Files (x86)\SquiDemo Title Deployment\SquiDemoIdDeploymentTool.exe

Ensuite, il ajouter à la clé de registre :

"HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Run\[NuGet Id]Deployment" :
    "C:\Program Files (x86)\[NuGet Title]] Deployment\[NuGet Id]DeploymentTool.exe" --checkInstall

Ce qui donne dans notre exemple :

"HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Run\SquiDemoIdDeployment" :
    "C:\Program Files (x86)\SquiDemo Title Deployment\SquiDemoIdDeploymentTool.exe" --checkInstall

Cette clé de registre permet d'exécuter à chaque ouverture de session d'un utilisateur la commande ci-dessus. La commande quant à elle permet de vérifier si l'application est installée et dans le cas échéant d'effectuer l'installation.

4) Installer l'application

Le fichier Setup.exe permet d'installer l'application. L'installation va :

L'application est installée et peut-être démarrer à partir du raccourcis présent sur le bureau.

5) Mise à jour

Maintenant que l'application est installée, il est possible de déployer une mise à jour. On commence par générer le package intermédiaire en précisant la nouvelle version :

PM> nuget pack SquiDemo.nuspec -BasePath .\bin\Release -Version 1.1.0

Puis, on génére la nouvelle version :

PM> Squirrel --releasify SquiDemoId.1.1.0.nupkg --releaseDir="C:\SquiDemoReleases"

Cela va générer les fichiers suivants :

Maintenant que la mise à jour est disponible, au prochain démarrage l'application se mettra à jour.

6) Désintallation

Comme l'application a été enregistrée dans Windows, il est possible de la désinstaller à partir "Programs and Features".

Aussi, il est possible d'exécuter la commande :

"%LocalAppData%\[NuGet Id]\Update.exe" --uninstall .

La désinstallation supprime tous les fichiers or ni "%LocalAppData%\[NuGet Id]\Update.exe". Le ticket GitHub suivant approfondit ce point.

Squirrel, it's like ClickOnce but Works