How to programmatically determine if .NET assembly is installed in GAC?
Asked Answered
A

2

16

What's the easiest way to check programmatically if an assembly is registered in the GAC (Global Assembly Cache) on the local machine? Is there some easy to use .NET API where I can give it a location to an assembly DLL or an Assembly object itself to check if it exists in GAC on the local machine? In my case the assembly I'm checking will already be loaded in the current AppDomain of the program checking so I'm not sure calling Assembly.ReflectionOnlyLoad and catching an exception will work like I've seen suggested in other posts, plus that seems kind of hacky.

Ideally I'd like to avoid calling an external executable like gacutil.exe to check.

Abacus answered 18/10, 2013 at 18:23 Comment(6)
possible duplicate of Check GAC for an assemblyRed
Use the fusion api, IAssemblyCache.QueryAssemblyInfo.Hum
@Hans I probably should have specified I wanted a fully managed solution but you're correct, the Fusion API would do what I want as well. See my answer for what I ended up doing for a fully managed solution.Abacus
Sigh. It is C# code. Not an unsafe keyword in sight, every C# program uses Fusion. What you posted is On Error Resume Next code, that' Visual Basic code.Hum
@Hans true, it's not unsafe code but I like to avoid COM as well if I can, it just seemed simpler and a lot less code. If I were calling this is a tight loop a bunch I'd probably go with the Fusion API to avoid the awful try/catch control logic, but even interop has a performance penalty, although I'm guessing a lot less than a stack unwind from the exception catch.Abacus
@HansPassant I updated my answer, I think you're right, using the fusion API is the correct approachAbacus
A
24

This question is very similar to the following questions but mine is a little more precise, plus neither had an accepted answer and none of the answers offered seemed complete or optimal:

Originally I thought the following was the best approach but it doesn't work unless you specify the full name of the assembly and it's kind of hacky beacause of the try/catch but it's simple and works for many cases:

public static class GacUtil
{
    public static bool IsAssemblyInGAC(string assemblyFullName)
    {
        try
        {
            return Assembly.ReflectionOnlyLoad(assemblyFullName)
                           .GlobalAssemblyCache;
        }
        catch
        {
            return false;
        }
    }

    public static bool IsAssemblyInGAC(Assembly assembly)
    {
        return assembly.GlobalAssemblyCache;
    }
}

This is a better approach that works without a try/catch by using the Fusion API. It's a bunch more code but it works with partial assembly names:

public static class GacUtil
{
    [DllImport("fusion.dll")]
    private static extern IntPtr CreateAssemblyCache(
        out IAssemblyCache ppAsmCache, 
        int reserved);

    [ComImport]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("e707dcde-d1cd-11d2-bab9-00c04f8eceae")]
    private interface IAssemblyCache
    {
        int Dummy1();

        [PreserveSig()]
        IntPtr QueryAssemblyInfo(
            int flags, 
            [MarshalAs(UnmanagedType.LPWStr)] string assemblyName, 
            ref AssemblyInfo assemblyInfo);

        int Dummy2();
        int Dummy3();
        int Dummy4();
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct AssemblyInfo
    {
        public int cbAssemblyInfo;
        public int assemblyFlags;
        public long assemblySizeInKB;

        [MarshalAs(UnmanagedType.LPWStr)]
        public string currentAssemblyPath;

        public int cchBuf;
    }

    public static bool IsAssemblyInGAC(string assemblyName)
    {
        var assembyInfo = new AssemblyInfo { cchBuf = 512 };
        assembyInfo.currentAssemblyPath = new string('\0', assembyInfo.cchBuf);

        IAssemblyCache assemblyCache;

        var hr = CreateAssemblyCache(out assemblyCache, 0);

        if (hr == IntPtr.Zero)
        {
            hr = assemblyCache.QueryAssemblyInfo(
                1, 
                assemblyName, 
                ref assembyInfo);

            if (hr != IntPtr.Zero)
            {
                return false;
            }

            return true;
        }

        Marshal.ThrowExceptionForHR(hr.ToInt32());
        return false;
    }
}
Abacus answered 18/10, 2013 at 21:32 Comment(3)
Nb. To my experience the assemblyFullName has to be FULL. That includes processorArchitecture=MSIL or the like. So in my test I use the AssemblyName.FullName +", processorArchitecture=MSIL" on this test. And also try the 2 other alternatives as well before i say "no". ("AMD64" and "x86") The only match I got without it was assemblies within "c:\Windows\assembly\GAC" which is a set of very few.Chose
Not only that, but today I'm looking at a situation where adding ProcessorArchitecture=MSIL fails for some and succeeds for others, and removing ProcessorArchitecture from the full name switches around which ones fail and which succeed.Fuse
Using Assembly.ReflectionOnlyLoad() method I get Exception from HRESULT: 0x80131047, but replacing it with Assembly.LoadFile(), it works like a charm.Fleenor
G
-1

Check if CodeBase is null

if (asm.CodeBase == null) {
     // IN GAC
}
Goof answered 28/12, 2015 at 22:38 Comment(1)
Does not work. This is null even when the type is in the currently executing assembly.Muzz

© 2022 - 2024 — McMap. All rights reserved.