Getting Squirrelly...again

Previously

I first investigated Squirrel.Windows as a technology for Windows application deployment two years ago during a search for an alternative to ClickOnce (my 2 cents). I documented some of those efforts in my post ClickOnce, Squirrel and Nuts. In that post I expressed a very favorable opinion of Squirrel but I also I noted some areas of dissatisfaction in the discussion and conclusions.

  • Latest version of the deployed application was not always run
  • Cleanup of previous application versions wasn't working
  • Instability with releasify command
  • Reliance on historical .nupkg files from the Releases folder

Lately

Recently I again went in search of ClickOnce replacement options. It became quickly apparent not much had changed. Squirrel still looked to me to be the best available alternative so I took another dive. I examined the state of the issues previously discussed then developed a basic strategy for controlling Squirrel deployed releases.

Latest Version Issue

The first issue can now be resolved through the use of a simple static method on the class Squirrel.UpdateManager.

UpdateManager.RestartApp("{app_exe_name}");  

This works quite well and if added to an application's start-up, after a Squirrel update but before the rendering of the view, users will always be presented with the latest version of the application. Very nice.

Cleanup Issue

Application cleanup was still an issue when I started evaluating Squirrel the second time. In looking through GitHub issues for Squirrel.Windows I found others had the same problem so I attempted to debug it myself. I discovered that this issue was a trival defect. It's been since resolved. Squirrel now cleans older versions of the deployed application leaving only the current and one previous.

Releasify Instability Issue

This issue still exists, however, occurrences have GREATLY diminished. Previously the releasify command would stop working (requiring a Visual Studio restart) every 3-5 executions. During the second evaluation I encountered this issue exactly two times in roughly 100 executions.

Historical .nupkg Files Issue

In my first post I noted the releasify command relies on the output of the previous execution in order to generate delta packages. My concern was this would necessitate source control for binary .nupkg files. That would mean source control of binary files created by compiling code files stored in the same source control project. That's far from ideal.

Over the course of the second evaluation I developed a simple strategy to build and manage Squirrel deployment releases. This strategy addressed my concerns related to historical .nupkg files.

Squirrel Deployments Release Strategy

I developed the following strategy to provide controlled releases to Squirrel deployments. It assumes access to both a source control system and some form of shared file (binary) storage. Ideally both should be covered under some type of backup policy.

Setup and Initial Release

As described in Squirrel's Getting Started Guide the Squirrel.Windows NuGet package should installed in the start-up project of your Windows application.

In the static start-up of your application add code to check for available updates, apply any updates found and restart the application if updates were applied. All this should be performed prior to rendering the display. This ensures the user is always presented with the latest version of the application. The following code sample illustrates this method for a Windows Forms Application.

static class Program  
{
    [STAThread]
    static void Main()
    {
        Task.Run(() => CheckAndApplyUpdate()).GetAwaiter().GetResult();
        Application.EnableVisualStyles();       
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }

    public static async Task CheckAndApplyUpdate()
    {
        bool updated = false;
        using (var updateManager = new UpdateManager(URI))
        {
            var updateInfo = await updateManager.CheckForUpdate();
            if (updateInfo.ReleasesToApply != null && 
                updateInfo.ReleasesToApply.Count > 0)
            {
                var releaseEntry = await updateManager.UpdateApp();
                updated = true;
            }
        }
        if (updated) { UpdateManager.RestartApp(EXE_NAME); }
    }

    private const string EXE_NAME = "WindowsFormsApp2.exe";
    private const string URI = "{URI to Releases folder}";
}

Add a {project}.nuspec file to the start-up project (be sure it's also added to source control). Include only file entries for the assemblies that are necessary to run the application through the Squirrel update and restart. Excluded all assemblies not required to get to that point. This ensures that the setup.exe file created by the initial releasify command will be as small as possible. Following is sample content for a .nuspec file's packages element. Note: The description element is required and its value is used by Windows search features to find the application.

<metadata>  
  <id>WindowsFormsApp2</id>
  <version>1.0.0</version>
  <authors>Dane Vinson</authors>
  <requireLicenseAcceptance>false</requireLicenseAcceptance>
  <description>WindowsFormsApp2</description>
</metadata>  
<files>  
  <file src=".\bin\Release\WindowsFormsApp2.exe" 
        target="lib\net45\WindowsFormsApp2.exe" />
  <file src=".\bin\Release\WindowsFormsApp2.exe.config" 
        target="lib\net45\WindowsFormsApp2.exe.config" />
  <file src=".\bin\Release\DeltaCompressionDotNet.dll" 
        target="lib\net45\DeltaCompressionDotNet.dll" />
  <file src=".\bin\Release\DeltaCompressionDotNet.MsDelta.dll" 
        target="lib\net45\DeltaCompressionDotNet.MsDelta.dll" />
  <file src=".\bin\Release\DeltaCompressionDotNet.PatchApi.dll" 
        target="lib\net45\DeltaCompressionDotNet.PatchApi.dll" />
  <file src=".\bin\Release\Mono.Cecil.dll" 
        target="lib\net45\Mono.Cecil.dll" />
  <file src=".\bin\Release\NuGet.Squirrel.dll" 
        target="lib\net45\NuGet.Squirrel.dll" />
  <file src=".\bin\Release\SharpCompress.dll" 
        target="lib\net45\SharpCompress.dll" />
  <file src=".\bin\Release\Splat.dll" 
        target="lib\net45\Splat.dll" />
  <file src=".\bin\Releas\Squirrel.dll" 
        target="lib\net45\Squirrel.dll" />
</files>  

Create the initial release

  1. Build the start-up project
  2. Create .nupkg from .nuspec
  3. releasify the .nupkg
  4. Commit changes to .nuspec file

The initial "minified" setup.exe file should deployed to a location accessible to any potential application users. Designed correctly it's possible for this file to serve as the application's primary installer for its lifetime.

The first change to the application should be to add file entries to the .nuspec file for all remaining application dependencies. This should be considered a release. For this and all future releases proceed with the steps below.

Create New Release

Creation of Squirrel deployment releases should follow these steps.

  1. Commit changes for the release
  2. Delete Releases from the build-time environment (if it exists)
  3. Copy Releases from the shared location to the build-time environment
  4. Increment the version element in the .nuspec file
  5. Build the start-up project
  6. Create .nupkg from .nuspec
  7. releasify the .nupkg
  8. Commit changes to .nuspec
  9. Copy newest *.nupkg and RELEASES files from build-time Releases folder to the shared Releases location

If for any reason the shared Releases repository is lost simply re-create an empty Releases folder at the share location and repeat the previous steps. The next time users run the application they'll have to download all files but the application will continue to function correctly. Even a fresh install with the original "minified" setup.exe will correctly update to the latest version of the application. I've found Squirrel to be quite resilient with respect to historical .nupkg files.

Conclusions...the Sequel

Once again I've been impressed with Squirrel. As an application updater it's matched or surpassed ClickOnce in almost every way. The one remaining feature of ClickOnce that Squirrel does not provide is the ability to run the application directly from a browser. I didn't discuss this in my previous post because I felt it fell beyond Squirrel's intent. That said there are numerous benefits to that feature not the least of which is that it eliminates the need for an actual "installer" (e.g. setup.exe).

Finally

I'm currently working towards using Squirrel and the described release strategy to replace ClickOnce in of one of my company's production applications. I'm also working on an idea to provide install/run capabilities directly from the browser but as of now that's only partially formed.