简体   繁体   中英

enumerating assemblies in GAC

How can I enumerate all available assemblies in GAC in C#?

Actually I am facing an issue with a stupid code - the assembly called Telerik.Web.UI.dll is referred and used in project - this particular DLL is not present in BIN folder. And application is working fine on the server - but on my machine, obviously compiler is giving error. Ex-Developer insists that it is not in GAC on the server and 'it is what it is'. Now I need to check whether this DLL is registered in GAC or not.

Help will be appreciated.

Good Day;

If you have limited access to the server then this might work:

// List of all the different types of GAC folders for both 32bit and 64bit
// environments.
List<string> gacFolders = new List<string>() { 
    "GAC", "GAC_32", "GAC_64", "GAC_MSIL", 
    "NativeImages_v2.0.50727_32", 
    "NativeImages_v2.0.50727_64",
    "NativeImages_v4.0.30319_32",
    "NativeImages_v4.0.30319_64"
};

foreach (string folder in gacFolders)
{
    string path = Path.Combine(
       Environment.ExpandEnvironmentVariables(@"%systemroot%\assembly"), 
       folder);

    if(Directory.Exists(path))
    {
        Response.Write("<hr/>" + folder + "<hr/>");

        string[] assemblyFolders = Directory.GetDirectories(path);
        foreach (string assemblyFolder in assemblyFolders)
        {
            Response.Write(assemblyFolder + "<br/>");
        }
    }
}

It basically enumerates the raw GAC folders. It works on DiscountASP shared hosting so might work for your hosting environment.

It could be embellished by enumerating deeper into each assembly's folder to yank out the version number and public key token.

Check this codeproject GAC API Interface article. This uses undocumented fusion.dll to enumurate the GAC. But author claims that

This code is known to work with .NET 1.1 and 2.0. Please note that the DLL fusion.dll is different in 1.1 and 2.0. So if you have both frameworks installed, make sure to point the code to the right DLL. Otherwise most API-calls will fail. The default implementation uses the one found in the WINDOWS directory.

You don't really need to write something in C# to find out if this specific dll is in the gac. You can use gacutil.exe with the /l option from the command line to list the contents of the GAC.

a simple method to enumerate dll files in a directory

public static string[] GetGlobalAssemblyCacheFiles(string path)
{
    List<string> files = new List<string>();

    DirectoryInfo di = new DirectoryInfo(path);

    foreach (FileInfo fi in di.GetFiles("*.dll"))
    {
        files.Add(fi.FullName);
    }

    foreach (DirectoryInfo diChild in di.GetDirectories())
    {
        var files2 = GetGlobalAssemblyCacheFiles(diChild.FullName);
        files.AddRange(files2);
    }

    return files.ToArray();
}

and you can get all files

string gacPath = Environment.GetFolderPath(System.Environment.SpecialFolder.Windows) + "\\assembly";
var files = GetGlobalAssemblyCacheFiles(gacPath);

If you need to load each assembly, You get a exception for different run-time version. For this reason you can load assembly with Assembly.ReflectionOnlyLoadFrom to load Assembly in reflection only. Then, Assembly don't load in your AppDomain and don't throw exception.

If it's plain-jane ASP.NET site, and the assembly is not in the site's bin folder or in the server's GAC (and there is nothing fishy in the web.config), perhaps the site is a sub-site of some sort and one of the site's higher up contains the reference in the bin folder (or something fishy in its web.config, since sub-sites/folders inherit the web.configs of their parents)?

Now, if you have an environment where the file is being loaded correctly (and it sounds like you do), you can just ask .NET where the .dll is coming from and display that instead, for example:

Assembly a = Assembly.Load("Microsoft.VisualStudio.Shell, Version=2.0.0.0, "
           + "PublicKeyToken=b03f5f7f11d50a3a, Culture=Neutral");

Console.WriteLine(a.Location);

Will load an assembly and display its on-disk location, my output is: C:\\Windows\\assembly\\GAC_MSIL\\Microsoft.VisualStudio.Shell\\2.0.0.0__b03f5f7f11d50a3a\\Microsoft.VisualStudio.Shell.dll

If it's under "C:\\Windows\\assembly\\" you know it's in the GAC.

You can also do this without an Assembly.Load call if you already referencing the assembly you can enumerate the assemblies loaded into the current app.domain and just print out (or render to a literal control, or Response.Write, etc...) what their .Location properties are.

Edit: The code for that looks something like this:

foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies())
{
    Console.WriteLine(a.GetName().FullName);
    Console.WriteLine(a.Location);
    Console.WriteLine();
}

Edit: On a full trust environment, the following code (console app) will enumerate the GAC and write out each assembly, it should be easy to modify it into an ASP.NET app, but I'm not sure if it will work in an environment that is less than full trust (just guessing here, you might get lucky):

static void ProcessFile(string file)
{
    try
    {
        Assembly a = Assembly.LoadFile(file);

        Console.WriteLine(a.GetName().FullName);
    }
    catch { /* do nothing */ }
}

static void ProcessFolder(string folder)
{
    foreach (string file in Directory.GetFiles(folder))
    {
        ProcessFile(file);
    }

    foreach (string subFolder in Directory.GetDirectories(folder))
    {
        ProcessFolder(subFolder);
    }
}

static void Main(string[] args)
{
    ProcessFolder(@"C:\Windows\Assembly");
}

Figure i might as well throw in my linq based solution (handy if your just looking for a single file and don't want to enumerate through every file)

function to find the files:

    public static IEnumerable<string> FindFiles (string path, string filter = "", int depth = int.MaxValue) {
        DirectoryInfo di = new DirectoryInfo(path);

        IEnumerable<string> results = (! string.IsNullOrEmpty(filter) ? di.GetFiles(filter) : di.GetFiles()).Select(f => f.FullName);
        if (depth > 0) {
            results = results.Concat(di.GetDirectories().SelectMany(d => FindFiles(d.FullName, filter, depth - 1)));
        }

        return results;
    }

And to call (i didn't have Environment.SpecialFolder.Windows so used Environment.SpecialFolder.System instead)

string path = Environment.GetFolderPath(Environment.SpecialFolder.System) + "\\..\\assembly";
foreach (string s in FindFiles(path, "*.dll")) {
     Console.WriteLine(s);
}

Further, as a FYI this method enumerates the entire GAC and you may find duplicate DLLs in tmp/ and temp/, Tmp is used for installation, and Temp is used for uninstallation.

Check the way how fusion.dll is used in ILSpy to enumerate all the GAC assemblies.

Fusion COM Wrappers

using System;
using System.Runtime.InteropServices;
using System.Text;

namespace GacWithFusion
{
    // .NET Fusion COM interfaces
    [ComImport, Guid("CD193BC0-B4BC-11D2-9833-00C04FC31D2E"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    internal interface IAssemblyName
    {
        [PreserveSig]
        int SetProperty(uint PropertyId, IntPtr pvProperty, uint cbProperty);

        [PreserveSig]
        int GetProperty(uint PropertyId, IntPtr pvProperty, ref uint pcbProperty);

        [PreserveSig]
        int Finalize();

        [PreserveSig]
        int GetDisplayName([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder szDisplayName,
                           ref uint pccDisplayName,
                           uint dwDisplayFlags);

        [PreserveSig]
        int BindToObject(object refIID,
                         object pAsmBindSink,
                         IApplicationContext pApplicationContext,
                         [MarshalAs(UnmanagedType.LPWStr)] string szCodeBase,
                         long llFlags,
                         int pvReserved,
                         uint cbReserved,
                         out int ppv);

        [PreserveSig]
        int GetName(ref uint lpcwBuffer,
                    [Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pwzName);

        [PreserveSig]
        int GetVersion(out uint pdwVersionHi, out uint pdwVersionLow);

        [PreserveSig]
        int IsEqual(IAssemblyName pName,
                    uint dwCmpFlags);

        [PreserveSig]
        int Clone(out IAssemblyName pName);
    }

    [ComImport(), Guid("7C23FF90-33AF-11D3-95DA-00A024A85B51"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    internal interface IApplicationContext
    {
        void SetContextNameObject(IAssemblyName pName);

        void GetContextNameObject(out IAssemblyName ppName);

        void Set([MarshalAs(UnmanagedType.LPWStr)] string szName,
                 int pvValue,
                 uint cbValue,
                 uint dwFlags);

        void Get([MarshalAs(UnmanagedType.LPWStr)] string szName,
                 out int pvValue,
                 ref uint pcbValue,
                 uint dwFlags);

        void GetDynamicDirectory(out int wzDynamicDir,
                                 ref uint pdwSize);
    }

    [ComImport(), Guid("21B8916C-F28E-11D2-A473-00C04F8EF448"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    internal interface IAssemblyEnum
    {
        [PreserveSig]
        int GetNextAssembly(out IApplicationContext ppAppCtx,
                            out IAssemblyName ppName,
                            uint dwFlags);

        [PreserveSig]
        int Reset();

        [PreserveSig]
        int Clone(out IAssemblyEnum ppEnum);
    }

    internal static class Fusion
    {
        // dwFlags: 1 = Enumerate native image (NGEN) assemblies
        //          2 = Enumerate GAC assemblies
        //          4 = Enumerate Downloaded assemblies
        //
        [DllImport("fusion.dll", CharSet = CharSet.Auto)]
        internal static extern int CreateAssemblyEnum(out IAssemblyEnum ppEnum,
                                                      IApplicationContext pAppCtx,
                                                      IAssemblyName pName,
                                                      uint dwFlags,
                                                      int pvReserved);
    }
}

Main

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;

namespace GacWithFusion
{
    public class Program
    {
        static void Main(string[] args)
        {
            foreach (var assemblyName in GetGacAssemblyFullNames())
            {
                Console.WriteLine(assemblyName.FullName);
            }
        }

        public static IEnumerable<AssemblyName> GetGacAssemblyFullNames()
        {
            IApplicationContext applicationContext;
            IAssemblyEnum assemblyEnum;
            IAssemblyName assemblyName;

            Fusion.CreateAssemblyEnum(out assemblyEnum, null, null, 2, 0);
            while (assemblyEnum.GetNextAssembly(out applicationContext, out assemblyName, 0) == 0)
            {
                uint nChars = 0;
                assemblyName.GetDisplayName(null, ref nChars, 0);

                StringBuilder name = new StringBuilder((int)nChars);
                assemblyName.GetDisplayName(name, ref nChars, 0);

                AssemblyName a = null;
                try
                {
                    a = new AssemblyName(name.ToString());
                }
                catch (Exception)
                {
                }

                if (a != null)
                {
                    yield return a;
                }
            }
        }
    }
}

May be you need GAC viewer something like this:

替代文字

Video: How to use Add GAC References Dialog

Hope this helps

s

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM