21

I've got a ClickOnce application that is leaving all old versions on my disk. It's an internal corporate application that gets frequent updates, so this is a disaster for rapidly inflating our backup size.

According to the documentation and other Stack Overflow questions, it is supposed to only leave the current and previous versions on disk. However, each time I deploy the project and upgrade a client, I get another copy of all EXE, DLL and data files. I'm not making any changes whatsoever to the application, just pushing deploy again in Visual Studio.

How do I fix this problem?

The problem seems to happen on both Windows 7 and Windows XP, as well as 64-bit and 32-bit Windows.

I've done a diff of the folders where the version is installed and the following files are different:

MyApp.exe.manifest
MyApp.exe.cdf-ms
MyDll1.cdf-ms
MyDll2.cdf-ms

No actual executable files are different, nor the MyApp.manifest, MyDll1.manifest, etc.

How about an alternative. Is it safe to look for other folders containing my application at runtime and delete them? Is that going to break anything?

Is ClickOnce just an apparent mysterious black box?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Clyde
  • 8,017
  • 11
  • 56
  • 87
  • To be clear, you mean that all the old versions remain stored on the *client* machine? All the versions should remain in the *deployment location* until/unless you delete them, because you may have different users on different versions at any one time. – Jay May 20 '10 at 13:10
  • Yes, on the client machine, buried in the user folders – Clyde May 20 '10 at 13:28
  • Does it make a difference if you specify a minimum required version number in your publish settings? – Jay May 20 '10 at 14:27
  • No it does not seem to make a difference – Clyde May 20 '10 at 15:51
  • 1
    Are you persisting any user settings or configuration on the client side? This is a shot in the dark -- just wondering if maybe in order to save the settings across upgrades, it has to hold on to the first version in which the settings were created or saved? – Jay May 21 '10 at 01:04

5 Answers5

8

I think I've actually finally figured this one out. For some unknown (and unacceptable, frankly) reason, the cleanup of older versions will not happen if the newer version is running -- it just fails silently.

The cleanup attempts to run as the old version exits, but because I restarted the app, the new version is already starting, blocking the cleanup.

Also, note that the "scavenger service" mentioned in other answers appears to be a total fabrication. I've found no documentation of any such service, nor any evidence of it running. The cleanup appears to happen inline as part of the clickonce update process.

Clyde
  • 8,017
  • 11
  • 56
  • 87
  • We encounter the same problem with our corporate application. Our app is updated in code with 'ApplicationDeployment.CurrentDeployment.Update()'. Not restarting after the update didn't solve the problem, older versions remains on the disk. Did you managed to solve the problem? – Niki Oct 27 '11 at 12:27
1

The scavenger service is part of the ClickOnce engine; it runs automatically, and isn't something you can access directly. It should be coming around and cleaning up the old versions.

Question about the min version. If you deploy new version & set min required version to new version#, does it update the app to that version? Then leaves the former one(s) on disk?

Is there any pattern that you can see? Is there a limit to the number of deployments that it is caching?

There's also something about shadow folders that is tickling the edges of my memory; the files aren't really there. I'll do a little research and look through my notes and see what I can find.

What kind of app is it? WinForms/WPF/VSTO?

When you say it is caching folders, which folder is it? For a winforms app, for example, there are two folders created for each version (xxxxtion... and xxxxexe... or something like that) plus a bunch of folders -- one for each assembly included in the deployment -- these are cached versions of the assemblies, to keep from having to download them every time if they haven't changed. Is it the xxxxtion and xxxxexe folders that you are seeing multiples of?

Sampson
  • 265,109
  • 74
  • 539
  • 565
RobinDotNet
  • 11,723
  • 3
  • 30
  • 33
  • It's a winforms app and does have the two types of folders you described. There are only two "exe" folders, and they contain the application icon, config file, and a chm help file I've got included as application content. It is the "tion" folders that are being endlessly duplicated. Every single one contains the exe, all the dlls, PLUS the files that were in the "exe" folder, including the 10 meg chm file. – Clyde May 24 '10 at 12:31
  • I don't know if it matters, but I do not have the app auto-check version on startup -- that's handled in code using ApplicationDeployment.CurrentDeployment.CheckForDetailedUpdate and then ApplicationDeployment.CurrentDeployment.Update() – Clyde May 24 '10 at 12:44
  • have you tried changing it to auto update and see if it clears out the files? – RobinDotNet May 28 '10 at 18:27
1

This is a function to clean old ClickOnce versions in the client side.

In my machine I've freed 6Gb of space. I don't want to even know the total space used by old versions org wide...

    /// <summary>
    /// Delete directories of old ClickOnce versions leaving newest in place.
    /// NOTE: Users have privileges for installing and deleting folders in their local deployment directory.
    /// </summary>
    public static void CleanOldVersions()
    {
        
        string path = AppDomain.CurrentDomain.BaseDirectory;
        int lastSlash = path.LastIndexOf(@"\");
        path = path.Substring(0, lastSlash );
        lastSlash = path.LastIndexOf(@"\");
        path = path.Substring(0, lastSlash);

        var dirInfo = new DirectoryInfo(path);

        var directories = dirInfo.EnumerateDirectories()
                                    .OrderByDescending(d => d.CreationTime)
                                    .ToList();

        List<string> DeletedAppIDs = new List<string>();

        foreach (DirectoryInfo subDirInfo in directories)
        {

            int first_ = subDirInfo.Name.IndexOf("_");
            if (first_ < 0) continue;
            string appID = subDirInfo.Name.Substring(first_+1, 21);

            if (DeletedAppIDs.Contains(appID)) continue;

            var subdirectories = subDirInfo.Parent.EnumerateDirectories()
                                        .Where(d => d.Name.Contains(appID))
                                        .OrderByDescending(d => d.CreationTime)                                                
                                        .ToList();

            bool isNewest = true;
            foreach (DirectoryInfo subDirName in subdirectories)
            {
                if (isNewest)
                {
                    isNewest = false;
                }
                else
                {
                    try
                    {
                        SetAttributesToNormal(subDirName); //Set attributes to normal to prevent failures
                        subDirName.Delete(true);

                        if (!DeletedAppIDs.Contains(appID))
                        {
                            DeletedAppIDs.Add(appID);
                        }
                    }
                    catch (UnauthorizedAccessException)
                    {
                        //Catch unauthorized access to prevent exit if a previous version has any open dll
                    }

                }

            }
  
        }
                   
    }

    private static void SetAttributesToNormal(DirectoryInfo dir)
    {
        foreach (var subDir in dir.GetDirectories())
            SetAttributesToNormal(subDir);
        foreach (var file in dir.GetFiles())
        {
            file.Attributes = FileAttributes.Normal;
        }
    }

It can be called at launch like this:

    private async void InitTasks()
    {
        try
        {
            await Task.Run(() => CleanOldVersions());
        }
        catch (Exception ex)
        {
            //Error handling
        }                 
    }
0

Are you checking it immediately? They don't even go away after a day or so? The ClickOnce scavenger service is supposed to come around and remove the old versions. If you push the most recent version as a required update, does it remove the other versions? (To do this, set the minimum required version in the Updates dialog to the same version you are deploying).

Sampson
  • 265,109
  • 74
  • 539
  • 565
RobinDotNet
  • 11,723
  • 3
  • 30
  • 33
  • The minimum required version doesn't do anything. Is there a way to run this "ClickOnce scavenger" service immediately? Is there any documentation about this scavenger service? I've not heard of it – Clyde May 20 '10 at 18:13
  • Furthermore, I created a new windows forms app and deployed it with click once and it did behave correctly, leaving only two versions. So there is something about my real app that is different, but I can't find any difference in their publish configuration. – Clyde May 20 '10 at 18:15
  • And yes, they're still there a day later – Clyde May 21 '10 at 12:38
  • Can you provide any links to MSDN about this scavenger service? I couldn't find it, but I'm probably just missing the obvious here. – Clyde May 21 '10 at 12:38
-1

How to Delete

Clear Click-Once (versioning) Cache

From the docs:

ClickOnce applications that are hosted online are restricted in the amount of space they can occupy by a quota that constrains the size of the ClickOnce cache. The cache size applies to all the user's online applications; a single partially-trusted, online application is limited to occupying half of the quota space. Installed applications are not limited by the cache size and do not count against the cache limit. For all ClickOnce applications, the cache retains only the current version and the previously installed version. By default, client computers have 250 MB of storage for online ClickOnce applications. Data files do not count toward this limit. A system administrator can enlarge or reduce this quota on a particular client computer by changing the registry key, HKEY_CURRENT_USER\Software\Classes\Software\Microsoft\Windows\CurrentVersion\Deployment\OnlineAppQuotaInKB, which is a DWORD value that expresses the cache size in kilobytes. For example, in order to reduce the cache size to 50 MB, you would change this value to 51200

Theofanis Pantelides
  • 4,724
  • 7
  • 29
  • 49
  • 1
    I think you've misinterpreted the docs -- this cache limit is the total limit for all click-once applications on the machine. For a single application, "the cache retains only the current version and the previously installed version" http://msdn.microsoft.com/en-us/library/267k390a(v=VS.100).aspx – Clyde May 28 '10 at 16:48