Sample Managed GAC API Wrappers


//————————————————————-
// GACWrap.cs
//
// This implements managed wrappers to GAC API Interfaces
//————————————————————-

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

namespace System.GACManagedAccess
{
    //————————————————————-
    // Interfaces defined by fusion
    //————————————————————-
    [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid(“e707dcde-d1cd-11d2-bab9-00c04f8eceae”)]
    internal interface IAssemblyCache
    {
        [PreserveSig()]
        int UninstallAssembly(
                            int flags,
                            [MarshalAs(UnmanagedType.LPWStr)]
                            String assemblyName,
                            InstallReference refData,
                            out AssemblyCacheUninstallDisposition disposition);
      
        [PreserveSig()]
        int QueryAssemblyInfo(
                            int flags,
                            [MarshalAs(UnmanagedType.LPWStr)]
                            String assemblyName,
                            ref AssemblyInfo assemblyInfo);
        [PreserveSig()]
        int Reserved        (
                            int flags,
                            IntPtr pvReserved,
                            out Object ppAsmItem,
                            [MarshalAs(UnmanagedType.LPWStr)]
                            String assemblyName);
        [PreserveSig()]
        int Reserved(out Object ppAsmScavenger);
      
        [PreserveSig()]
        int InstallAssembly(
                            int flags,
                            [MarshalAs(UnmanagedType.LPWStr)]
                            String assemblyFilePath,
                            InstallReference refData);
    }// IAssemblyCache

    [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid(“CD193BC0-B4BC-11d2-9833-00C04FC31D2E”)]
    internal interface IAssemblyName
    {
        [PreserveSig()]
        int SetProperty(
                int PropertyId,
                IntPtr pvProperty,
                int cbProperty);
      
        [PreserveSig()]
        int GetProperty(
                int PropertyId,
                IntPtr pvProperty,
                ref int pcbProperty);

        [PreserveSig()]
        int Finalize();
      
        [PreserveSig()]
        int GetDisplayName(
                StringBuilder pDisplayName,
                ref int pccDisplayName,
                int displayFlags);
      
        [PreserveSig()]
        int Reserved(ref Guid guid,
            Object obj1,
            Object obj2,
            String string1,
            Int64 llFlags,
            IntPtr pvReserved,
            int cbReserved,
            out IntPtr ppv);

        [PreserveSig()]
        int GetName(
                ref int pccBuffer,
                StringBuilder pwzName);

        [PreserveSig()]
        int GetVersion(
                out int versionHi,
                out int versionLow);
        [PreserveSig()]
        int IsEqual(
                IAssemblyName pAsmName,
                int cmpFlags);

        [PreserveSig()]
        int Clone(out IAssemblyName pAsmName);
    }// IAssemblyName

    [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid(“21b8916c-f28e-11d2-a473-00c04f8ef448”)]
    internal interface IAssemblyEnum
    {
        [PreserveSig()]
        int GetNextAssembly(
                IntPtr pvReserved,
                out IAssemblyName ppName,
                int flags);
        [PreserveSig()]
        int Reset();
        [PreserveSig()]
        int Clone(out IAssemblyEnum ppEnum);
    }// IAssemblyEnum

    [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid(“582dac66-e678-449f-aba6-6faaec8a9394”)]
    internal interface IInstallReferenceItem
    {
        // A pointer to a FUSION_INSTALL_REFERENCE structure.
        // The memory is allocated by the GetReference method and is freed when
        // IInstallReferenceItem is released. Callers must not hold a reference to this
        // buffer after the IInstallReferenceItem object is released.
        // This uses the InstallReferenceOutput object to avoid allocation
        // issues with the interop layer.
        // This cannot be marshaled directly – must use IntPtr
        [PreserveSig()]
        int GetReference(
                out IntPtr pRefData,
                int flags,
                IntPtr pvReserced);
    }// IInstallReferenceItem

    [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid(“56b1a988-7c0c-4aa2-8639-c3eb5a90226f”)]
    internal interface IInstallReferenceEnum
    {
        [PreserveSig()]
        int GetNextInstallReferenceItem(
                out IInstallReferenceItem ppRefItem,
                int flags,
                IntPtr pvReserced);
    }// IInstallReferenceEnum

    public enum AssemblyCommitFlags
    {
        Default          = 1,
        Force            = 2
    }// enum AssemblyCommitFlags

    public enum AssemblyCacheUninstallDisposition
    {
        Unknown                 = 0,
        Uninstalled             = 1,
        StillInUse              = 2,
        AlreadyUninstalled      = 3,
        DeletePending           = 4,
        HasInstallReference     = 5,
        ReferenceNotFound       = 6
    }
  
    [Flags]
    internal enum AssemblyCacheFlags
    {
        GAC          = 2,
    }

    internal enum CreateAssemblyNameObjectFlags
    {
        CANOF_DEFAULT            = 0,
        CANOF_PARSE_DISPLAY_NAME = 1,
    }
 
    [Flags]
    internal enum AssemblyNameDisplayFlags
    { 
        VERSION                   = 0x01,
        CULTURE                   = 0x02,
        PUBLIC_KEY_TOKEN          = 0x04,
        PROCESSORARCHITECTURE     = 0x20,
        RETARGETABLE              = 0x80,
        // This enum will change in the future to include
        // more attributes.
        ALL                       = VERSION
                                    | CULTURE
                                    | PUBLIC_KEY_TOKEN
                                    | PROCESSORARCHITECTURE
                                    | RETARGETABLE
    }
  
    [StructLayout(LayoutKind.Sequential)]
    public class InstallReference
    {
        public InstallReference(Guid guid, String id, String data)
        {
            cbSize = (int)(2*IntPtr.Size+16+(id.Length+data.Length)*2);
            flags = 0;
            // quiet compiler warning
            if (flags == 0) { }
            guidScheme = guid;
            identifier = id;
            description = data;
        }

        public Guid GuidScheme
        {
            get { return guidScheme;}
        }

        public String Identifier
        {
            get { return identifier; }
        }

        public String Description
        {
            get { return description;}
        }

        int         cbSize;
        int         flags;
        Guid        guidScheme;
        [MarshalAs(UnmanagedType.LPWStr)]
        String      identifier;
        [MarshalAs(UnmanagedType.LPWStr)]
        String      description;
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct AssemblyInfo
    {
        public int      cbAssemblyInfo; // size of this structure for future expansion
        public int      assemblyFlags;
        public long     assemblySizeInKB;
        [MarshalAs(UnmanagedType.LPWStr)]
        public String   currentAssemblyPath;
        public int      cchBuf; // size of path buf.
    }

    [ComVisible(false)]
    public class InstallReferenceGuid
    {
        public static bool IsValidGuidScheme(Guid guid)
        {
            return (guid.Equals(UninstallSubkeyGuid)    ||
                    guid.Equals(FilePathGuid)           ||
                    guid.Equals(OpaqueGuid)       ||
                    guid.Equals(Guid.Empty));
        }
 
        public readonly static Guid UninstallSubkeyGuid  = new Guid(“8cedc215-ac4b-488b-93c0-a50a49cb2fb8”);
        public readonly static Guid FilePathGuid         = new Guid(“b02f9d65-fb77-4f7a-afa5-b391309f11c9”);
        public readonly static Guid OpaqueGuid           = new Guid(“2ec93463-b0c3-45e1-8364-327e96aea856”);
        // these GUID cannot be used for installing into GAC.
        public readonly static Guid MsiGuid              = new Guid(“25df0fc1-7f97-4070-add7-4b13bbfd7cb8”);
        public readonly static Guid OsInstallGuid        = new Guid(“d16d444c-56d8-11d5-882d-0080c847b195”);  
    }

    [ComVisible(false)]
    public static class AssemblyCache
    {
        public static void InstallAssembly(String assemblyPath, InstallReference reference, AssemblyCommitFlags flags)
        {
            if (reference != null) {
                if (!InstallReferenceGuid.IsValidGuidScheme(reference.GuidScheme))
                    throw new ArgumentException(“Invalid reference guid.”, “guid”);
            }

            IAssemblyCache ac = null;

            int hr = 0;
    
            hr = Utils.CreateAssemblyCache(out ac, 0);
            if (hr >= 0) {
                hr = ac.InstallAssembly((int)flags, assemblyPath, reference);
            }

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

        // assemblyName has to be fully specified name.
        // A.k.a, for v1.0/v1.1 assemblies, it should be “name, Version=xx, Culture=xx, PublicKeyToken=xx”.
        // For v2.0 assemblies, it should be “name, Version=xx, Culture=xx, PublicKeyToken=xx, ProcessorArchitecture=xx”.
        // If assemblyName is not fully specified, a random matching assembly will be uninstalled.
        public static void UninstallAssembly(String assemblyName, InstallReference reference, out AssemblyCacheUninstallDisposition disp)
        {
            AssemblyCacheUninstallDisposition dispResult = AssemblyCacheUninstallDisposition.Uninstalled;
            if (reference != null) {
                if (!InstallReferenceGuid.IsValidGuidScheme(reference.GuidScheme))
                    throw new ArgumentException(“Invalid reference guid.”, “guid”);
            }
          
            IAssemblyCache ac = null; 
    
            int hr = Utils.CreateAssemblyCache(out ac, 0);
            if (hr >= 0) {
                hr = ac.UninstallAssembly(0, assemblyName, reference, out dispResult);
            }

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

            disp = dispResult;
        }

        // See comments in UninstallAssembly
        public static String QueryAssemblyInfo(String assemblyName)
        {
            if (assemblyName == null) {
                throw new ArgumentException(“Invalid name”, “assemblyName”);
            }

            AssemblyInfo aInfo = new AssemblyInfo();

            aInfo.cchBuf = 1024;
            // Get a string with the desired length
            aInfo.currentAssemblyPath = new String(‘\0’, aInfo.cchBuf) ;

            IAssemblyCache ac = null;
            int hr = Utils.CreateAssemblyCache(out ac, 0);
            if (hr >= 0) {
                hr = ac.QueryAssemblyInfo(0, assemblyName, ref aInfo);
            }
            if (hr < 0) {
                Marshal.ThrowExceptionForHR(hr);
            }

            return aInfo.currentAssemblyPath;
        }
    }

    [ComVisible(false)]
    public class AssemblyCacheEnum
    {
        // null means enumerate all the assemblies
        public AssemblyCacheEnum(String assemblyName)
        {
            IAssemblyName fusionName = null;
            int hr = 0;

            if (assemblyName != null) {
                hr = Utils.CreateAssemblyNameObject(
                        out fusionName,
                        assemblyName,
                        CreateAssemblyNameObjectFlags.CANOF_PARSE_DISPLAY_NAME,
                        IntPtr.Zero);
            }

            if (hr >= 0) {
                hr = Utils.CreateAssemblyEnum(
                        out m_AssemblyEnum,
                        IntPtr.Zero,
                        fusionName,
                        AssemblyCacheFlags.GAC,
                        IntPtr.Zero);
            }

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

        public String GetNextAssembly()
        {
            int hr = 0;
            IAssemblyName fusionName = null;

            if (done) {
                return null;
            }
    
            // Now get next IAssemblyName from m_AssemblyEnum
            hr = m_AssemblyEnum.GetNextAssembly((IntPtr) 0, out fusionName, 0);

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

            if (fusionName != null) {
                return GetFullName(fusionName);
            }
            else {
                done = true;
                return null;
            }
        }
  
        private String GetFullName(IAssemblyName fusionAsmName)
        {
            StringBuilder sDisplayName = new StringBuilder(1024);
            int iLen = 1024;
          
            int hr = fusionAsmName.GetDisplayName(sDisplayName, ref iLen, (int)AssemblyNameDisplayFlags.ALL);
            if (hr < 0) {
                Marshal.ThrowExceptionForHR(hr);
            }

            return sDisplayName.ToString();
        }

        private IAssemblyEnum m_AssemblyEnum = null;
        private bool done;
    }// class AssemblyCacheEnum

    public class AssemblyCacheInstallReferenceEnum
    {
        public AssemblyCacheInstallReferenceEnum(String assemblyName)
        {
            IAssemblyName fusionName = null;

            int hr = Utils.CreateAssemblyNameObject(
                        out fusionName,
                        assemblyName,
                        CreateAssemblyNameObjectFlags.CANOF_PARSE_DISPLAY_NAME,
                        IntPtr.Zero);

            if (hr >= 0) {
                hr = Utils.CreateInstallReferenceEnum(out refEnum, fusionName, 0, IntPtr.Zero);
            }

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

        public InstallReference GetNextReference()
        {
            IInstallReferenceItem item = null;
            int hr = refEnum.GetNextInstallReferenceItem(out item, 0, IntPtr.Zero);
            if ((uint)hr == 0x80070103) {   // ERROR_NO_MORE_ITEMS
                return null;
            }

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

            IntPtr refData;
            InstallReference instRef = new InstallReference(Guid.Empty, String.Empty, String.Empty);

            hr = item.GetReference(out refData, 0, IntPtr.Zero);
            if (hr < 0) {
                Marshal.ThrowExceptionForHR(hr);
            }

            Marshal.PtrToStructure(refData, instRef);
            return instRef;
        }

        private IInstallReferenceEnum refEnum;
    }

    internal class Utils
    {
        [DllImport(“fusion.dll”)]
        internal static extern int CreateAssemblyEnum(
                out IAssemblyEnum ppEnum,
                IntPtr pUnkReserved,
                IAssemblyName pName,
                AssemblyCacheFlags flags,
                IntPtr pvReserved);

        [DllImport(“fusion.dll”)]
        internal static extern int CreateAssemblyNameObject(
                out IAssemblyName ppAssemblyNameObj,
                [MarshalAs(UnmanagedType.LPWStr)]
                String szAssemblyName,
                CreateAssemblyNameObjectFlags flags,
                IntPtr pvReserved);

        [DllImport(“fusion.dll”)]
        internal static extern int CreateAssemblyCache(
                out IAssemblyCache ppAsmCache,
                int reserved);

        [DllImport(“fusion.dll”)]
        internal static extern int CreateInstallReferenceEnum(
                out IInstallReferenceEnum ppRefEnum,
                IAssemblyName pName,
                int dwFlags,
                IntPtr pvReserved);     
    }
}

<update date=”2004/9/28″> Add enumeration of install reference to managed GAC API wrappers. And Add comments to UninstallAssembly behavior. </update>

Comments (18)

  1. David Levine says:

    Thanks for the wrappers. I’ve been playing around with them and have a couple of questions.

    1. Is there a list of HRESULT error codes that the GAC APIs might return? It would be helpful for displaying meaningful error displays.

    2. Is there an interface for enumerating the trace references? I think this might be useful for utilities trying to detect corrupted systems (references that are no longer valid).

    3. How is the AssemblyCommitFlags flag used when installing assemblies and the value is set to Force?

    4. When uninstalling an assembly what happens when a simple or partial name is used instead of the full name? For example, if the name used is "foobar" without specifying a version, culture, etc., will it attempt to uninstall all versions of foobar? Can it uninstall sets of assemblies (e.g. all instances of a particular culture for a given assembly?)

    5. Can you provide a definition of the assembly Flags in the AssemblyInfo struct, and perhaps for the AssemblyCacheFlags and CreateAssemblyNameObjectFlags?

    I know this is a lot – any additional you can provide is appreciated. Thanks.

  2. 1. Alan described the error codes here

    http://blogs.gotdotnet.com/alanshi/commentview.aspx/18d8429f-ac4f-421b-9e5c-d0fdad0d6b9f

    2. There are interfaces for enumerating trace references. I don’t think many people are interested in it, so I did not include it.

    3. The force behavior is discussed here:

    http://blogs.msdn.com/junfeng/archive/2004/02/14/72666.aspx

    4. Uninstall takes a fully specified name. The behavior for partial name uninstall is undefined.

    5. Those flags are not really useful. I never see people use them. So I exclude them for more clarify.

  3. JLops says:

    Thanks for the wrapper and the examples, they helped alot!

    I tried this code in a ClickOnce deployed app with "Full Trust" and it worked fine. I always thought ClickOnce deployed apps couldn’t touch the GAC? Is this some way around that? If so, is it a bug?

  4. ClickOnce app is all driven by permission. If you are admin on the machine, and ClickOnce app has full trust, it should do can install/uninstall assembly like normal applications.

  5. I just realize there is an absolute need to enumerate trace reference as well, for migration purpose. I’ll update the wrapper later to include that.

  6. David Levine says:

    I followed the link to the KB article and I added support for the trace reference to the wrapper code. Thanks for the info – it helped a lot. I can forward the modified file if you like.

    …I had a couple of remaining questions…

    The AssemblyCommitEnum defines two enumerated fields, Default = 0, and Force = 1. The KB article calls out ASSEMBLYCACHE_INSTALL_FLAG_REFRESH = 1 and xxx_FORCE_REFRESH = 2. To force the install should a value of 1 or 2 be used?

    When enumerating the ZAP cache the AssemblyName object obtained from GetNextAssembly appears to support building the display string differently then the GAC cache objects. It appears to always omit the processor architecture portion of the display name.

    This creates problems later when I try to pass that string to AssemblyCache::QueryAssemblyInfo – it fails it if it does not contain the processor. Also, if I leave off the version info as well as the processor info then it starts working again. Any idea of what I might be doing wrong here?

    Everything else works as expected. Thanks

  7. David,

    Please forward it to me, via the contact link. Saves some time for me:)

    I have to look at the FORCE install with care. It is likely there is a bug in my managed wrapper.

    The managed wrapper is intended for GAC only. It does not work with ZAP cache.

    AssemblyCache::QueryAssemblyInfo does not work with ZAP at all.

    I will explain the PA problem later.

  8. David Levine says:

    Thanks for clarifying some issues; one about the PA part of the name, and the other about the proper value for FORCE versus REFRESH. Everything else was pretty much what I expected.

    I noticed you added a value for an OsInstallGuid guid…I assume this is a means of identifying an assembly installed into the GAC by the OS.

  9. I added it for display purpose. Yes, this one can only used by OS during OS setup. Today only Windows Server 2003 setup uses it.

  10. Flier Lu says:

    Here is a sample program, maby more complete

    http://flier.5i4k.net/GacUtilW.rar

    and some articles in Chinese

    http://www.blogcn.com/User8/flier_lu/index.html?id=3765092

    http://www.blogcn.com/User8/flier_lu/index.html?id=4046367

    I wish these maybe useful. 😀

    Thanks Zhang 😛

  11. Flier,

    Your sample is based on v1.1’s fusion, and it lacks of ProcessorArchitecture. Fusion.h is already in v2.0 beta1. But the documentation is behind.

    Also we already have an AssemblyName class in System.Reflection namespace. You probably don’t want to introduce a new one.

  12. Yves Dolce says:

    &amp;nbsp;

    This is a version that fulfill my needs but if you need filtering, the information required is…

  13. Yves Dolce says:

    &amp;nbsp;

    This is a version that fulfills my needs but if you need filtering, the information required…

  14. Yves Dolce says:

    &amp;nbsp;

    This is a version that fulfills my needs but if you need filtering, the information required…

  15. If you haven’t noticed, assemblies in .Net framework 2.0 have a new name attribute ProcessorArchitecture….

  16. Yves Dolce says:

    &amp;nbsp;

    This is a version that fulfills my needs but if you need filtering, the information required…

  17. If you haven’t noticed, assemblies in .Net framework 2.0 have a new name attribute ProcessorArchitecture….

  18. Recently a number of people have been asking about deploying assemblies into the GAC (Global Assembly…