File: RegisterAssembly.cs
Web Access
Project: ..\..\..\src\Tasks\Microsoft.Build.Tasks.csproj (Microsoft.Build.Tasks.Core)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#if NETFRAMEWORK && FEATURE_APPDOMAIN
 
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Security;
 
using Microsoft.Build.Shared;
using Microsoft.Build.Shared.FileSystem;
using Microsoft.Build.Utilities;
 
#endif
 
using Microsoft.Build.Framework;
 
#nullable disable
 
namespace Microsoft.Build.Tasks
{
#if NETFRAMEWORK && FEATURE_APPDOMAIN
 
    /// <summary>
    /// Registers a managed assembly for COM interop (equivalent of regasm.exe functionality, but this code doesn't actually call the exe).
    /// </summary>
    /// <comment>ITypeLibExporterNotifySink is necessary for the ITypeLibConverter.ConvertAssemblyToTypeLib call.</comment>
    public class RegisterAssembly : AppDomainIsolatedTaskExtension, ITypeLibExporterNotifySink, IRegisterAssemblyTaskContract
    {
        #region Properties
 
        [Required]
        public ITaskItem[] Assemblies
        {
            get
            {
                ErrorUtilities.VerifyThrowArgumentNull(_assemblies, nameof(Assemblies));
                return _assemblies;
            }
            set => _assemblies = value;
        }
 
        private ITaskItem[] _assemblies;
 
        [Output]
        public ITaskItem[] TypeLibFiles { get; set; }
 
        public bool CreateCodeBase { get; set; }
 
        /// <summary>
        /// The cache file for Register/UnregisterAssembly. Necessary for UnregisterAssembly to do the proper clean up.
        /// </summary>
        public ITaskItem AssemblyListFile { get; set; }
 
        #endregion
 
        #region ITask members
 
        /// <summary>
        /// Task entry point
        /// </summary>
        public override bool Execute()
        {
            // TypeLibFiles isn't [Required], but if it is specified, it must have the same length as Assemblies
            if ((TypeLibFiles != null) && (TypeLibFiles.Length != Assemblies.Length))
            {
                Log.LogErrorWithCodeFromResources("General.TwoVectorsMustHaveSameLength", Assemblies.Length, TypeLibFiles.Length, "Assemblies", "TypeLibFiles");
                return false;
            }
 
            if (TypeLibFiles == null)
            {
                TypeLibFiles = new ITaskItem[Assemblies.Length];
            }
 
            AssemblyRegistrationCache cacheFile = null;
 
            if ((AssemblyListFile?.ItemSpec.Length > 0))
            {
                cacheFile = StateFileBase.DeserializeCache<AssemblyRegistrationCache>(AssemblyListFile.ItemSpec, Log) ??
                            new AssemblyRegistrationCache();
            }
 
            bool taskReturnValue = true;
 
            try
            {
                for (int i = 0; i < Assemblies.Length; i++)
                {
                    try
                    {
                        string tlbPath;
 
                        // if the type lib path is not supplied, generate default one
                        if ((TypeLibFiles[i]?.ItemSpec.Length > 0))
                        {
                            tlbPath = TypeLibFiles[i].ItemSpec;
                        }
                        else
                        {
                            tlbPath = Path.ChangeExtension(Assemblies[i].ItemSpec, ".tlb");
                            TypeLibFiles[i] = new TaskItem(tlbPath);
                        }
 
                        // If one of assemblies failed to register, the whole task failed.
                        // We still process the rest of assemblies though.
                        if (!Register(Assemblies[i].ItemSpec, tlbPath))
                        {
                            taskReturnValue = false;
                        }
                        else
                        {
                            cacheFile?.AddEntry(Assemblies[i].ItemSpec, tlbPath);
                        }
                    }
                    catch (ArgumentException ex) // assembly path has invalid chars in it
                    {
                        Log.LogErrorWithCodeFromResources("General.InvalidAssemblyName", Assemblies[i], ex.Message);
                        taskReturnValue = false;
                    }
#if DEBUG
                    catch (Exception e)
                    {
                        Debug.Assert(false, "Unexpected exception in AssemblyRegistration.Execute. " +
                            "Please log a MSBuild bug specifying the steps to reproduce the problem. " +
                            e.Message);
                        throw;
                    }
#endif
                }
            }
            finally
            {
                cacheFile?.SerializeCache(AssemblyListFile.ItemSpec, Log);
            }
 
            return taskReturnValue;
        }
 
        #endregion
 
        #region ITypeLibExporterNotifySink methods
 
        private bool _typeLibExportFailed;
 
        /// <summary>
        /// Callback method for reporting type library export events
        /// </summary>
        public void ReportEvent(ExporterEventKind kind, int code, string msg)
        {
            // if we get an error, log it and remember we should fail ExportTypeLib
            if (kind == ExporterEventKind.ERROR_REFTOINVALIDASSEMBLY)
            {
                Log.LogError(msg);
                _typeLibExportFailed = true;
            }
            // if it's just a warning, log it and proceed
            else if (kind == ExporterEventKind.NOTIF_CONVERTWARNING)
            {
                Log.LogWarning(msg);
            }
            // it's just a status message (type xxx converted etc.), log it at lowest possible priority
            else if (kind == ExporterEventKind.NOTIF_TYPECONVERTED)
            {
                Log.LogMessage(MessageImportance.Low, msg);
            }
            else
            {
                Debug.Assert(false, "Unknown ImporterEventKind value");
                Log.LogMessage(MessageImportance.Low, msg);
            }
        }
 
        /// <summary>
        ///  Callback method for finding type libraries for given assemblies. If we are here, it means
        ///  the type library we're looking for is not in the current directory and it's not registered.
        ///  Currently we assume that all dependent type libs are already registered.
        /// </summary>
        /// <comment>
        ///  In theory, we could automatically register dependent assemblies for COM interop and return
        ///  a newly created typelib here. However, one danger of such approach is the following scenario:
        ///  The user creates several projects registered for COM interop, all of them referencing assembly A.
        ///  The first project that happens to be built will register assembly A for COM interop, creating
        ///  a type library in its output directory and registering it. The other projects will then refer to that
        ///  type library, since it's already registered. If then for some reason the first project is deleted
        ///  from disk, the typelib for assembly A goes away too, and all the other projects, built five years ago,
        ///  suddenly stop working.
        /// </comment>
        public object ResolveRef(Assembly assemblyToResolve)
        {
            ErrorUtilities.VerifyThrowArgumentNull(assemblyToResolve);
 
            Log.LogErrorWithCodeFromResources("RegisterAssembly.AssemblyNotRegisteredForComInterop", assemblyToResolve.GetName().FullName);
            _typeLibExportFailed = true;
            return null;
        }
 
        #endregion
 
        #region Methods
 
        /// <summary>
        /// Helper registration method
        /// </summary>
        private bool Register(string assemblyPath, string typeLibPath)
        {
            ErrorUtilities.VerifyThrowArgumentNull(typeLibPath);
 
            Log.LogMessageFromResources(MessageImportance.Low, "RegisterAssembly.RegisteringAssembly", assemblyPath);
 
            if (!FileSystems.Default.FileExists(assemblyPath))
            {
                Log.LogErrorWithCodeFromResources("RegisterAssembly.RegisterAsmFileDoesNotExist", assemblyPath);
                return false;
            }
 
            ITypeLib typeLib = null;
 
            try
            {
                // Load the specified assembly.
                Assembly asm = Assembly.UnsafeLoadFrom(assemblyPath);
 
                var comRegistrar = new RegistrationServices();
 
                // Register the assembly
                if (!comRegistrar.RegisterAssembly(asm, CreateCodeBase ? AssemblyRegistrationFlags.SetCodeBase : AssemblyRegistrationFlags.None))
                {
                    // If the assembly doesn't contain any types that could be registered for COM interop,
                    // warn the user about it.
                    Log.LogWarningWithCodeFromResources("RegisterAssembly.NoValidTypes", assemblyPath);
                }
 
                // Even if there aren't any types that could be registered for COM interop,
                // regasm still creates and tries to register the type library, so we should too.
                Log.LogMessageFromResources(MessageImportance.Low, "RegisterAssembly.RegisteringTypeLib", typeLibPath);
 
                // only regenerate the type lib if necessary
                if ((!FileSystems.Default.FileExists(typeLibPath)) ||
                    (File.GetLastWriteTime(typeLibPath) < File.GetLastWriteTime(assemblyPath)))
                {
                    // Regenerate the type library
                    try
                    {
                        // if export failed the error message is already logged, so just exit
                        if (!ExportTypeLib(asm, typeLibPath))
                        {
                            return false;
                        }
                    }
                    catch (COMException ex)
                    {
                        Log.LogErrorWithCodeFromResources("RegisterAssembly.CantExportTypeLib", assemblyPath, ex.Message);
                        return false;
                    }
                }
                else
                {
                    Log.LogMessageFromResources(MessageImportance.Low, "RegisterAssembly.TypeLibUpToDate", typeLibPath);
                }
 
                // Also register the type library
                try
                {
                    typeLib = (ITypeLib)NativeMethods.LoadTypeLibEx(typeLibPath, (int)NativeMethods.REGKIND.REGKIND_NONE);
 
                    // if we got here, load must have succeeded
                    NativeMethods.RegisterTypeLib(typeLib, typeLibPath, null);
                }
                catch (COMException ex)
                {
                    Log.LogErrorWithCodeFromResources("RegisterAssembly.CantRegisterTypeLib", typeLibPath, ex.Message);
                    return false;
                }
            }
            catch (ArgumentNullException e)
            {
                Log.LogErrorWithCodeFromResources("RegisterAssembly.CantRegisterAssembly", assemblyPath, e.Message);
                return false;
            }
            catch (InvalidOperationException e)
            {
                Log.LogErrorWithCodeFromResources("RegisterAssembly.CantRegisterAssembly", assemblyPath, e.Message);
                return false;
            }
            catch (TargetInvocationException e)
            {
                Log.LogErrorWithCodeFromResources("RegisterAssembly.CantRegisterAssembly", assemblyPath, e.Message);
                return false;
            }
            catch (IOException e)
            {
                Log.LogErrorWithCodeFromResources("RegisterAssembly.CantRegisterAssembly", assemblyPath, e.Message);
                return false;
            }
            catch (TypeLoadException e)
            {
                Log.LogErrorWithCodeFromResources("RegisterAssembly.CantRegisterAssembly", assemblyPath, e.Message);
                return false;
            }
            catch (UnauthorizedAccessException e)
            {
                Log.LogErrorWithCodeFromResources("RegisterAssembly.UnauthorizedAccess", assemblyPath, e.Message);
                return false;
            }
            catch (BadImageFormatException)
            {
                Log.LogErrorWithCodeFromResources("General.InvalidAssembly", assemblyPath);
                return false;
            }
            catch (SecurityException e)
            {
                Log.LogErrorWithCodeFromResources("RegisterAssembly.CantRegisterAssembly", assemblyPath, e.Message);
                return false;
            }
            finally
            {
                if (typeLib != null)
                {
                    Marshal.ReleaseComObject(typeLib);
                }
            }
 
            return true;
        }
 
        /// <summary>
        ///  Helper method - exports a type library for an assembly. Returns true if succeeded.
        /// </summary>
        private bool ExportTypeLib(Assembly asm, string typeLibFileName)
        {
            _typeLibExportFailed = false;
            ITypeLib convertedTypeLib = null;
 
            try
            {
                // Create a converter and run the conversion
                ITypeLibConverter tlbConverter = new TypeLibConverter();
                convertedTypeLib = (ITypeLib)tlbConverter.ConvertAssemblyToTypeLib(asm, typeLibFileName, 0, this);
 
                if (convertedTypeLib == null || _typeLibExportFailed)
                {
                    return false;
                }
 
                // Persist the type library
                ICreateTypeLib createTypeLib = (ICreateTypeLib)convertedTypeLib;
 
                createTypeLib.SaveAllChanges();
            }
            finally
            {
                if (convertedTypeLib != null)
                {
                    Marshal.ReleaseComObject(convertedTypeLib);
                }
            }
 
            return !_typeLibExportFailed;
        }
 
        #endregion
    }
 
#elif !NETFRAMEWORK

    public sealed class RegisterAssembly : TaskRequiresFramework, IRegisterAssemblyTaskContract
    {
        public RegisterAssembly()
            : base(nameof(RegisterAssembly))
        {
        }
 
        #region Properties

        public ITaskItem[] Assemblies { get; set; }
 
        [Output]
        public ITaskItem[] TypeLibFiles { get; set; }
 
        public bool CreateCodeBase { get; set; }
 
        public ITaskItem AssemblyListFile { get; set; }
 
        #endregion
    }
 
#endif
 
    internal interface IRegisterAssemblyTaskContract
    {
        #region Properties
 
        ITaskItem[] Assemblies { get; set; }
        ITaskItem[] TypeLibFiles { get; set; }
        bool CreateCodeBase { get; set; }
        ITaskItem AssemblyListFile { get; set; }
 
        #endregion
    }
}