When you're ready to distribute your Electron based application to the millions of people waiting to get a hold of your creation, you're going to need a way to package your app and make it available to them with a nice easy installer. You could take a look at Wix or other installers, but the standard for Electron based apps has quickly become Squirrel. Visual Studio Code and the Slack client are a couple of high profile apps that use this framework to perform installs and updates. In this post I'll walk you through creating a basic Windows distribution of an Electron based app.

*** UPDATE *** Electron and Squirrel are evolving quite rapidly, and I fear some of what I wrote here is out of date. Please check http://electron.atom.io/docs/v0.36.8/api/auto-updater/ and https://github.com/atom/grunt-electron-installer for more information on how to accomplish these tasks.

Setting the Stage

If you are new to Electron, check out my previous post Getting Started with Electron in Visual Studio Code. I'm going to assume the structure of your project will be setup this way. If you have things setup differently you may need to adjust paths and such, but you should still gain some value from this post.

Within your project's root directory, create a folder called Staging. This folder will house the tools and files we need to create a Windows distribution.

Next, download the Squirrel.Windows binaries off GitHub here. At the time of this post, Squirrel.Windows 1.0 Preview 2 is the current release, you may need to adjust some steps if they have changed things since. Extract these files and put them in the Staging folder you created.

Squirrel.Windows is built on NuGet. To create the NuGet packages that Squirrel.Windows uses download the NuGet command line tool from: http://www.nuget.org/nuget.exe and plop it into the same Staging folder.

You should now have everything you need to get started.

Handling Squirrel Events in Your "SquirrelAwareVersion" of Electron

If you take a look at the resources in electron.exe you'll see that there is a flag set in the Version Info section that states the executable is a "SquirrelAwareVersion":

Capture

This tells Squirrel that the executable knows how to handle the Squirrel Events that happen when an app is installed, updated, uninstalled, obsoleted, etc... This fact is not well documented in Squirrel.Windows' documentation and it took me awhile to understand, but having this flag set changes the default behavior of Squirrel by passing the responsibility of creating things like shortcuts to the app itself.

When Squirrel is run, it will execute the "Squirrel Aware" executable it finds passing event flags as arguments. These event flags must be handled in your program's entry point proceeded by an immediate exit. That being said, we need to modify the main.js file in our Electron app to handle these events. At the top of your main.js before anything else is called copy this chunk of code in:

var app = require('app');

var path = require('path');

var cp = require('child_process');



var handleSquirrelEvent = function() {

   if (process.platform != 'win32') {

      return false;

   }



   function executeSquirrelCommand(args, done) {

      var updateDotExe = path.resolve(path.dirname(process.execPath), 

         '..', 'update.exe');

      var child = cp.spawn(updateDotExe, args, { detached: true });

      child.on('close', function(code) {

         done();

      });

   };



   function install(done) {

      var target = path.basename(process.execPath);

      executeSquirrelCommand(["--createShortcut", target], done);

   };



   function uninstall(done) {

      var target = path.basename(process.execPath);

      executeSquirrelCommand(["--removeShortcut", target], done);

   };



   var squirrelEvent = process.argv[1];

   switch (squirrelEvent) {

      case '--squirrel-install':

         install(app.quit);

         return true;

      case '--squirrel-updated':

         install(app.quit);

         return true;

      case '--squirrel-obsolete':

         app.quit();

         return true;

      case '--squirrel-uninstall':

         uninstall(app.quit);

         return true;

   }



   return false;

};



if (handleSquirrelEvent()) {

   return;

}

The code above will tell Squirrel on install/updated events to add shortcuts to your executable, and to remove them on an uninstall event. The Squirrel Update.exe executable knows how to do this for you. You can do additional things here other than creating shortcuts. For example, Visual Studio Code adds registry values to "Open With Code" when you right-click on something in Windows Explorer.

Branding Electron

UPDATE: Please read this section, but a lot of what I talk about here can be done via electron-packager. See my brief post Using Electron-Packager to Package and Electron App and then return to this tutorial. Note that later in this tutorial you will need to adjust the <file> locations in the .nuspec file to point to the packages created by electron-packager. Contact me if you need clarification on how to do this.

If you were to install your app with the default electron.exe the shortcut and other references to your app will show up as Electron with the Electron icon. You need to modify the name and resources of the Electron executable to brand it to your application. Squirrel also uses these resources to create shortcuts and the like. Check out the documentation here for more information on naming conventions in Squirrel.

The executable you want to modify is found at <project_folder>/node_modules/electron-prebuilt/dist/electron.exe. Copy and paste this file to the same directory and rename it to <your_app_name>.exe.

To change the executable's icon and the version info you could probably use rcedit.exe packaged with Squirrel. I found it a bit easier to user Resource Hacker following the directions in this tutorial: Stupid Geek Tricks: How to Modify the Icon of an .Exe File. If you need a .ico icon file to test with feel free to use this one:

neutron

While you are editing the icon, also edit the the Version Info resource to match your application. This is important for the Squirrel installer. When you save your changes in Resource Hacker, it will create a <your_app_name>_original.exe. Delete that as you should still have electron.exe in the same folder. Note: when you change the icon Windows Explorer might not update the icon you see attached to your new .exe. This is OK, don't be alarmed.

Creating the NuGet Package

As previously noted, Squirrel.Windows is built on NuGet. You need to package up the files you want in a NuGet package and then use Squirrel to create an installer. You should already have nuget.exe in your Staging directory. Open that folder in a console and run the following command:

nuget spec

This will create a default Package.nuspec file. Open this file in an editor and modify it to look something like this:

<?xml version="1.0"?>

<package >

 <metadata>

  <id>MyAppNoSpaces</id>

  <version>0.1.0</version>

  <authors>My Name</authors>

  <iconUrl>http://mywebsite.com/icon.png</iconUrl>

  <description>MyAppNoSpaces: Does this.</description>

  <releaseNotes>Consider release notes using markdown.</releaseNotes>

  <copyright>Copyright 2015</copyright>

  <dependencies>

   <!-- important: no dependencies here -->

  </dependencies>

 </metadata>

 <files>

  <file src="Squirrel.exe" target="lib/net45" /> 

  <file src="..\node_modules\electron-prebuilt\dist\**\*.*" 

        exclude="..\node_modules\electron-prebuilt\dist\electron.exe;..\node_modules\electron-prebuilt\dist\resources\default_app\**\*.*"

        target="lib/net45" /> <!-- lib/net45 is important -->

  <file src="..\app\**\*.*" target="lib/net45/resources/app" /> 

 </files>

</package>

Check out the nuspec reference page for additional information on creating nuspec files.

Important notes:

  • According to Squirrel's documentation you must put the target folder as lib/net45. Otherwise it will not work.
  • You'll want to make make sure there are no dependencies in the <dependencies> tags.
  • For more hacky caveats check out the documentation here: https://github.com/Squirrel/Squirrel.Windows/blob/master/docs/getting-started.md.
  • The <description> and <iconUrl> will be used to register the application with Windows. You should see them if you go to the Add/Remove/Uninstall program dialog in Windows.
  • We excude electron.exe because the executable we created should be in the dist folder. If you put it somewhere else, add a file path to it to ensure it is included.
  • You should also consider adding any node_modules you need that are not in the /app folder.

Next, create a NuGet package using the following command:

nuget pack Package.nuspec

You should now have a <my_app_name>.<version>.nupkg in your Staging directory.

Creating the Installer

Now that  you have a NuGet package containing your application you are ready to create an installer using Squirrel. Open up a console in the Staging directory if you haven't already and run the following command:

squirrel --releasify <my_app_name>.<version>.nupkg

After it spins for a bit it will create a Releases folder with 3 files: your NuGet package, a RELEASES file, and a Setup.exe. This is all you will need to send to your users for installation. Note that you can modify the above command to change Setup.exe to use a custom icon or display an image during installation.

Go ahead and run the Setup.exe on your local machine. Squirrel will launch quickly followed by your app showing up and shortcuts being added to the desktop and start menu. Your application will be installed to the current user's %LocalAppData% folder. In Windows 8.1 this is: C:\Users\<user_name>\AppData\Local\<app_name>. Note that log files for Squirrel are also located there and they can be useful when debugging installation issues.

Uninstall you application by going to the "Programs and Features" dialog in Windows. Ensure the shortcuts added to the desktop and start menu are properly removed.

Updates

Now that the user has an installer they can install your app and use it, but when you have updates to feed them Squirrel makes things pretty easy. Follow these steps to create an update:

  1. Open the same Staging folder you created your initial version in.
  2. Modify the Package.nuspec file and increment the number in the <version> tags to match your new release. You should consider updating this in the Version Info resource of your copy of Electron's executable.
  3. Rerun the command that creates the .nupkg file, e.g. nuget pack.
  4. Rerun the command that creates the installer on the new .nupkg file, e.g. squirrel --releasify
  5. Distribute the contents of the Releases folder you your users.
  6. Profit.

You'll eventually want to prune off old versions of the packages that are no longer used. Also, you don't really have to distribute the old NuGet packages, only Setup.exe, RELEASES, and the latest NuGet package. There is a delta created when you run the above procedure between the previous and latest versions. I assume distributing that along with the updated setup will probably speed up installation times.

Automatic Updates

The method above involves distributing a new setup file to your users to perform the update. However, you can also configure your app and Squirrel to run updates in the background. Open up your main.js file and put the following after you have checked for Squirrel events:

var updateDotExe = path.resolve(path.dirname(process.execPath), 

 '..', 'update.exe');

var child = cp.spawn(updateDotExe, 

    ["--update", "http://mywebsite.com/releases"], { detached: true });

child.on('close', function(code) {

    // anything you need to do when update is done.

});

Using the above script, If Squirrel finds an update at the URL specified then it will automatically download and update the program in the background. The next time the user launches the application they will run the latest version. It might be better to add a "Check for Updates" button to your app, or bring up a dialog stating updates are available and let the user decide. Here is a crude example of how to do this in the main process:

var dialog = require('dialog');

var updateDotExe = path.resolve(path.dirname(process.execPath), 

 '..', 'update.exe');

var child = cp.spawn(updateDotExe, ["--download", "http://mywebsite.com/releases"], { detached: true });

var stdout = '';

child.stdout.setEncoding('utf8');

var jsonStarted = false;

child.stdout.on('data', function (d) {

 if (!jsonStarted && d.startsWith("{")) {

  jsonStarted = true;

  return stdout += d; 

 }  

 if (!jsonStarted) {

  return;

 } 

 return stdout += d; 

});

child.on('close', function(code) {

 if (stdout.length > 0) {

  var data = JSON.parse(stdout); 

  dialog.showMessageBox({ message: "Update to version " + data.futureVersion + "?",

   buttons: ["Update", "Not Now"] }, function (choice) {

    if (choice === 0) { 

     var child = cp.spawn(updateDotExe, ["--update", "http://mywebsite.com/releases"], { detached: true });

     dialog.showMessageBox({ message: "The update should be available next time you start the application.",

      buttons: ["Awesome"] });

    }

  });

 }

});

Of course you might want to just run the updater and then restart the application. I did mention this is a crude example?

Additional Resources

I had some difficulties understanding how Squirrel works to update an Electron app. Solutions to most of the difficulties I had should be captured in this post, but you should probable take a look at the Squirrel.Windows documentation here: https://github.com/Squirrel/Squirrel.Windows/tree/master/docs

There is a pretty good video about using Squirrel.Windows for .NET applications here: http://channel9.msdn.com/Events/dotnetConf/2015/Squirrel-for-Windows-installing-NET-apps-the-way-it-should-be Some of the concepts discussed carry over to Electron based applications, as Slack application the presenter uses Squirrel.Windows for is Electron based.

If you are using Grunt there is a utility here: https://github.com/atom/grunt-electron-installer to create Squirrel installers for Electron apps automatically. I haven't given this a try, but I would assume it would be better than doing things by hand.

In Closing

It took me quite awhile to understand the caveats of using Squirrel.Windows to distribute an Electron app. The documentation is clearly geared towards .NET developers, but I was able to piece it together in the end. I hope there is something in this post that you find useful.

If there is something with Squirrel or Electron you would like me to research and turn into a post please let me know. Thanks for reading!