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 be XCOPY
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
Each of these is explored in detail within the following sections. The GAC
itself is implemented as a hierarchy of directories. When using these interfaces
to interact with the GAC, we are isolated from the implementation details of the
GAC (which by-the-way is a good thing).
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.
Some starting references within the .NET and Visual Studio.NET documentation
around Windows Installer 2.0 are as follows:
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
gacutil /i MyAssembly.dll
To remove this assembly from the GAC, you use the command
gacutil /u MyAssembly
To view the contents of the GAC, you use the command
gacutil /l
Note 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 Extension
SHFusion.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.g
gacutil /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) under
HKLM\Software\Microsoft\Fusion
to the pathname of the folder where
the GAC needs to be located. .NET will create an assembly
subfolder
underneath the CacheLocation
specified in the registry key, so you
should not include assembly
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 the assembly
subfolder underneath the current GAC location to the new location specified in
the CacheLocation
registry key.
As an example, if you want to move the GAC on your machine from the default
c:\windows\assembly
to d:\dotnetgac
, you should
- Set
HKLM\Software\Microsoft\Fusion\CacheLocation
to
d:\dotnetgac
.
XCOPY c:\windows\assembly to d:\dotnetgac\assembly
A few points worth noting regarding the GAC relocation process are as
follows:
- 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.
When I first tried to move the GAC to another location, I noticed that the
Shell Namespace Extension 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.
History
- First revision, June 17th, 2003.
- Second revision, Jan 15th, 2004.