A while ago I needed to move a ClickOnce installation to a new URL based on an update I was performing. Now, anyone who is familiar with ClickOnce knows you can migrate an application to a new URL inside Visual Studio by setting up the new "Update Location" in the settings dialog below:
In the past, I have bookmarked and followed Robin's guide on how to move a ClickOnce deployment using this standard method. Robin's blog is a one-stop shop for any technical information regarding ClickOnce.
However, guess what? It turns out this method does not work if you want to change the CPU type of the application (which is what I wanted to do) or if your certificate has already expired. I wanted to change my application from AnyCPU to x86. Apparently there is nothing you can do about this because the process architecture setting is part of the ClickOnce deployment manifest.
Basically I was stuck!!
And then I found this stack overflow post about uninstalling a click once application silently. The accepted answer on there links to a project on Github. It's a small project that was used by the Wunderlist app. The project is great and the code silently finds the un-install process from the registry and silently executes it in the background of the application.
I used this to un-install my ClickOnce app and then kick-off the installer for the new version of the app at a second location. It's so useful, if I ever need to deploy another ClickOnce application, I think I will include this code by default, and wire it up to the database.
Problems
However, there were some users that the code failed for. It turns out (for reasons I won't go into) that some of the applications had different names!! This tripped up the finding of the uninstaller code. So I forked the GitHub repository and added a function to find the uninstaller by the ClickOnce application URL. The code I added can be found here and my pull request is here.
Final Solution
Using this library, my final solution for this problem looked something like this:
private void MigrateToNewUrl() { try { // Step 1: Get Uninstaller Location var location = "file://someserver/somedirectory/application/appname.application"; var uninstallInfo = UninstallInfo.FindByInstallerUrl(location); if (uninstallInfo == null) { MessageBox.Show("Could not find application to uninstall"); return; } // Step 2: Start Silent Uninstall Process var uninstaller = new Uninstaller(); uninstaller.Uninstall(uninstallInfo); // Step 3: Start Install of new ClickOnce deployment Process.Start(@"\\someserver2\somedirectory2\application\appname.application"); Application.Exit(); } catch (Exception ex) { Logger.LogException(ex, MethodBase.GetCurrentMethod().Name, SystemInformation.UserName); } }
Upgrading from the database
The above method can then be called from the MainForm like so:
private void MainForm_Load(object sender, EventArgs e) { try { string sql = "select [value] from appsettings where name = 'MigrateApp'"; if ( m_dbConn.ExecSqlCommandScalar<int>(sql) == 1 ) { sql = "select PropertyValue from AppSettings where PropertyName = 'NewLocation'"; var newLocation = m_dbConn.ExecSqlCommandScalar<string>(sql); MigrateToNewUrl(newLocation); } } catch (Exception ex) { Logger.LogException(ex, MethodBase.GetCurrentMethod().Name, SystemInformation.UserName); } }
On start-up of the application, the above code checks the database to see if the app has been migrated to a second location and performs the un-install and setup process automatically.