检测特定版本的程序集是否在 GAC 中

Detect if a specific version of an assembly is in the GAC

我有一个 程序集完全限定名称列表,我检查是否有一些 assemblyGAC 我的应用 [=43] =].

例如,我想做类似的事情:

var result = AssemblyIsInGAC("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");

我已经尝试使用 fusion API(CreateAssemblyCacheQueryAssemblyInfo),如 this blog post 中所述,但不幸的是它只使用 程序集的部分名称

如果我调用 QueryAssemblyInfo("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"),它会给我一个 FileNotFound 异常,如果我调用它 QueryAssemblyInfo("mscorlib") 它 returns mscorlib 的最新版本,它并不总是我要找的版本。

关于如何执行此操作的任何想法?

更新:我无法将程序集加载到我的应用程序中 因此无法使用 Assembly.LoadAssembly.ReflectionOnlyLoad

static class AssemblyHelper
{
    public static bool AssemblyIsInGAC(string assemblyFullName)
    {
        try
        {
            var assembly = Assembly.ReflectionOnlyLoad(assemblyFullName);
            return assembly.FullName == assemblyFullName && // makes "mscorlib, Version=wrong version ..." work properly
                assembly.GlobalAssemblyCache;
        }
        catch(FileNotFoundException)
        {
            return false;
        }
    }
}

用法:

static void Main()
{
    Demo("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
    Demo("mscorlib, Version=wrong version, Culture=neutral, PublicKeyToken=b77a5c561934e089");
    Demo("msshrtmi, Version=2.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
    Demo("msshrtmi, Version=2.6.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
    Demo("msshrtmi, Version=2.7.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
    Demo("msshrtmi, Version=2.8.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
}

static void Demo(string assemblyFullName) =>
    Console.WriteLine($"{assemblyFullName} is in GAC: {AssemblyHelper.AssemblyIsInGAC(assemblyFullName)}");

输出:

mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 is in GAC: True
mscorlib, Version=wrong version, Culture=neutral, PublicKeyToken=b77a5c561934e089 is in GAC: False
msshrtmi, Version=2.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 is in GAC: True
msshrtmi, Version=2.6.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 is in GAC: True
msshrtmi, Version=2.7.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 is in GAC: True
msshrtmi, Version=2.8.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 is in GAC: False

我使用这个实用程序,如您所见,它针对 mscorlib 或特殊程序集进行了修改:

Console.WriteLine(GetAssemblyPath("System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"));
// dumps C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll

...

public static string GetAssemblyPath(string name, bool matchClrVersion = true, bool throwOnError = false)
{
    if (name == null)
        throw new ArgumentNullException(nameof(name));

    string finalName = name;
    var aInfo = new ASSEMBLY_INFO();
    aInfo.cchBuf = 1024;
    aInfo.pszCurrentAssemblyPathBuf = new string('[=10=]', aInfo.cchBuf);

    var hr = CreateAssemblyCache(out IAssemblyCache ac, 0);
    if (hr >= 0)
    {
        hr = ac.QueryAssemblyInfo(0, finalName, ref aInfo);
        if (hr < 0 && matchClrVersion)
        {
            var asmName = new AssemblyName(name);
            finalName = asmName.Name + ", Version=" + Environment.Version.Major + "." + Environment.Version.Minor;
            aInfo.pszCurrentAssemblyPathBuf = new string('[=10=]', aInfo.cchBuf);
            hr = ac.QueryAssemblyInfo(0, finalName, ref aInfo);
        }
    }

    if (hr < 0)
    {
        if (throwOnError)
            Marshal.ThrowExceptionForHR(hr);

        return null;
    }

    return aInfo.pszCurrentAssemblyPathBuf;
}

[InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("e707dcde-d1cd-11d2-bab9-00c04f8eceae")]
private interface IAssemblyCache
{
    void UninstallAssembly(); // not fully defined

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

[StructLayout(LayoutKind.Sequential)]
private struct ASSEMBLY_INFO
{
    public int cbAssemblyInfo;
    public int dwAssemblyFlags;
    public long uliAssemblySizeInKB;
    [MarshalAs(UnmanagedType.LPWStr)]
    public string pszCurrentAssemblyPathBuf;
    public int cchBuf;
}

[DllImport("fusion")]
private static extern int CreateAssemblyCache(out IAssemblyCache ppAsmCache, int reserved);

您可以使用CreateAssemblyNameObject and CreateAssemblyEnum

例如=>

            IAssemblyEnum pAssemblyEnum = null;
            IAssemblyName pAssemblyName = null;
            HRESULT hr = HRESULT.E_FAIL;
            string sAssemblyName = "mscorlib";
            hr = CreateAssemblyNameObject(out pAssemblyName, sAssemblyName, 0, IntPtr.Zero);
            if (hr == HRESULT.S_OK)
            {
                hr = CreateAssemblyEnum(out pAssemblyEnum, IntPtr.Zero, pAssemblyName, ASM_CACHE_FLAGS.ASM_CACHE_GAC,  IntPtr.Zero);
                if (hr == HRESULT.S_OK)
                {
                    while (pAssemblyEnum.GetNextAssembly(IntPtr.Zero, out pAssemblyName, 0) == 0 && pAssemblyName != null)
                    {
                        int nSize = 260;
                        StringBuilder sbDisplayName = new StringBuilder(nSize);
                        hr = pAssemblyName.GetDisplayName(sbDisplayName, ref nSize, ASM_DISPLAY_FLAGS.ASM_DISPLAYF_FULL);
                        Console.WriteLine("Display Name: {0}", sbDisplayName.ToString());
                     }
                    Marshal.ReleaseComObject(pAssemblyEnum);
                }
            }

我明白了(我把全名写进去了,你可以把它缩减为用一个字符串作为函数参数来过滤):

Display Name: mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=AMD64
Display Name: mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=x86
Display Name: mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=AMD64
Display Name: mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=x86

有声明=>

    public enum HRESULT : int
    {
        S_OK = 0,
        S_FALSE = 1,
        E_NOINTERFACE = unchecked((int)0x80004002),
        E_NOTIMPL = unchecked((int)0x80004001),
        E_FAIL = unchecked((int)0x80004005),
    }        

    [Guid("21b8916c-f28e-11d2-a473-00c04f8ef448")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IAssemblyEnum
    {
        HRESULT GetNextAssembly(IntPtr pvReserved, out IAssemblyName ppName, int dwFlags);
        HRESULT  Reset();
        HRESULT  Clone(out IAssemblyEnum ppEnum);
    }

    public enum ASM_CACHE_FLAGS
    {
        ASM_CACHE_ZAP = 0x01,
        ASM_CACHE_GAC = 0x02,
        ASM_CACHE_DOWNLOAD = 0x04,
        ASM_CACHE_ROOT = 0x08,
        ASM_CACHE_ROOT_EX = 0x80
    }

    [DllImport("Fusion.dll", SetLastError = true)]
    public static extern HRESULT CreateAssemblyEnum(out IAssemblyEnum pEnum, IntPtr pUnkReserved, IAssemblyName pName, ASM_CACHE_FLAGS dwFlags, IntPtr pvReserved);

    [Guid("CD193BC0-B4BC-11d2-9833-00C04FC31D2E")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IAssemblyName
    {
        HRESULT SetProperty(int PropertyId, IntPtr pvProperty, int cbProperty);
        HRESULT GetProperty(int PropertyId,  out IntPtr pvProperty, ref int pcbProperty);
        HRESULT Finalize();
        HRESULT GetDisplayName(StringBuilder szDisplayName,  ref int pccDisplayName, ASM_DISPLAY_FLAGS dwDisplayFlags);
        HRESULT Reserved([In, MarshalAs(UnmanagedType.LPStruct)] Guid refIID, IntPtr pUnkReserved1, IntPtr pUnkReserved2, string szReserved, UInt64 llReserved,  IntPtr pvReserved, int cbReserved, out IntPtr ppReserved);
        HRESULT GetName(ref  int lpcwBuffer, StringBuilder pwzName);
        HRESULT GetVersion(out int pdwVersionHi, out int pdwVersionLow);
        HRESULT IsEqual(IAssemblyName pName, int dwCmpFlags);
        HRESULT Clone(out IAssemblyName pName);
    }

    public enum ASM_DISPLAY_FLAGS
    {
        ASM_DISPLAYF_VERSION = 0x1,
        ASM_DISPLAYF_CULTURE = 0x2,
        ASM_DISPLAYF_PUBLIC_KEY_TOKEN = 0x4,
        ASM_DISPLAYF_PUBLIC_KEY = 0x8,
        ASM_DISPLAYF_CUSTOM = 0x10,
        ASM_DISPLAYF_PROCESSORARCHITECTURE = 0x20,
        ASM_DISPLAYF_LANGUAGEID = 0x40,
        ASM_DISPLAYF_RETARGET = 0x80,
        ASM_DISPLAYF_CONFIG_MASK = 0x100,
        ASM_DISPLAYF_MVID = 0x200,
        ASM_DISPLAYF_CONTENT_TYPE = 0x400,
        ASM_DISPLAYF_FULL = (((((ASM_DISPLAYF_VERSION | ASM_DISPLAYF_CULTURE) | ASM_DISPLAYF_PUBLIC_KEY_TOKEN) | ASM_DISPLAYF_RETARGET) | ASM_DISPLAYF_PROCESSORARCHITECTURE) | ASM_DISPLAYF_CONTENT_TYPE)
    }        

    [DllImport("Fusion.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    public static extern HRESULT CreateAssemblyNameObject(out IAssemblyName ppAssemblyNameObj, [MarshalAs(UnmanagedType.LPWStr)] string szAssemblyName, CREATE_ASM_NAME_OBJ_FLAGS flags,  IntPtr pvReserved);

    public enum CREATE_ASM_NAME_OBJ_FLAGS
    {
        CANOF_PARSE_DISPLAY_NAME = 0x1,
        CANOF_SET_DEFAULT_VALUES = 0x2,
        CANOF_VERIFY_FRIEND_ASSEMBLYNAME = 0x4,
        CANOF_PARSE_FRIEND_DISPLAY_NAME = (CANOF_PARSE_DISPLAY_NAME | CANOF_VERIFY_FRIEND_ASSEMBLYNAME)
    }