Squirrel / Squirrel.Windows

An installation and update framework for Windows desktop apps
MIT License
7.31k stars 1.03k forks source link

Inconsistent config file after update. #198

Closed theofanis closed 5 years ago

theofanis commented 9 years ago

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 of Properties.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?

theofanis commented 9 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.

anaisbetts commented 9 years ago

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?

theofanis commented 9 years ago

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.

anaisbetts commented 9 years ago

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

mshumilov commented 9 years ago

Yeah, Settings.Upgrade() and Settings.GetPreviousVersion() do not work with Squirrel. That is confusing.

garyjohnson commented 9 years ago

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.

garyjohnson commented 9 years ago

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)

theofanis commented 9 years ago

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.

amirvt commented 7 years ago

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.

Slion commented 7 years ago

Moving away from ClickOnce to Squirrel has been rather flawless up to that point but this one is a real bummer.

Slion commented 7 years ago

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.

Slion commented 7 years ago

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) {}

    }
markfox1 commented 7 years ago

I just implemented @Slion's solution in Visual Basic. The translation was straightforward. Its a very simple and slick way to solve the problem.

Mierenga commented 7 years ago

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.

MrBjorn commented 6 years ago

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.

Thieum commented 5 years ago

@shiftkey it's a common scenario that would benefit from a PR imho. Would tag as a feature-request.

shiftkey commented 5 years ago

@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.

cbordeman commented 4 years ago

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.

jefferous commented 2 years ago

Is this something that Squirrel would add into it's base code?