Saturday, September 6, 2008

ClickOnce deployment or not?

Some weeks ago, I developed a small program in C# called P4 Explorer. To distribute it, i used a technique called ClickOnce deployment, which is a part of Visual Studio C# Express 2008. This blog post will be about what ClickOnce deployments are, how they can be used, and why you in the end shouldn't use them...

ClickOnce
ClickOnce deployments will simplify your deployment a lot. To deploy your application, all you need to do is:
  1. specify the location to publish the application (i.e. a network drive, web site or ftp),
  2. specify from what location the users will install it from (propably the same as 1), and
  3. decide whether the application should be installed at the start menu, or always must be started from the install path (2).
Furhermore you can specify if the application automatically should check for updates, which is really great if you plan to update your application frequently. You then specify how often the it should check for updates, and when an update is found, the user will be notified and can download the latest version. All by only selecting "The application should check for updates". Very nice!

Now, for you to deploy a new version, all you do is click "Publish". When the user then starts the program, the update will be found and installed (if permitted by the user). Very easy!

Limitations
Application Path
ClickOnce deployment doesn't come for free though. First of, the application won't be installed under "C:\Program Files\MyCompany\MySoftware" as you might expect, but at some secret place (hidden from the user and developer). If you decide to let the installer add a shortcut to your start menu, there is no way to find out where this shortcut points, as it's not a normal shortcut. Right clicking on it and selecting properties will not give you the information you expect, i.e. the path to the executable.
Now, there is a simple way of finding out the path of the executable. When starting the installed application, the property Application.ExecutablePath will be correctly set and you can thus be used to, for example, add to the PATH environment variable:
void UpgradePathVariable()
{
string l_ApplicationFolder = Path.GetDirectoryName(Application.ExecutablePath);
string l_PathVariable = Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Machine);
if (!l_PathVariable.Contains(l_ApplicationFolder))
{
l_PathVariable = l_OldPathVariable + ";" + l_ApplicationFolder;
}
Environment.SetEnvironmentVariable("PATH", l_PathVariable, EnvironmentVariableTarget.Machine);
}

Parameters
Also, because the program doesn't have a fixed installation path, there is no support for sending parameters to the program. I.e. args in static void Main(string[] args) will always be empty. The documentation at MSDN states that the only way to send parameters to a click once application is to deploy it at a web page, and let the user send html-parameters to it, i.e.:
http://www.yourdomain.com/yourapplication.exe?parameter1=val1&parameter2=val2
But
  1. this requires your application to be deployed using a web page, and
  2. you must always download the installer every time you need to sent parameters to the program.
Say for example you want to let the user start the program by right clicking on a file in explorer and selecting "Open in my program". You would need to add a line in your registry pointing to the executable file:

pathtoyourexe "%1"

This would be both slow and hacky if using the recommended http-style mentioned above. Instead, by using the Application.ExecutablePath property, you can actually access the physical path to the installed executable, and thus add this path to the registry. With this setup correctly, right clicking on a file and selecting your program to open it with, will open the program and send the parameter correctly to it!

Settings
But, even though everything might seem to work out fine, you will soon notice that the Settings object defined in your application will be different if starting the program using the shortcut from your start menu, or right clicking on a file. They are not sharing the same application settings. And if you upgrade the program, all settings in your "right clicked version" will be gone, because the new version will be installed in a new folder.

In the end
Even though click once applications are really easy to deploy, and give you a lot for free, the limitations just makes them really hard to work with. Making simple applications might work, but as soon as you want the user to interact with it through the explorer shell, you are better off making your own installer (or using a free one such as NSIS). So in the end I decided to deploy my program manually using a source control, which made all my problems go away. Sending parameters worked, settings worked, and the application had a fixed folder!

The only thing I was really missing from ClickOnce was the ability for the program to automatically check for updates, which was something I really needed to make sure everyone using my application had the latest version (hopefully having less bugs in it). But it turned out this wasn't so hard to make on my own! The pseudo code for it, printed here, is divided in two programs - your main application, yourapp.exe, and the upgrade application, upgrade.exe.

yourapp.exe:
  1. Is there a new version available (i.e. check using source control)
  2. Copy upgrade.exe (and dependencies) to a new folder "temp"
  3. Start new process temp\upgrade.exe
  4. Close program
upgrade.exe:
  1. Make sure all instances of yourapp.exe are closed
  2. Download the latest version (i.e. using source control)
  3. Start new process ..\yourapp.exe
  4. Close program
You can always make it faster by running the check for new version in it's own thread, and only checking every x hour, but the main thing is that I get the same features as if running with ClickOnce, but don't need to limit my application as much as you do with ClickOnce applications!

So, ClickOnce, good for small application, not good for more advanced applications.

7 comments:

Anonymous said...

Good idea to implement the update process yourself. ClickOnce is a pain.
Can you give a little more details on how you check the current version of the software with the source control?
T.

Christian Genne said...

I use perforce as a source control system, and in perforce you have a command to retrieve the latest available revision number at a given path; "p4 changes". So I execute this command to find out the latest revision at my program path, then compare this version to a locally stored version (stored in application settings), and if the versions differ I ask the user to update the program.

If the user selects to update the program, I download the latest version (as described in the post), and after it's been downloaded I automatically restart the application again, but this time with the flag "/afterupdate". This tells the program it's been updated to the latest version, and that it should update it's locally stored version to the revision given by p4 changes.

Samme said...

This mirrors my experience with Click Once as well, that it's very inflexible. Have you looked att DDay.Update? Seems like a decent framework for integrating automatic updates into an application. I haven't tried it yet though.

Christian Genne said...

Haven't heard of DDay.Update, but from googling it it looks promising. Though, making your own updater is relatively simple and you also get the freedom to do some more random stuff. Like adding a "do you want to download the latest beta version?" dialog :)

If you test DDay.Update, let me know how it works!

Anonymous said...

l_OldPathVariable is never defined ;-)

viagra online said...

You then specify how often the it should check for updates, and when an update is found, the user will be notified and can download the latest version. All by only selecting "The application should check for updates". Very nice!

carry on luggage size said...

Very helpful for someone like me. This is one of the highly informative and attractive blogs that has not only educated also informed me in a very effective manner. There are very few blog like this one I have read. Thank you for doing such a great job.