Closed theofanis closed 5 years ago
Here's what happens.
User settings for my app are saved in user.config in a folder under AppData
which follows the pattern MyCompany\MyApp_HashBasedOnEvidence\Version
.
But, if the app is executed from a different directory than before, then a new MyApp_HashBasedOnEvidence
subfolder will be created.
This is apparent with using squirrel as well, since each updated version is run from a different folder that follows the pattern app-<version>
. So, after running the app after an update took place, a new user.config will be created under a new MyApp_HashBasedOnEvidence
subfolder and there the usual Properties.Settings.Default.Upgrade()
doesn't work since it doesn't find the previous version.
Finally, the user's machive will get to a state where each app version, creates a new MyApp_HashBasedOnEvidence
with the version folder under it, that will look like this:
AppData\MyCompany\MyApp_HashBasedOnEvidence1\1.0.0.0
AppData\MyCompany\MyApp_HashBasedOnEvidence2\1.0.1.0
AppData\MyCompany\MyApp_HashBasedOnEvidence3\1.0.2.0
...
So now, user.config is useless the way it works by default and the developer has to take care of settings upgrade when updating the app.
I should either create some logic myself to port the settings I want to the new config file, or dump the Application settings and use a custom settings file, probably saved in AppData\MyAppData.
Yeah, the easiest way to do this is to handle Squirrel events - this gives you a chance to run code on both update and obsolete (i.e. telling the old version it's no longer the latest version)
But, if the app is executed from a different directory than before, then a new MyApp_HashBasedOnEvidence subfolder will be created.
Maybe don't do that then? Why do you need to have this kind of folder scheme rather than just %AppData%\MyCompany\MyApp?
Maybe I didn't describe it clearly, I'm sorry if my previous comment was confusing.
Squirrel installs each version in a different subdirectory. For example, AppData\MyApp\app-1.0.0\
, AppData\MyApp\app-1.1.0\
, AppData\MyApp\app-1.2.0\
etc.
The first time an executable runs, a user.config is created in a default location which follows the scheme I described in my previous post. That's how Windows does it, not me. That's the same way ClickOnce did it, but it didn't save the config files in AppData but in ClickOnce Data Directory, following a similar scheme, but it didn't create a MyApp_HashBasedOnEvidence
subfolder for each version, but used the same one and placed subfolders for each version under it, so the result was something like this:
MyApp_HashBasedOnEvidence\1.0.0.0\user.config
MyApp_HashBasedOnEvidence\1.1.0.0\user.config
MyApp_HashBasedOnEvidence\1.2.0.0\user.config
In Squirrel's case, there's a different MyApp_HashBasedOnEvidence
subfolder for each version, after the user executes it for the first time.
Because of this, ClickOnce was able to use Settings.Upgrade()
and merge the older version's user.config with the latest version's user.config. But with Squirrel this is useless, since those files don't exist under the same MyApp_HashBasedOnEvidence
umbrella.
I took care of this in my code, by manually creating a copy of user.config onAppUpdate
and writing it in the correct (newly created) folder when the updated .exe runs.
I don't think Squirrel should do something similar to my workaround, but I think this would be much easier for the developer to just call Settngs.Upgrade()
if Squirrel placed the latest version of the app inside the same subfolder every time. This of course, cannot be done when updating, but maybe Update.exe
could do this, before it executes the latest version.
The first time an executable runs, a user.config is created in a default location which follows the scheme I described in my previous post. That's how Windows does it, not me.
Ah, this is a built-in .NET thing, now that makes more sense.
I don't think Squirrel should do something similar to my workaround, but I think this would be much easier for the developer to just call Settngs.Upgrade() if Squirrel placed the latest version of the app inside the same subfolder every time.
A core part of how we never have to reboot / restart apps is that we never try to replace DLLs / EXEs, this is definitely not possible. I know it sucks, but you might consider moving off of Settings
and onto a different way to store your data? Serializing to JSON is pretty easy, or if you have more interesting settings, Akavache works great too
Yeah, Settings.Upgrade() and Settings.GetPreviousVersion() do not work with Squirrel. That is confusing.
Just ran into the same issue. It's too bad this breaks a built-in .NET thing -- is there an opportunity to maybe run the app out of a symlinked dir? Like, AppData\MyApp\app-current\
is symlinked to the correct version (AppData\MyApp\app-1.0.0\
) and the link is swapped to a newer dir on update. I'm not sure if this possible on account of how Squirrel works but may prevent .NET apps from creating a new dir for user.config. I can experiment with this when I get some free time.
An alternative solution might be to strong-name sign the executable. This link suggests that the path doesn't matter in this case. (EDIT: would need a strong-named version of Squirrel to test this)
I'm already moving to Akavache full time for this, since I already employed it for replacing custom json files for my application's local data, so I simply don't bother anymore about this.
This Stackoverflow answer shows you how to make a custom settings provider with a custom user.config file path. I tested it out and it seems to work.
Moving away from ClickOnce to Squirrel has been rather flawless up to that point but this one is a real bummer.
I wonder if the config file could be copied from the older folder to the new folder during the update. Here is how to get the path to the user.config.
Indeed you can use the methods below to backup your settings, typically just after your update has completed, so just after your call to await mgr.UpdateApp();
You want to restore them at the very beginning of your program, like just after Squirrel's event handler registration. Don't try doing a restore from the onAppUpdate it won't work. By the look of it onAppUpdate is executing from the older app process as it would not get access to the newer app data folder.
/// <summary>
/// Make a backup of our settings.
/// Used to persist settings across updates.
/// </summary>
public static void BackupSettings()
{
string settingsFile = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal).FilePath;
string destination = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "\\..\\last.config";
File.Copy(settingsFile, destination, true);
}
/// <summary>
/// Restore our settings backup if any.
/// Used to persist settings across updates.
/// </summary>
private static void RestoreSettings()
{
//Restore settings after application update
string destFile = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal).FilePath;
string sourceFile = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "\\..\\last.config";
// Check if we have settings that we need to restore
if (!File.Exists(sourceFile))
{
// Nothing we need to do
return;
}
// Create directory as needed
try
{
Directory.CreateDirectory(Path.GetDirectoryName(destFile));
}
catch (Exception) {}
// Copy our backup file in place
try
{
File.Copy(sourceFile, destFile, true);
}
catch (Exception) {}
// Delete backup file
try
{
File.Delete(sourceFile);
}
catch (Exception) {}
}
I just implemented @Slion's solution in Visual Basic. The translation was straightforward. Its a very simple and slick way to solve the problem.
Agreed, this is the best solution I have found for using Properties.Settings
with Squirrel. Thanks, @Silon. One thing I had to be sure to do after calling RestoreSettings()
is make a call to Properties.Settings.Default.Reload()
before attempting to use any of the settings.
Thanks, @Silon. I stumbled over this as well and used your solution. It has its pros and cons the way Squirrel installs an app. I understand the problem with have the same install folder, but have a new folder each time has it problems to.
@shiftkey it's a common scenario that would benefit from a PR imho. Would tag as a feature-request.
@Thieum this actually feels like it's more closer to #47 (which talks about files modified by the user as well as new files added to the install directory) and in alternate technologies I'd use a different directory to store these things, but given how .NET leads you to this situation of having config files inside the app directory then maybe this is worth exploring more.
Indeed you can use the methods below to backup your settings, typically just after your update
Wow, thanks for this. Worked perfectly for me. Although, like others have said, after restoring the file you need to refresh settings.
Is this something that Squirrel would add into it's base code?
I update my app with Squirrel and I call
RestartApp()
.Since the old installation folder is left intact and a new one is created inside the
rootAppDirectory
, the config file is written from scratch. As a result, any settings retained in the old installation's config file, don't migrate to the new config file. This is a problem if the app makes use ofProperties.Settings
to keep application settings.I should either create some logic myself to port the settings I want to the new config file, or dump the Application settings and use a custom settings file, probably saved in
AppData\MyAppData
.Am I missing something?