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 theReleases
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
- Build the start-up project
- Create
.nupkg
from.nuspec
releasify
the.nupkg
- 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.
- Commit changes for the release
- Delete
Releases
from the build-time environment (if it exists) - Copy
Releases
from the shared location to the build-time environment - Increment the
version
element in the.nuspec
file - Build the start-up project
- Create
.nupkg
from.nuspec
releasify
the.nupkg
- Commit changes to
.nuspec
- Copy newest
*.nupkg
andRELEASES
files from build-timeReleases
folder to the sharedReleases
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.