Squirrel – replace ClickOnce the easy way
Last week I was taking a look at Squirrel which states
Squirrel: It’s like ClickOnce but Works™
The goal of me looking at Squirrel was to find a decent replacement to ClickOnce, I read a tweet from @paulcbetts mentioning his work and how he had created Squirrel as a replacement to using ClickOnce.
Having spent some time implementing this and coming across one or two issues which I overcame, the good news is ClickOnce is hopefully no longer needed.
Squirrel works and does exactly what we require, our windows forms application’s can have a ‘check for updates’ option, which updates to the latest release, close the old application down after its updated, re-open from the original desktop or start menu shortcut and your done.
Steps to get Squirrel working
- Sourcecode is on GitHub.
- Create a simple Windows Forms Application
- Add the nuget package called squirrel.windows
like so:-
- Add the following to the program.cs like so (line 19 is the only one I added) :-
[sourcecode language=”csharp”]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;using Squirrel;
namespace WindowsFormsApplication1
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread] static void Main()
{
SquirrelAwareApp.HandleEvents(onAppUpdate: Form1.OnAppUpdate,
onAppUninstall: Form1.OnAppUninstall,
onInitialInstall: Form1.OnInitialInstall);Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}
[/sourcecode] - Add some content like a simple button or a menu with the choice to check for updates
- in the code behind we implement squirrel like so:-
[sourcecode language=”csharp”]
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;using System.Configuration;
using Squirrel;
using System.Reflection;
using System.IO;namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
private const ShortcutLocation DefaultLocations = ShortcutLocation.StartMenu
| ShortcutLocation.Desktop;public Form1()
{
InitializeComponent();
}private void checkForUpdatesToolStripMenuItem_Click(object sender, EventArgs e)
{
// Check for Squirrel Updates
var t = UpdateApp();
}public async Task UpdateApp()
{
var updatePath = ConfigurationManager.AppSettings["UpdatePathFolder"];
var packageId = ConfigurationManager.AppSettings["PackageID"];using (var mgr = new UpdateManager(updatePath, packageId, FrameworkVersion.Net45))
{
var updates = await mgr.CheckForUpdate();
if (updates.ReleasesToApply.Any())
{
var lastVersion = updates.ReleasesToApply.OrderBy(x => x.Version).Last();
await mgr.DownloadReleases(new[] { lastVersion });
await mgr.ApplyReleases(updates);
await mgr.UpdateApp();MessageBox.Show("The application has been updated – please close and restart.");
}
else
{
MessageBox.Show("No Updates are available at this time.");
}
}
}public static void OnAppUpdate(Version version)
{
// Could use this to do stuff here too.
}public static void OnInitialInstall(Version version)
{
var exePath = Assembly.GetEntryAssembly().Location;
string appName = Path.GetFileName(exePath);var updatePath = ConfigurationManager.AppSettings["UpdatePathFolder"];
var packageId = ConfigurationManager.AppSettings["PackageID"];using (var mgr = new UpdateManager(updatePath, packageId, FrameworkVersion.Net45))
{// Create Desktop and Start Menu shortcuts
mgr.CreateShortcutsForExecutable(appName, DefaultLocations, false);
}
}public static void OnAppUninstall(Version version)
{
var exePath = Assembly.GetEntryAssembly().Location;
string appName = Path.GetFileName(exePath);var updatePath = ConfigurationManager.AppSettings["UpdatePathFolder"];
var packageId = ConfigurationManager.AppSettings["PackageID"];using (var mgr = new UpdateManager(updatePath, packageId, FrameworkVersion.Net45))
{
// Remove Desktop and Start Menu shortcuts
mgr.RemoveShortcutsForExecutable(appName, DefaultLocations);
}
}
}
}
[/sourcecode] - We now need to create a .nuspec file which we will use to package up our application, Squirrel needs to have every file within the lib\net45 folder.
To create a .nuspec file I simply copied nuget.exe into the solution and then went to a command prompt and typed:- nuget spec, this creates a .nuspec file which we can then hand edit inside Visual Studio and looks like this:-
[sourcecode language=”csharp”] <?xml version="1.0"?>
<package >
<metadata>
<id>WindowsFormsAppliation1</id>
<version>1.0.0.0</version>
<authors>Gregor Suttie</authors>
<title>WindowsFormsAppliation1 Squirrel Tester</title>
<description>Testing out Squirrel</description>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<releaseNotes>None</releaseNotes>
<copyright>Copyright 2015</copyright>
</metadata>
<files>
<file src="bin\Debug\*.*" target="lib\net45\"></file>
</files>
</package>
[/sourcecode] - Next we need to pack the .nuspec file to generate the nuget package, so run the following:-
Nuget pack WindowsFormsApplication1.nuspec - From the package manager console inside Visual Studio we now need to run the following command:-
squirrel –releasify C:\Squirrel\WindowsFormsApplication1\WindowsFormsApplication1\WindowsFormsApplication1.1.0.0.0.nupkg
- In the folder above we should now have a folder called Releases, containing a setup.exe which we use to install the application.
- Run setup.exe and your app will install with shortcuts on the desktop and the start menu if you use the code above which has defined these as shortcut options.
- If you then run check for updates, the code will go off and check the folder you specify in the app.config like so:-
[sourcecode language=”csharp”]
<add key="UpdatePathFolder" value="c:\updatesForMyApp\"/>
[/sourcecode] - In order to update our app we also need the following appsetting which is the id of the Nuget package we created earlier, be careful not to have the id with a space or a dot (.) within the Nuget package Id.:-
[sourcecode language=”csharp”] <add key="PackageID" value="WindowsFormsApplication1"/>
[/sourcecode] - Also add this one line to the assmblyinfo.cs:-
[sourcecode language=”csharp”] [assembly: AssemblyMetadata("SquirrelAwareVersion", "1")] [/sourcecode] - Once you install the application, go back and make a change to the code, save it, update the .nuspec file version number from 1.0.0 to 1.0.1 and then redo steps 7 and 8 (with the path to the new WindowsFormsApplication1.1.0.0.1.nupkg file)
- Almost done, copy the content of the releases folder to our location where we store the updates (our app.config setting for UpdatePathFolder which was set to c:\updatesForMyApp\)
- Run the app and choose the menu option ‘Check for Updates’ and you’ll get the latest version.
- Also note of you add a Debugger.Launch() statement in the code you can debug the code and step through line by line.
To find out more about further options available and much more about Squirrel click here.