Introduction
My first introduction to the .NET Global Assembly Cache (henceforth referred to only as the GAC) was during a Guerrilla .NET Course conducted by Develop Mentor a week after the .NET Framework Beta 1 Release. I was immediately fascinated by this magical and mysterious piece within the entire .NET puzzle (yes, .NET had been a puzzle to me for a very long time). While ordinary (Private
) assemblies cohabit peacefully within their master’s (client executable’s) folder or sub-folders thereof, the highly sociable (Shared
) assemblies, including the all important .NET System assemblies implementing the Framework Class Library, reside within this majestic abode called the GAC.The primary purpose behind writing this article is to share my findings related to the GAC with the development community. The information presented herein is original and a direct result of my personal research. To the best of my knowledge, these details have not been covered in any .NET article or book.
Since references to Fusion occur throughout this article, it may be appropriate to clarify the significance of this name vis-à-vis the GAC. Fusion is the internal name of the project at Microsoft to eliminate the problems of sharing DLL code. The GAC was originally called the Fusion cache and is currently implemented within
Fusion.dll
.Credits
I would like to thank Vince Henderson (Software Design Engineer/Test Lead), Sameer Bhangar (Software Design Engineer/Test) and Alan Shi (Software Design Engineer) from the Microsoft .NET Team for patiently answering my questions while I was researching the material for this article as well as for providing valuable suggestions after reviewing the first draft.Disclaimers
Considering the Under-The-Hood nature of details covered in some sections of this article, appropriate cautionary notes have been placed immediately preceding such information. It will be prudent to heed these warnings and if you disregard these cautionary notes, it will be at your own risk.The Basics
For the benefit of those that are genuinely unaware of what the GAC is, I am providing this simple description which has been plagiarized from the online documentation:Each computer wherein the common language runtime is installed has a machine-wide code cache called the Global Assembly Cache. This Global Assembly Cache stores .NET assemblies specifically designated to be shared by several applications on that computer.
As a general rule, the Windows Registry is considered legacy within .NET and XCOPY deployment is highly encouraged, which in turn implies building assemblies that are private to a program’s main executable. Yet, there is a definite need for a central repository for .NET System assemblies as well as other user created Shared assemblies and it is this very need which is addressed by the GAC. Refer to .NET Framework Developer’s Guide: Global Assembly Cache for the formal product documentation on this topic.
The Microsoft KB article Q315682 HOW TO: Install an Assembly into the Global Assembly Cache in Visual Studio .NET walks the uninitiated through the steps required to install a .NET assembly into the GAC. If it is not already obvious, a key point to note here is that once your application installs Assemblies in the GAC, it loses the benefits of XCOPY deployment as these assemblies (other than the .NET System Libraries) must be explicitly installed in the GAC.
While covering the basics, it is important to understand the concept of Identity as it pertains to a .NET Assembly. An Assembly’s identity includes its simple textual name, a version number, an optional culture if the assembly contains localized resources, and an optional public key used to guarantee name uniqueness and to protect the name from unwanted reuse (name spoofing). An assembly with a simple Identity includes just the mandatory simple textual name and version number components. Since the GAC is the machine-wide repository for shared assemblies, it is very easy to run into the problem of name collisions if these assemblies only had simple identities because they are developed independently by different vendors and even developers within your own organization. Hence it is mandatory that every shared assembly installed in the GAC have a Strong Name. The formal definition of the term in the online documentation is
A strong name consists of the assembly's identity — its simple text name, version number, and culture information (if provided) — plus a public key and a digital signature. It is generated from an assembly file (the file that contains the assembly manifest, which in turn contains the names and hashes of all the files that make up the assembly), using the corresponding private key.
Refer to Strong-Named Assemblies and Creating and Using Strongly-named assemblies for the details around strongly-named assemblies. Of particular interest within the first URL are the three bullets that describe the requirements satisfied by strong names as well as the last paragraph that describes why a strong-named assembly can only reference other strong-named assemblies.
Popular misconceptions
One popular misconception is that strongly-named assemblies must always be installed in the GAC. Strongly-named assemblies can be put in the GAC, but by no-means have to be put there. It is desirable to put strongly-named assemblies under the application directory if you want to ensure your application has no system-wide impact, and make sure that it can beXCOPY
deployed.Another popular misconception is that assemblies must be installed in the GAC to make them accessible to COM Interop or unmanaged code. Neither of these scenarios mandates installing assemblies in the GAC and as a general guideline, you should install assemblies in the GAC only if they must be shared with other applications on the same machine.
Contrary to popular belief, it is not possible to directly reference an assembly from the GAC within a Visual Studio.NET project. In simpler terms, the assemblies listed within the .NET tab of the Add Reference dialog box for both Visual Studio.NET 2002 and 2003 are not enumerated from the GAC as this dialog box is path based. Refer to the More Information section of Microsoft KB article titled INFO: How to Display an Assembly in the Add Reference Dialog Box for a workaround to this very common issue faced by .NET developers.
The Public and not so public faces of the GAC
In order for the GAC to be useful, we need to be able to interact with it. I am aware of the following five interfaces available for such interaction.- The Windows Installer 2.0
- The command line tool
GACUtil.exe
- The Windows Shell namespace extension implemented in
SHFusion.dll
- The .NET Framework Configuration Administrative tool
- Programmatically accessing the GAC through APIs
The default ACLs inherited from the
<%windir%>
directory enable the local Administrators group and the SYSTEM user to have Full Control to the GAC. The local Users group has Read & Execute, List Folder Contents and Read permissions to the GAC. These permissions will come into play when you try interacting with the GAC using the techniques outlined in this article.1. The Windows Installer 2.0
Developers of Windows Installer packages can install assemblies to the GAC using Microsoft Windows Installer 2.0. This is the preferred way for installing such shared assemblies and should be the only way shared assemblies are installed on non development machines. The main benefits of using the Windows Installer 2.0 for installing assemblies to the GAC are:- It supports accurate reference counting based on installation, repair and removal of assemblies in the GAC.
- It supports Install-on-Demand of assemblies in the GAC. If an assembly was missing from the GAC and a user launches an application that requires that assembly, MSI will automatically install / re-install that assembly to the GAC.
- Rollback of unsuccessful installations, repairs and removals of assemblies in the GAC. Assemblies are added and removed from the GAC as a unit; that is, the files that constitute an assembly are always installed or removed together. This is due to the transactional model of the installation, which doesn't actually commit to the GAC until the end of the script so that rollback can remove the GAC assemblies.
- Deploying a Runtime Application Using Windows Installer
- Visual Studio Deployment Concepts
- Visual Studio Deploying Applications
2. The command line tool GACUtil.exe
This command line tool allows you to install assemblies to the GAC, remove them from the GAC and list the contents of the GAC. The online documentation for this tool is available at Global Assembly Cache Tool (Gacutil.exe).To install an assembly called MyAssembly in the GAC, you use the command
Collapse | Copy Code
gacutil /i MyAssembly.dll
To remove this assembly from the GAC, you use the commandgacutil /u MyAssembly
To view the contents of the GAC, you use the commandgacutil /lNote that when uninstalling, we use just the simple textual name of the assembly since for strong-named assemblies installed in the GAC, its simple textual name matches the DLL name.
Using the
/r
option (gacutil /ir MyAssembly.dll
and gacutil /ur MyAssembly
) will ensure that references to the assembly are traced. When listing shared assemblies using gacutil /lr
, the traced references are also displayed for each shared assembly. In .NET Framework 1.1, the /r must be listed separately e.g. gacutil /l /r
.You should avoid using
GACUtil
to install assemblies to the GAC for the following reasons:- Unless the
/r
option is used, there is no way to track installation references in the GAC. GACUtil.exe
is part of the .NET Framework SDK and hence is not guaranteed to be on all target machines. Additionally, it is not marked as redistributable and hence you need to check the licensing to make sure you can package it up with your application’s installation image.- Installing using
GACUtil.exe
does not enable Auto-Repair if the assembly in the GAC is missing (Auto-Repair is possible only with Windows Installer 2.0).
3. The Windows Shell namespace extension implemented in SHFusion.dll
Since a picture is worth a thousand words, I am including this image of the GAC as seen through the Windows Explorer when the Shell ExtensionSHFusion.dll
is enabled.The complete documentation for this Shell Extension is available at Assembly Cache Viewer (SHFusion.dll).
When the c:\windows\assembly directory is selected (clicked on), the right hand pane displays each and every assembly installed in the GAC along with additional information like Type, Version, Culture and Public Key Token. An assembly displayed with a Type of Native Images essentially means that an NGEN.exe generated native image is available for that assembly. Notice also that for every such assembly, there is a corresponding MSIL assembly e.g.
System.Windows.Forms
or System.Xml
. This is required because the metadata is not included in the native image. The source IL images used to generate native images do not need to live in the GAC although they often do.Selecting ‘Properties’ from the right-click menu displays the following properties window:
Most of this information is self evident but a couple of properties may need additional clarification.
References
This is not a very useful field and may well be removed in the next release of the .NET Framework. The basic problem is that the GAC implementation only gets a Yes / No answer from MSI on whether it has a reference to an assembly in the GAC but not an exact count of how many references MSI holds to the assembly (e.g. MSI could have 10 references and it'll still show up as only one reference). Additionally, the GAC implementation knows about its own traced references (gacutil /ir
option). So the number displayed by the References field is really Number of Traced References + 1 (if MSI holds any references).To view reference info
gacutil /lr
is a lot more useful. Even though it does not show details on MSI references but provides more info than just a number.CodeBase
This property displays the path of origin for the assembly. It is for informational purposes only and cannot be relied on to be available always. When assemblies are installed through MSI, there's no codebase available for that shared assembly since the bits are streamed in from the MSI package and hence the field will display as blank. Basically, codebase is available only when a shared assembly is installed using a file path (e.ggacutil /i
<full path name of the assembly>).The value of this property should be clear when you consider that the display name for two different assemblies can be the same, say Microsoft’s System and Widgets’ System. Strong names make this a real possibility and the path of origin can help make clear to the user which assembly is being referred to whereas the different public key tokens would not. It is also important to point out that there is no mechanism to try to recover / restore files from this path of origin a la Windows File Protection or the Auto-Repair option supported by Windows Installer 2.0.
Disabling the Assembly Cache Viewer
WARNING: The following steps involve modifying the Windows Registry. If you use Registry Editor incorrectly, you may cause serious problems that may require you to reinstall your operating system. The author as well as Microsoft cannot guarantee that you can solve problems that result from using Registry Editor incorrectly. Use Registry Editor at your own risk.If you want to disable the Assembly Cache Viewer and see the GAC in all its naked glory within Windows Explorer, you can set
HKLM\Software\Microsoft\Fusion\DisableCacheViewer [DWORD]
to 1.4. The .NET Framework Configuration Administrative tool
The Microsoft .NET Framework Configuration MMC snap-in is accessible through Start | Control Panel | Administrative Tools. When you first click on the Assembly node under My Computer the following screen is displayed:View List of Assemblies in the Assembly Cache
Upon selecting this task, the right pane displays the list of installed assemblies, similar to the view provided by the Assembly Cache Viewer.
The Action pull down menu includes the Copy, Delete, Properties and Help menu items, all of which are self explanatory. The properties window displays only the General tab. When compared to the properties window displayed by the Assembly Cache Viewer, the References property is missing while an additional Cache type property is displayed.
The View pull down menu includes the Add/Remove Columns, Help Topics and Refresh assembly list menu items. The Help Topic menu item switches to the view displayed when you first click on the Assembly Cache node.
Add an Assembly to the Assembly Cache
Upon selecting this task the following dialog box is displayed which enables an assembly to be installed into the GAC.
5. Programmatically accessing the GAC through APIs
CAUTION: Do not use these APIs in your application to perform assembly binds or to test for the presence of assemblies or other run time, development, or design-time operations. Only administrative tools and setup programs must use these APIs. If you use the GAC, this directly exposes your application to assembly binding fragility or may cause your application to work improperly on future versions of the .NET Framework.While the previous four options for interacting with the GAC are useful, there are occasions when we may be forced to use a programmatic way to interact with the GAC through our own code. The developers that implemented the GAC have already accounted for this and there is a full fledged API that is available for this very purpose.
The Microsoft KB Article Q317540 Global Assembly Cache (GAC) APIs Are Not documented in the .NET Framework Software Development Kit (SDK) Documentation formally documents this API, so there is no reason for me to rehash the information within this section. It will be prudent to heed the cautionary note at the beginning of the KB article which has been reproduced in this article.
Relocating the GAC
The default location of the GAC is under the<%windir% >\assembly
folder on the hard disk where the Windows operating system is installed. This location is not configurable during the .NET Framework setup / installation. Once the .NET Framework is fully installed, it is possible to relocate the GAC to a different location. The steps to move the GAC to a different location are as follows:WARNING: The following steps involve modifying the Windows Registry. If you use Registry Editor incorrectly, you may cause serious problems that may require you to reinstall your operating system. The author as well as Microsoft cannot guarantee that you can solve problems that result from using Registry Editor incorrectly. Use Registry Editor at your own risk.
- Set the registry key
CacheLocation
(REG_SZ) underHKLM\Software\Microsoft\Fusion
to the pathname of the folder where the GAC needs to be located. .NET will create anassembly
subfolder underneath theCacheLocation
specified in the registry key, so you should not includeassembly
in the pathname specified. - XCOPY the contents of your current GAC (which most likely will be the default location
c:\<%windir%>\Assembly
) to this new location. You can also use the Explorer shell to copy theassembly
subfolder underneath the current GAC location to the new location specified in theCacheLocation
registry key.
c:\windows\assembly
to d:\dotnetgac
, you should- Set
HKLM\Software\Microsoft\Fusion\CacheLocation
tod:\dotnetgac
. XCOPY c:\windows\assembly to d:\dotnetgac\assembly
- The order in which Steps 1 and 2 are performed is irrelevant.
- Even the basic .NET Framework assemblies will not be found after changing the registry key unless you migrate or reinstall them.
- .NET does not set any permission on the new cache location. The default location under
<%windir%>
inherits the ACLs so that only administrators have Modify permission to that folder. After relocating the GAC, you will need to manually set the appropriate ACLs on the new location so that the GAC cannot be tampered with by all users.
SHFusion.dll
displayed the abstracted view within Windows Explorer for both the original and new locations. Upon further investigation, I realized that SHFusion.dll
uses the hidden file Desktop.ini
under the <CacheLocation>\Assembly
directory to determine how to display the contents of that folder.Once the initial euphoria after learning this technique has subsided, the logical question one would ask is why would anyone ever want to do this in real life? Initially, I flirted with the idea of using this feature to have the GAC installed on a File Share (e.g. a Network Access Storage device) and reference that location from other machines that require the same set of shared assemblies. This way, the GAC would not occupy hard disk space on each machine within the cluster / group. Additionally, I would be able to install shared assemblies from one machine and have these available to .NET applications running on all other machines in that cluster / group.
Given my somewhat decent understanding of .NET Security (and without a lot of time available to experiment with this configuration), I quickly came to the conclusion that such a configuration would be impractical given the fact that the system assemblies themselves will be executed under the LocalIntranet Permission Set, leading to unpredictable behaviour. I am also sure that Microsoft Product Support considers this an unsupported configuration. All the same, I do feel that this would be a nice option to have as I have seen the size of the GAC bloat dramatically over time and a central location for a cluster of servers will be conducive to disk space as well as administration.
So the only good reason I can think of for now is to relocate the GAC to a different Windows drive (C: D: etc.) in case space becomes tight on the hard disk partition on which the GAC was initially installed. This is certainly possible given the amount of additional disk space that side-by-side installation of the .NET Framework and third party shared assemblies utilize.
Application Center 2000 Replication Support for .NET GAC
Application Center 2000 (AC2000) Replication feature ensures that specified files and directories are kept in sync across servers in a cluster. If such a file or directory is accidentally (or maliciously) deleted / overwritten, AC2000 Replication will ensure that the original is immediately replicated on that server (adding an event log entry to indicate the operation it performed).Prior to the release of AC2000 SP2, GAC assembly replication support was made available as a separate download for AC2000 installed on servers running the Windows 2000 O/S. Microsoft KB article Q396250 INFO: Application Center 2000 GAC Replication Support for Windows 2000 has all the details regarding this interim support package. With the release of AC2000 SP2, the temporary version of the GAC assembly replication is no longer relevant (unless there is some kind of exception granted by Microsoft Support Services that allows a server installation to continue running AC2000 SP1). Installing AC2000 SP2 on servers running the Windows 2000 O/S wherein the temporary version of the GAC assembly replication has been previously installed will automatically replace it with the final version. AC2000 SP2 is the first service pack that supports Application Center on servers running Windows Server 2003 and installing SP2 on these servers will always install the final version of the GAC assembly replication feature.
Exploring the current implementation of the GAC
WARNING: The GAC details which are described within this section are relevant only to the implementation as observed in the Microsoft .NET Framework 1.0 and 1.1 releases. Subsequent releases of the .NET Framework may result in changes to or even a complete overhaul of today’s implementation. Any reliance on the implementation details described herein is strongly discouraged and should be used at your own risk.Additionally, using MS DOS commands to alter or delete these internal folders or contents of files therein may lead to unpredictable behavior or other serious problems that may require you to reinstall the .NET Framework on the affected machine(s). Use such commands at your own risk.
As we know from a previous section, the default location of the GAC is under the
<%windir% >\assembly
folder. The following screen shot displays a MS DOS Command Window wherein various commands have been executed at the prompt.The Dir command displays four separate directories which are briefly described here.
GAC: The container for all the MSIL assemblies installed within the GAC.
NativeImages1_v1.0.3705: The native images generated for .NET Framework 1.0.
Temp and Tmp : These are temporary staging directories that are used by the current implementation.
The
Dir /AH
command exposes the hidden Desktop.ini
file used by SHFusion.dll
while the type desktop.ini
command reveals the contents of this file. I used the CLSID to search the HKCR
node within the Registry Editor and found that it refers to a COM component implemented in SHFusion.dll
. What is interesting is that MSCorEE.dll
is listed under the InprocServer32
key even though the object is implemented in SHFusion.dll
and SHFusion.dll
is listed under the Server
key. The reason for this is that MSCorEE.dll
is the shim that knows about multiple side-by-side runtimes on the machine. Since SHFusion.dll
lives under the versioned runtime directory, MSCorEE.dll
is used as InProcServer32
so that it can load the correct version of SHFusion.dll
at runtime.Exploring further into the GAC folder reveals folders named after each of the assemblies installed in the GAC.
Progressively drilling through this hierarchy of folders reveals the organization of the GAC and how it implements Side-By-Side installation.
Within each folder for a shared assembly, there is a sub folder named using the version and public key token components of the assembly e.g.
1.0.3300.0__b77a5c561934e089
. Within this sub folder resides the actual assembly that is installed into the GAC.Notice the
__AssemblyInfo__.ini
file that resides alongside each and every assembly file. This stores assembly properties like URL
, MVID
, DisplayName
etc. For assemblies that have a native image installed, a CustomString
property is also included. It goes without saying that the format and information stored in this file is subject to change in future runtime versions.The URL is the same as the Path of Origin displayed as the CodeBase property displayed within the Properties dialog displayed by
SHFusion.dll
.DisplayName
, Version
, Culture
and PublicKeyToken
are the components that uniquely identify the assembly.MVID
and CustomString
are used by the loader to determine if a specific pre-JITed assembly (installed using NGEN.exe) is valid to use during runtime.What happens if a directory is accidentally deleted?
Since there is no Windows File Protection for GAC assemblies, it is important to reiterate what has been covered in previous sections of this article. If one accidentally deletes a folder within the GAC folder hierarchy, ONLY assemblies that are installed using MSI will be reinstalled at runtime if they are not found in the GAC. Since the .NET Framework is installed using MSI, all the System.* assemblies are considered safe from accidental or malicious tampering (provided the .NET Runtime MSI is accessible).Summary
During the course of this article, we have explored the .NET GAC in sufficient detail. It is my sincere hope that the reader has learned something new in the process and will find some of this information useful when working with .NET on a day to day basis.I am always open to suggestions for improvement and hence will appreciate healthy criticism related to the material presented here.
0 comments:
Post a Comment