I've written multiple desktop apps from scratch. There's a lot of repeated work that goes into doing it. This is my attempt at a basic framework that will allow any project to get a running start.
It's built around a few assumptions and technology choices, which should be suitable for most projects. Most code is written in C#, targeted against version 2 of the .NET framework. We assume all users will be running at least Windows XP. The installer is written using NSIS. We assume that updates should be done in the background, with no UI or UAC elevation prompts.
This framework comes from a project that is currently supporting over 100k users.
Here is a breakdown of these technology choices. You are free to change them to suit your application, of course. Afterwards is a list of instructions for getting started with your particular project.
- There are two main executables:
- The first is the main client app, which runs as the logged on user, and sits in the Windows system tray.
- The Service is a Windows service, which runs as SYSTEM, and is available to apply automatic updates, or do other things as SYSTEM.
- Users love software that gets better over time, with no effort on their part.
- Our update mechanism is simple, and secure.
- Most updates are applied by the Windows service, running as SYSTEM, so the user doesn't run into any UAC elevation prompts.
- The client app also runs update check code, in case something gets broken in the service.
- The update system assumes that any time is a good time for an updates, which you'll need to modify if there's some user interaction that shouldn't be interrupted.
- All update commands are signed using public key cryptography.
- The updates are downloaded over plain HTTP, but their hashes are checked before execution.
- The update public key is hard coded into the client. The root CA system (and all of its flaws) are bypassed entirely.
- There's a python script included (
make_new.py) that helps you generate your update key pairs.
- The updater is based on an NSIS script, but could be any executable you generate.
- It is responsible for killing the client app, and/or stopping the Windows service.
- It then restarts the service, but should not restart the client app, since it's running as SYSTEM.
- The service then impersonates any users who were running the client and re-launches the client app for each.
- There is logging that's active at all times, and logging that conditionally activates if certain registry values are present.
- To activate the second kind of logging, run
- After doing so, all NSIS scripts (the installer, uninstaller, and updater) will
OutputDebugString()various log messages.
DbgView.exeor a similar app to read, log, and filter them to your heart's content!
- Those registry flags also turn on
OutputDebugString()for client and service logging.
- Otherwise, on all installs, log files are written and rotated using NLog.
- Each install is given a
ClientGuidwhich is kind of like a cookie that lives in the registry.
- It's fairly brittle, but is a basic 80/20 for being able to identify users across web requests to your various web services.
- If you need anything more robust, I'd recommend a user account system.
- Other than update keys, you should consider Authenticode keys, and you'll need to generate a new strong signing key pair.
- The build system assumes you'll put your Authenticode keys in the
- I'd recommend using an Authenticode key for anything other than a quick hobby app.
- We'll walk through generating your own assembly strong signing keys as well, using sn.
- First, download the project to your machine.
cdinto that directory.
python customize.py YourProjectName
YourProjectNameshould not include any spaces.
- This will rename everything that says
- First, we'll generate new strong signing keys.
sn -k YourProjectNameStrongSignKeys.snk
- (You may have to locate
snif it's not in your PATH. It should be in your Visual Studio
- Now we'll generate auto update keys.
rename yourprojectname_client_update_key yourprojectname_client_update_key_BACKUP
- (This folder is mostly there to give a flavor of what the keys look like. We won't need it.)
- Before you run the
make_newpython script, make sure you have
OpenSSLin your PATH. You can test this by running
python make_new.py yourprojectname_client_update_key
- This script creates two update key pairs:
PRODis generated first, followed by
TEST. (Note the line saying "NOW DOING TYPE PROD".)
PROD, you'll need to pick a passphrase used to encrypt your private key.
- Let's assume it's "tutu".
- The script will ask you to provide your passphrase several times.
- I counted four tutu's, followed by the certificate parameters (which really don't matter), followed by the challenge password (must be blank), followed by two tutu's, followed by the name of you p12 file (
YourProjectName PRODthe first time around, then
YourProjectName TESTthe second time around), followed by two more tutu's.
- For your
TESTkey pair, I'd recomment "test" as your passphrase.
- If you weren't comfortable doing this the first time around, just blast away the key directory and run it again!
- I'm sorry about all the run around in the script. There are lots of commands because we need each key in several representations to make all the moving pieces work together. God help you if you want to use one of these key pairs in Java. (Email me for Java Key Store instructions!)
- Now, securely delete all of the files in the new folder hierarchy that say
- And finally, copy and paste the
certificate.pemfile contents of each of
TESTinto the relevant sections at the bottom of
Your first build
- Ensure that an instance of Visual Studio is running on your machine. (This is needed for Dotfuscator.)
- Visual Studio will build your solution.
- Then you'll see Dotfuscator pop up. Wait for the splash screen to go away, hit the play button on the toolbar, wait for obfuscation to finish, then close Dotfuscator.
- You'll see nant continue its thing.
- Builds end up in the
Your first update
- Change something small about the app. We're going to build and publish an update.
- Assuming you're still in the
nantas before. This will produce build 2.
- Note that the updater is designed to be ran by the service, as SYSTEM, in session 0.
- So while you can double click it, its behavior will be a little off from ideal.
- Instead, put it up on a web server somewhere. Let's suppose it's available from
- You need some way to tell the client about the update. This is done through an XML file that lists the executable location, and hash.
- This XML file is signed using the update key pair we just generated.
- To generate this XML file, launch
YourProjectNameUpdateSignerin Visual Studio.
- The update signer app tries to guess the updater location, the download URL, private key, etc.
- Enter the password for your
PRODupdate key. ("tutu" from above.)
- Change the download URL to
- Click Go
- Assuming it says Done!, you can close it now. It created a file called
YourProjectNameUpdateInfo.xmlin the latest build directory.
- Now the client expects to get that XML back when it requests
- If you ever want to change that URL, edit
client/SharedSource/UpdateChecker.cs, but you won't need to do that for this exercise.
- On your test machine, change your Windows hosts file to redirect
updates.yourprojectname.comto 127.0.0.1, and then we'll use
tools/FakeUpdateServerto serve up the XML. Let's walk through that.
- First, copy
FakeUpdateServer.exeand the update info XML file to your test machine's desktop.
FakeUpdateServer.exeas an administrator, so it can listen on port 80.
- Make sure the XML file path in the app points to the update XML file you copied over.
- You can ignore the exe path part. It can be used to serve the updater executable, but we put it on a public Internet server, which is in some ways simpler.
- Make sure the Updates via Service checkbox is checked, and that the other is not. This indicates that the Windows service should get the update command, rather than the client app.
- Add a string registry value under
ImmediateUpdateCheck, and set its value to
- While you're at it, add the
DebugFlags.regvalues and open
DbgView.exe, so you can see the update happen. It's pretty fun to watch!
- Now, using the
Servicesapp under Administrator Tools, restart the
- The logging is pretty verbose. Ten seconds after you restart the service, it will check for an update. The
FakeUpdateServerwill send the update command XML. The service will download the updater, verify its hash, and run it. The updater will shut down the service and client app, update all files, and relaunch the service. The service will relaunch the client app. The whole process will take about thirty seconds.
- You can verify that the update happened by right clicking on
Program Files (x86), going to
Properties, then the
Detailstab. The version should now be 126.96.36.199!
- Despite its name and poor design,
FakeUpdateServerhas been successfully serving updates to over 100,000 clients.
- You can edit the EULA, which is shown during the installer, by replacing
- You can edit your application's icon by replacing
- But you'll also have to go into Visual Studio, and replace the parallel copies in each project's
Properties, and in
Desktop code is a bit of a corner case in today's software development landscape. Web apps have rightfully taken over many types of computing, but there are still corners of our industry that call for desktop apps. Recent examples include Spotify and Dropbox.
Just don't build desktop apps unless it's required for your application, and unless your application is a compelling one. It's always better to update your personal toolchain and explore new applications than exploit current skills.
Even where desktop apps are necessary, a C# framework might be the wrong choice. OS X is on the rise, and a framework built on top of python, C++, or a more portable language might be preferable. Please just don't use Adobe Air!
And if you end up building a desktop app in a portable language, please factor out your app-specific logic and publish DesktopBootstrap++!