Common Task: Optionally Cleanup User Data on Uninstall

This is a common scenario... Your application, when it runs, creates log files, registry entries, and customized data files on the machine. These are files that were not installed by the installer. Therefore when you go to uninstall, these files would not normally be cleaned up.

It ought to be simple to add code to the installer to tackle removing these files. But... how do you handle it if an uninstall is happening because the user is upgrading? In that case, the user probably wants to KEEP those 'hanging chads'.

So you are left with a situation where you want the removal of items to be conditional - under some conditions remove them, under other conditions don't.

This gets tricky. I've worked up a solution to it. I'm not claiming it's ideal or anything.

Deleting Files and Folders

Before I begin, let me say that as I understand it the best practice is for an application NOT to create data files in its INSTALLDIR. Instead, files like this should go under a Documents and Settings folder either for the current user or under All Users. Further, my understanding is that you don't technically have to remove files under Doc n Settings. However, the requirements I'm usually given say "must remove all traces of product on uninstall". So having said all that...

For this example let's say that the product creates a folder called Logs, under INSTALLDIR. You'll need a Directory table entry for this path, such as INSTALLDIR_LOGS. To cause this folder to get removed on uninstall you add 2 entries to the RemoveFile table. One entry with FileName of *.* and DirProperty of INSTALLDIR_LOGS. This entry causes all files under Logs to get deleted. A second entry with FileName blank and DirProperty of INSTALLDIR_LOGS. This entry causes the (now empty) folder to get deleted. Both of these table entries should be associated with a component that will be uninstalled at uninstall time.

Now let's get tricky and make this conditional. Let's assume that by default you want to KEEP this folder. The reason to keep it by default is so that a silent uninstall (such as when upgrading) will keep the folder.

In the Directory table, create an entry for UNINSTALL_INSTALLDIR_LOGS. Directory_Parent is TempFolder. DefaultDir is ThisFolderShouldNotExist. Yeah, that's a weird name, but it is self-explanatory. We are setting the path to a path which we do NOT expect to actually exist on the target machine - a subdirectory of the Temp folder. The point being, we don't intend for any files to actually get removed at this path.

Go back to RemoveFile table and change the DirProperty for both entries so that they point to UNINSTALL_INSTALLDIR_LOGS.

Now.. when uninstall occurs, it looks in RemoveFile table and tries to delete TempFolder\ThisFolderShouldNotExist\*.*. This folder won't exist and there will not be any errors as a result of that. All is well.

Now we go to Custom Actions. Create a Set Directory custom action that will, based on a flag, set UNINSTALL_INSTALLDIR_LOGS to the REAL path where the log files exist. For example, my cusotm action is called Uninstall_SetLogsDir. The Directory value is [INSTALLDIR_LOGS]. This goes in the Install Exec sequence with a condition of FULLCLEANUP="yes".

So if the FULLCLEANUP property is set to "yes", then our fake path will get reset to the real path, and as a result the log files will get deleted.

Now all you need is a method of setting FULLCLEANUP to yes. In the Property table, create FULLCLEANUP and set it to no, by default. Using whatever method you want, cause a dialog to pop up during uninstall that asks the user if they want to do a full cleanup and if they say yes, then set FULLCLEANUP to yes.

NOTE: When the user uninstalls from Add/Remove Programs using the Remove button, it launches the uninstall with reduced UI, so any custom dialog you write will NOT appear. Therefore, you have to either A) disable the Remove button and force them to use Change button instead or B) display your full cleanup question dialog using some other method via a custom action (InstallScript?).

 Deleting Registry Keys

You can apply the same basic methods to conditionally removing a registry key using the Property table (instead of the Directory table) and the Registry table (instead of the RemoveFile table).

Create a Property that holds the path to the registry key. By default set the Property to something that will not exist, for example, a Property called MyRegKey which is set to "Software\ThisKeyWillNotExist".

In the Registry table, create an entry for the key you want to conditionally remove. The fields for this entry are:

Key = "[MyRegKey]"
Name="-"
Value=""

And set the Component to whatever component makes sense. It is the minus sign in the Name column that tells MSI to remove the reg key on uninstall.

In the Custom Actions, create an action that will set the MyRegKey property to the *real* path when FULLCLEANUP="yes". The real path might be something like: "Software\[CompanyName]\[ProductName]\MyCustomRegKey".

NOTE: This only works with whole reg keys, you cannot remove a registry VALUE using this method. In fact, there is not a clean way to remove only a registry value, as far as I know. I find this odd, but true.

 

There ya go. It appears to work, and it isn't too clunky. Have fun!

Published Thursday, September 04, 2008 4:18 PM by SusanGorman

Comments

# re: Common Task: Optionally Cleanup User Data on Uninstall

Friday, September 05, 2008 2:46 PM by HookEm

The conditioning of your custom "clean-up" CAs can be handled during an upgrade scenario by just using the standard MSI public property UPGRADINGPRODUCTCODE (msdn.microsoft.com/.../aa372380(VS.85).aspx).

The best way is to ask the user at uninstall whether or not they want to remove user data.  If UPGRADINGPRODUCTCODE is set, don't ask the user to remove data and don't execute the CA that removes the data (b/c the uninstall is running during an upgrade scenario).

If you don't want to prompt the user, then best practice would be to leave the data when an uninstall is running via an upgrade scenario (UPGRADINGPRODUCTCODE is set) and remove the data if uninstall is being executed by the user via Add/Remove Programs / Software Explorer (NOT UPGRADINGPRODUCTCODE).

-C

# re: Common Task: Optionally Cleanup User Data on Uninstall

Friday, September 05, 2008 3:46 PM by SusanGorman

HookEm,

Yeah, I started out with custom actions to delete things, but it became undesirable for us for this reason:

By having the actions embedded in a custom action it was actually sort of hiding the actions from easy view. It defeats the table-driven intention of the MSI and moves code into, well, custom actions. We preferred to have the functionality more directly viewable in the tables.

That's also a good point about using UPGRADINGPRODUCTCODE. In our case, we DID want to ask the user what they wanted, so I'm keying off another property, but as you say, if you don't want to ask the user then the UPGRADINGPRODUCTCODE property is a good flag to use.

As always, excellent points!