Thursday, May 22, 2008

InstallAware doesn't automatically remove assemblies from the GAC at uninstall time

I am updating the installer for one of our products to check for the presence of the SP1 version of the .NET 2.0 Framework, when I noticed something odd.  The assemblies that I was putting into the GAC were still there after an uninstall.  The guys at InstallAware seem to think that this is not a problem.  They refer to a topic in the MSDN, Removal of Assemblies from the Global Assembly Cache, which describes how the Windows Installer is not responsible for the removal of entries in the GAC:

The Windows Installer determines whether to allow the removal of a common language runtime assembly based upon a client list it keeps independently of the assembly. The Windows Installer keeps one pin bit to represent Windows Installer clients of the assembly. The installer pins the assembly for the first Windows Installer client and unpins it when the last Windows Installer client is removed. The assembly maintains a pin bit for every client of an assembly.

The Windows Installer is not directly responsible for the physical removal of common language runtime assemblies from the computer. The installer unpins the assembly when the last Windows Installer client is removed. If the Windows Installer is the last client of the assembly, the common language runtime provides the option to force a synchronous cleanup of the assembly. The cleanup process is atomic and there is no provision for a "rollback" at this point. All unpinning of common language runtime assemblies must occur after the user has had a chance to cancel the installation or removal.

That being said, the users want to uninstall everything.  Wise and InstallShield apparently do this.  With InstallShield, there is a property for the installed file called "Permanent".  If it's not set, it will be removed from the GAC if there are no other references to the assembly.

What InstallAware wants you to do is to explicitly remove the assembly during the uninstall portion of the install script.  They provide a command named "Remove Unpinned Assemblies" (it's listed as "Remove Assemblies").  This command will remove all unpinned assemblies that your installer had places into the GAC.  The code should look like this: 

  Apply Uninstall (get result into variable SUCCESS)
Remove Unpinned Assemblies


I'm still not sure if that will always work.  According to InstallAware, there is a bug in Windows Installer where sometimes the installer corrupts the Global Registry cache on .NET 2.0 assembles and there will always be a reference to unreferenced assembly.  The only work around is to delete the (Default) registry key for the assembly in the user and local machine hives.  This mess is documented here in the InstallAware support forum.


The work around sounds worse than the problem.  I'm not going to do a strafing run on the registry to deal with a Windows Installer bug.  Not when the only consequence is that a few assemblies (in my case, it's only a few) get left in the GAC.  There are other ways of removing assemblies from the GAC.  The gacutil.exe utility can be used to check the reference count and to remove the assembly,  The "/lr" command line parameter will list the reference counts for each assembly.  The "/u" parameter will remove an assenbly from the GAC.  For the command-line shy, the free tool GacView provides an Explorer like view of the GAC and it's easy to use to remove assemblies from the GAC.

3 comments:

  1. Yes, we ended up having to use GACUTIL.EXE with the "/ul" switch to uninstall a support file named "GAC.txt" which contained the assembly name and version of each assembly to remove.

    You can get the direct path to GACUTIL on a target machine with this script; hope it helps!


    Comment: Get the path to the framework SDK for 1.1 and 2.0
    Check Registry Key HKLM\SOFTWARE\Microsoft\.NETFramework\sdkInstallRootv1.1 into Framework11Folder
    Check Registry Key HKLM\SOFTWARE\Microsoft\.NETFramework\sdkInstallRootv2.0 into Framework20Folder

    Comment: Get the path to the GACUTIL executable
    Set Variable GacUtility11 to $Framework11Folder$bin\GACUTIL.EXE
    Set Variable GacUtility20 to $Framework11Folder$bin\GACUTIL.EXE


    ... then, with a GAC.TXT support file added to the project, execute GACUTIL after applying the uninstall, like this:


    Apply Uninstall (get result into variable SUCCESS)
    if Variable SUCCESS not Equals CANCEL
    if Variable SUCCESS not Equals ERROR
    Run Program $GacUtility20$ /ul "$SUPPORTDIR$\GAC.txt" (WAIT)
    end
    end


    ... we've tested this with 2003, 2005 and 2008 and it's working.

    ReplyDelete
  2. Jon,
    Thanks for the cool tip. That is what I like about InstallAware, every time I find a problem, there's always a work around of some sort. You can do the same sort of thing with other Windows Installer based tools, but it's easier to use InstallAware's Msicode.

    ReplyDelete
  3. FYI- GACUTIL is not a redist per MSFT EULA.

    ReplyDelete

Note: Only a member of this blog may post a comment.