We have got many questions on GAC assemblies on install and uninstall.
This article attempts to discuss how GAC assemblies install and uninstall are implemented, and the common mistakes/errors people seen in those area.
(Disclaimer: Information discussed here is implementation detail. It only applies to the current product as the article is written, and is subject to change without notice.)
GAC assemblies install takes a file path and an optional (but recommended) trace reference as input. Roughly it does the following:
- Create a temporary directory under GAC, and copy all the modules of the assembly to the temp directory.
- Verify all the modules of the assembly are in the temp directory, and the hashes of the modules are the same as recorded in the assembly’s manifest.
- Make sure the assembly is strongly named and validate the assembly’s strong name hash.
- (v2.0 only) Make sure the assembly’s manifest module file’s extension is either “dll” or “exe”.
- Install the assembly by calling MoveFile on the temporary directory, and move it to the final location.
- If a trace reference is provided, record the trace reference for that assembly.
The typical mistakes we have seen people making on assemblies install are:
- Forget to provide all the modules of the assembly.
This happens a lot for publisher policy assembly install. Publisher policy assembly is a multi-module assembly, with the config file being the first non manifest module. People may forget to include the config file when build the install package.
- Re-build modules without rebuilding the assembly manifest.
When the assembly manifest is built, the compiler records all the modules, as well as their hash. If one of the modules is changed, but the assembly manifest is not re-built, the hash of the module recorded in the manifest no longer matches the real hash of the module.
- Forget to sign the assembly.
This will fail the strong name validation.
- Try to install a simply named assembly
The following failure is not caused by user errors. Instead, it is caused by other applications interfering with assemblies install.
As discussed above, we call MoveFile on the temporary directory to move it to the final location. MoveFile will fail if a file in the directory is opened. Unfortunately when index services, anti-virus software, or other file monitoring application is running, they may open a file in the temporary directory while we call MoveFile, causing MoveFile to fail.
This does not happen very often. But we definite have seen reports of them.
GAC assembly install takes an assembly display name, and an optional trace reference as input. It has to take a display name as input, as it is the only way to uniquely identity an assembly.
The following is what it does:
- Make sure the assembly is installed in GAC.
If the assembly display name is partially specified, we will enumerate GAC to find all the assemblies matching the input assembly display name, and uninstall one of them. The current implementation tries to uninstall the one with the highest version. But due to a bug, it does not do so. So basically a random assembly (matching the input display name) is uninstalled.
If the assembly display name is fully specified, we will uninstall the exactly copy of the assembly.
The criteria to determine if an given assembly display name is fully specified is following:
If the assembly display name contains at least name, version, culture and publicKeyToken, then it is fully specified.
“foo” is partially specified.
“foo, publicKeyToken=0123456789abcdef” is partially specified.
“foo, version=22.214.171.124, culture=neutral, publicKeyToken=0123456789abcdef” is fully specified, and considered as a v1.0/v1.1 assembly.
“foo, version=126.96.36.199, culture=neutral, publicKeyToken=0123456789abcdef, processorArchitecture=MSIL” is fully specified, and considered as a v2.0 assembly.
(Please read my earlier blogs on assembly processorArchitecture.)
Due to randomness of assembly being uninstalled, it is highly recommended to give a fully specified assembly display name to uninstall.
(We should not accept partial assembly display name for uninstall. But that is how we shipped in v1.0 and we have to keep it that way for AppCompat.)
- If a trace reference is given, make sure the trace reference exists for that assembly.
- Remove the trace reference.
- If there is no more trace reference left, ask MSI to see if MSI has any outstanding reference. (MSI implements their own refer counting, so we have to ask MSI even when we don’t have any outstanding trace reference).
- If there is no MSI reference, we call MoveFile on the assembly directory to move the assembly to a temporary directory and delete the files from the temporary directory.
Here are the answers to some of the uninstall questions:
- I use gacutil to uninstall the assembly but it says there are more referecenes?
Believe in gacutil. It does not lie. Use “gacutil /lr” to show all the outstanding trace reference of the assembly.
- I try to uninstall the assembly but gacutil keeps telling me that there is a Windows Installer reference to it. How do I get rid of it?
We cannot uninstall assemblies installed by MSI. You need to use MSI to uninstall the application that carries the assembly.
- I build this MSI package. It can uninstall v1.0/v1.1 assemblies just fine. But it has trouble uninstalling v2.0 assemblies. What is the problem?
When MSI uninstalls an assembly, it constructs an assembly display name based on the MSIAssemblyName table, and passes it to fusion to uninstall.
v2.0 assemblies have different assembly display name from v1.0/v1.1 assemblies. Specifically, all v2.0 assemblies have processorArchitecture. To properly uninstall a v2.0 assembly, the MSIAssemblyName table needs to add the processorArchitecture field and populate it with the right value.
Since we may change assembly display name in the future, it is recommended to use the technique discussed in my previous article to populate the MSIAssemblyName table.