Cmdlets/src/XpandPwsh.Cmdlets/Gac/GlobalAssemblyCache.cs

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
 
namespace XpandPwsh.Cmdlets.Gac{
    public static class GlobalAssemblyCache{
        public static IEnumerable<AssemblyName> GetAssemblies(){
            ComCheck(FusionApi.CreateAssemblyEnum(out var assemblyEnum, IntPtr.Zero, null, AssemblyCacheFlags.Gac,
                IntPtr.Zero));
 
            IAssemblyName fusionAssemblyName;
            do{
                ComCheck(assemblyEnum.GetNextAssembly(IntPtr.Zero, out fusionAssemblyName, 0));
                if (fusionAssemblyName != null) yield return new AssemblyName(GetDisplayName(fusionAssemblyName));
            } while (fusionAssemblyName != null);
        }
 
        public static string GetAssemblyCacheClr2Path(){
            var bufferSize = 512;
            var buffer = new StringBuilder(bufferSize);
 
            var hResult = FusionApi.GetCachePath(AssemblyCacheFlags.Root, buffer, ref bufferSize);
            if ((uint) hResult == 0x8007007A) // ERROR_INSUFFICIENT_BUFFER
            {
                buffer = new StringBuilder(bufferSize);
                ComCheck(FusionApi.GetCachePath(AssemblyCacheFlags.Root, buffer, ref bufferSize));
            }
            else{
                ComCheck(hResult);
            }
 
            return buffer.ToString();
        }
 
        public static string GetAssemblyCacheClr4Path(){
            var bufferSize = 512;
            var buffer = new StringBuilder(bufferSize);
 
            var hResult = FusionApi.GetCachePath(AssemblyCacheFlags.RootEx, buffer, ref bufferSize);
            if ((uint) hResult == 0x8007007A) // ERROR_INSUFFICIENT_BUFFER
            {
                buffer = new StringBuilder(bufferSize);
                ComCheck(FusionApi.GetCachePath(AssemblyCacheFlags.RootEx, buffer, ref bufferSize));
            }
            else{
                ComCheck(hResult);
            }
 
            return buffer.ToString();
        }
 
        public static void InstallAssembly(string path, InstallReference reference, bool force){
            if (path == null) throw new ArgumentNullException(nameof(path));
 
            AssemblyCommitFlags flags;
            flags = force ? AssemblyCommitFlags.ForceRefresh : AssemblyCommitFlags.Refresh;
 
            FusionInstallReference fusionReference = null;
            if (reference != null){
                if (!reference.CanBeUsed())
                    throw new ArgumentException("InstallReferenceType can not be used", nameof(reference));
 
                fusionReference =
                    new FusionInstallReference(reference.Type, reference.Identifier, reference.Description);
            }
 
            var assemblyCache = GetAssemblyCache();
 
            ComCheck(assemblyCache.InstallAssembly((int) flags, path, fusionReference));
        }
 
        public static UninstallResult UninstallAssembly(AssemblyName assemblyName, InstallReference reference){
            if (assemblyName == null) throw new ArgumentNullException(nameof(assemblyName));
            if (!assemblyName.IsFullyQualified())
                throw new ArgumentOutOfRangeException(nameof(assemblyName), assemblyName,
                    "Must be a fully qualified assembly name");
 
            FusionInstallReference fusionReference = null;
            if (reference != null){
                if (!reference.CanBeUsed())
                    throw new ArgumentException("InstallReferenceType can not be used", nameof(reference));
 
                fusionReference =
                    new FusionInstallReference(reference.Type, reference.Identifier, reference.Description);
            }
 
            var assemblyCache = GetAssemblyCache();
 
            ComCheck(assemblyCache.UninstallAssembly(0, assemblyName.GetFullyQualifiedName(), fusionReference,
                out var disposition));
 
            return (UninstallResult) disposition;
        }
 
        public static IEnumerable<InstallReference> GetInstallReferences(AssemblyName assemblyName){
            ComCheck(FusionApi.CreateAssemblyNameObject(out var fusionAssemblyName, assemblyName.GetFullyQualifiedName(),
                CreateAssemblyNameObjectFlags.ParseDisplayName, IntPtr.Zero));
 
            ComCheck(FusionApi.CreateInstallReferenceEnum(out var installReferenceEnum, fusionAssemblyName, 0,
                IntPtr.Zero));
 
            do{
                var hResult = installReferenceEnum.GetNextInstallReferenceItem(out var item, 0, IntPtr.Zero);
                if ((uint) hResult == 0x80070103) // ERROR_NO_MORE_ITEMS
                    yield break;
                ComCheck(hResult);
 
                ComCheck(item.GetReference(out var refData, 0, IntPtr.Zero));
 
                var fusionReference = new FusionInstallReference();
                Marshal.PtrToStructure(refData, fusionReference);
 
                var reference = new InstallReference(InstallReferenceGuid.ToType(fusionReference.GuidScheme),
                    fusionReference.Identifier,
                    fusionReference.NonCanonicalData);
 
                yield return reference;
            } while (true);
        }
 
        [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
        public static string GetAssemblyPath(AssemblyName assemblyName){
            if (assemblyName == null) throw new ArgumentNullException(nameof(assemblyName));
            if (!assemblyName.IsFullyQualified())
                throw new ArgumentOutOfRangeException(nameof(assemblyName), assemblyName,
                    "Must be a fully qualified assembly name");
 
            var assemblyCache = GetAssemblyCache();
 
            var info = new AssemblyInfo{cbAssemblyInfo = Marshal.SizeOf(typeof(AssemblyInfo)), cchBuf = 1024};
            info.currentAssemblyPath = new string('\0', info.cchBuf);
 
            var hResult = assemblyCache.QueryAssemblyInfo(QueryAssemblyInfoFlags.Default,
                assemblyName.GetFullyQualifiedName(), ref info);
            if ((uint) hResult == 0x8007007A) // ERROR_INSUFFICIENT_BUFFER
            {
                info.currentAssemblyPath = new string('\0', info.cchBuf);
                ComCheck(assemblyCache.QueryAssemblyInfo(QueryAssemblyInfoFlags.Default,
                    assemblyName.GetFullyQualifiedName(), ref info));
            }
            else{
                ComCheck(hResult);
            }
 
            return info.currentAssemblyPath;
        }
 
        public static string GetFullyQualifiedAssemblyName(AssemblyName assemblyName){
            return assemblyName.GetFullyQualifiedName();
        }
 
        public static bool IsFullyQualifiedAssemblyName(AssemblyName assemblyName){
            return assemblyName.IsFullyQualified();
        }
 
        private static string GetDisplayName(IAssemblyName assemblyName){
            var bufferSize = 1024;
            var buffer = new StringBuilder(bufferSize);
 
            var hResult = assemblyName.GetDisplayName(buffer, ref bufferSize, AssemblyNameDisplayFlags.Full);
            if ((uint) hResult == 0x8007007A) // ERROR_INSUFFICIENT_BUFFER
            {
                buffer = new StringBuilder(bufferSize);
                ComCheck(assemblyName.GetDisplayName(buffer, ref bufferSize, AssemblyNameDisplayFlags.Full));
            }
            else{
                ComCheck(hResult);
            }
 
            return buffer.ToString();
        }
 
        [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
        private static int ComCheck(int hResult){
            if (hResult != 0) // S_OK
                Marshal.ThrowExceptionForHR(hResult);
 
            return hResult;
        }
 
        private static IAssemblyCache GetAssemblyCache(){
            ComCheck(FusionApi.CreateAssemblyCache(out var assemblyCache, 0));
            return assemblyCache;
        }
    }
}