|
// 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;
#if DEBUG
using System.Diagnostics;
#endif
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security;
using System.Threading;
using System.Runtime.InteropServices.ComTypes;
using Microsoft.Build.Shared;
using Microsoft.Build.Shared.FileSystem;
#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>
public class UnregisterAssembly : AppDomainIsolatedTaskExtension, IUnregisterAssemblyTaskContract
{
#region Properties
public ITaskItem[] Assemblies { get; set; }
public ITaskItem[] TypeLibFiles { 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>
/// <returns></returns>
public override bool Execute()
{
AssemblyRegistrationCache cacheFile;
if (AssemblyListFile != null)
{
cacheFile = StateFileBase.DeserializeCache<AssemblyRegistrationCache>(AssemblyListFile.ItemSpec, Log);
// no cache file, nothing to do. In case there was a problem reading the cache file, we can't do anything anyway.
if (cacheFile == null)
{
StateFileBase.DeleteFile(AssemblyListFile.ItemSpec, Log);
return true;
}
}
else
{
if (Assemblies == null)
{
Log.LogErrorWithCodeFromResources("UnregisterAssembly.AssemblyPathOrStateFileIsRequired", GetType().Name);
return false;
}
// 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;
}
cacheFile = new AssemblyRegistrationCache();
for (int i = 0; i < Assemblies.Length; i++)
{
// if the type lib path is not supplied, generate default one
if (TypeLibFiles?[i]?.ItemSpec.Length > 0)
{
cacheFile.AddEntry(Assemblies[i].ItemSpec, TypeLibFiles[i].ItemSpec);
}
else
{
cacheFile.AddEntry(Assemblies[i].ItemSpec, Path.ChangeExtension(Assemblies[i].ItemSpec, ".tlb"));
}
}
}
bool taskReturnValue = true;
try
{
for (int i = 0; i < cacheFile.Count; i++)
{
cacheFile.GetEntry(i, out string assemblyPath, out string typeLibraryPath);
try
{
// If one of assemblies failed to unregister, the whole task failed.
// We still process the rest of assemblies though.
if (!Unregister(assemblyPath, typeLibraryPath))
{
taskReturnValue = false;
}
}
catch (ArgumentException ex) // assembly path has invalid chars in it
{
Log.LogErrorWithCodeFromResources("General.InvalidAssemblyName", assemblyPath, 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
{
if (AssemblyListFile != null)
{
StateFileBase.DeleteFile(AssemblyListFile.ItemSpec, Log);
}
}
return taskReturnValue;
}
#endregion
#region Methods
/// <summary>
/// Helper unregistration method
/// </summary>
private bool Unregister(string assemblyPath, string typeLibPath)
{
ErrorUtilities.VerifyThrowArgumentNull(typeLibPath);
Log.LogMessageFromResources(MessageImportance.Low, "UnregisterAssembly.UnregisteringAssembly", assemblyPath);
if (FileSystems.Default.FileExists(assemblyPath))
{
try
{
// Load the specified assembly.
Assembly asm = Assembly.UnsafeLoadFrom(assemblyPath);
var comRegistrar = new RegistrationServices();
try
{
s_unregisteringLock.WaitOne();
// Unregister the assembly
if (!comRegistrar.UnregisterAssembly(asm))
{
// If the assembly doesn't contain any types that could be registered for COM interop,
// warn the user about it
Log.LogWarningWithCodeFromResources("UnregisterAssembly.NoValidTypes", assemblyPath);
}
}
finally
{
s_unregisteringLock.ReleaseMutex();
}
}
catch (ArgumentNullException e)
{
Log.LogErrorWithCodeFromResources("UnregisterAssembly.CantUnregisterAssembly", assemblyPath, e.Message);
return false;
}
catch (InvalidOperationException e)
{
Log.LogErrorWithCodeFromResources("UnregisterAssembly.CantUnregisterAssembly", assemblyPath, e.Message);
return false;
}
catch (TargetInvocationException e)
{
Log.LogErrorWithCodeFromResources("UnregisterAssembly.CantUnregisterAssembly", assemblyPath, e.Message);
return false;
}
catch (IOException e)
{
Log.LogErrorWithCodeFromResources("UnregisterAssembly.CantUnregisterAssembly", assemblyPath, e.Message);
return false;
}
catch (TypeLoadException e)
{
Log.LogErrorWithCodeFromResources("UnregisterAssembly.CantUnregisterAssembly", assemblyPath, e.Message);
return false;
}
catch (UnauthorizedAccessException e)
{
Log.LogErrorWithCodeFromResources("UnregisterAssembly.UnauthorizedAccess", assemblyPath, e.Message);
return false;
}
catch (BadImageFormatException)
{
Log.LogErrorWithCodeFromResources("General.InvalidAssembly", assemblyPath);
return false;
}
catch (SecurityException e) // running as normal user
{
Log.LogErrorWithCodeFromResources("UnregisterAssembly.UnauthorizedAccess", assemblyPath, e.Message);
return false;
}
}
else
{
Log.LogWarningWithCodeFromResources("UnregisterAssembly.UnregisterAsmFileDoesNotExist", assemblyPath);
}
Log.LogMessageFromResources(MessageImportance.Low, "UnregisterAssembly.UnregisteringTypeLib", typeLibPath);
if (FileSystems.Default.FileExists(typeLibPath))
{
try
{
ITypeLib typeLibrary = (ITypeLib)NativeMethods.LoadTypeLibEx(typeLibPath, (int)NativeMethods.REGKIND.REGKIND_NONE);
// Get the library attributes so we can unregister it
IntPtr pTlibAttr = IntPtr.Zero;
try
{
typeLibrary.GetLibAttr(out pTlibAttr);
if (pTlibAttr != IntPtr.Zero)
{
// Unregister the type library
System.Runtime.InteropServices.ComTypes.TYPELIBATTR tlibattr = (System.Runtime.InteropServices.ComTypes.TYPELIBATTR)Marshal.PtrToStructure(pTlibAttr, typeof(System.Runtime.InteropServices.ComTypes.TYPELIBATTR));
NativeMethods.UnregisterTypeLib(ref tlibattr.guid, tlibattr.wMajorVerNum, tlibattr.wMinorVerNum, tlibattr.lcid, tlibattr.syskind);
}
}
finally
{
typeLibrary.ReleaseTLibAttr(pTlibAttr);
Marshal.ReleaseComObject(typeLibrary);
}
}
catch (COMException ex)
{
// if the typelib to be unregistered is not registered, then we don't have anything left to do
if (ex.ErrorCode == NativeMethods.TYPE_E_REGISTRYACCESS)
{
Log.LogWarningWithCodeFromResources("UnregisterAssembly.UnregisterTlbFileNotRegistered", typeLibPath);
}
// if the typelib can't be loaded (say because it's not a valid typelib file) we should report an error
else if (ex.ErrorCode == NativeMethods.TYPE_E_CANTLOADLIBRARY)
{
Log.LogErrorWithCodeFromResources("UnregisterAssembly.UnregisterTlbCantLoadFile", typeLibPath);
return false;
}
// rethrow other exceptions
else
{
#if DEBUG
Debug.Assert(false, "Unexpected exception in UnregisterAssembly.DoExecute. " +
"Please log a MSBuild bug specifying the steps to reproduce the problem.");
#endif
throw;
}
}
}
else
{
Log.LogMessageFromResources(MessageImportance.Low, "UnregisterAssembly.UnregisterTlbFileDoesNotExist", typeLibPath);
}
return true;
}
#endregion
#region Data
private static readonly Mutex s_unregisteringLock = new Mutex(false, unregisteringLockName);
private const string unregisteringLockName = "MSBUILD_V_3_5_UNREGISTER_LOCK";
#endregion
}
#elif !NETFRAMEWORK
public sealed class UnregisterAssembly : TaskRequiresFramework, IUnregisterAssemblyTaskContract
{
public UnregisterAssembly()
: base(nameof(UnregisterAssembly))
{
}
#region Properties
public ITaskItem[] Assemblies { get; set; }
public ITaskItem[] TypeLibFiles { get; set; }
public ITaskItem AssemblyListFile { get; set; }
#endregion
}
#endif
public interface IUnregisterAssemblyTaskContract
{
#region Properties
ITaskItem[] Assemblies { get; set; }
ITaskItem[] TypeLibFiles { get; set; }
ITaskItem AssemblyListFile { get; set; }
#endregion
}
}
|