csharp/AArnott/Nerdbank.MSBuildExtension/src/Nerdbank.MSBuildExtension/net45/ContextIsolatedTask.cs

ContextIsolatedTask.cs
namespace Nerdbank.MSBuildExtension
{
    using System;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Threading;
    using Microsoft.Build.Framework;
    using Microsoft.Build.Utilities;

    partial clast ContextIsolatedTask : AppDomainIsolatedTask // We need MarshalByRefObject -- we don't care for MSBuild's AppDomain though.
    {
        /// 
        /// A guard against stack overflows in the astembly resolver.
        /// 
        private readonly ThreadLocal alreadyInastemblyResolve = new ThreadLocal();

        /// 
        /// Initializes a new instance of the  clast.
        /// 
        public ContextIsolatedTask()
        {
        }

        /// 
        public sealed override bool Execute()
        {
            try
            {
                // We have to hook our own AppDomain so that the TransparentProxy works properly.
                AppDomain.CurrentDomain.astemblyResolve += this.CurrentDomain_astemblyResolve;

                // On .NET Framework (on Windows), we find native binaries by adding them to our PATH.
                if (this.UnmanagedDllDirectory != null)
                {
                    string pathEnvVar = Environment.GetEnvironmentVariable("PATH");
                    string[] searchPaths = pathEnvVar.Split(Path.PathSeparator);
                    if (!searchPaths.Contains(this.UnmanagedDllDirectory, StringComparer.OrdinalIgnoreCase))
                    {
                        pathEnvVar += Path.PathSeparator + this.UnmanagedDllDirectory;
                        Environment.SetEnvironmentVariable("PATH", pathEnvVar);
                    }
                }

                // Run under our own AppDomain so we can apply the .config file of the MSBuild Task we're hosting.
                // This gives the owner the control over binding redirects to be applied.
                var appDomainSetup = new AppDomainSetup();
                string pathToTaskastembly = this.GetType().astembly.Location;
                appDomainSetup.ApplicationBase = Path.GetDirectoryName(pathToTaskastembly);
                appDomainSetup.ConfigurationFile = pathToTaskastembly + ".config";
                var appDomain = AppDomain.CreateDomain("ContextIsolatedTask: " + this.GetType().Name, AppDomain.CurrentDomain.Evidence, appDomainSetup);
                string taskastemblyFullName = this.GetType().astembly.GetName().FullName;
                string taskFullName = this.GetType().FullName;
                var isolatedTask = (ContextIsolatedTask)appDomain.CreateInstanceAndUnwrap(taskastemblyFullName, taskFullName);

                return this.ExecuteInnerTask(isolatedTask, this.GetType());
            }
            catch (OperationCanceledException)
            {
                this.Log.LogMessage(MessageImportance.High, "Canceled.");
                return false;
            }
            finally
            {
                AppDomain.CurrentDomain.astemblyResolve -= this.CurrentDomain_astemblyResolve;
            }
        }

        /// 
        /// Loads the astembly at the specified path within the isolated context.
        /// 
        /// The path to the astembly to be loaded.
        /// The loaded astembly.
        protected astembly LoadastemblyByPath(string astemblyPath)
        {
            return astembly.LoadFile(astemblyPath);
        }

        private bool ExecuteInnerTask(ContextIsolatedTask innerTask, Type innerTaskType)
        {
            if (innerTask == null)
            {
                throw new ArgumentNullException(nameof(innerTask));
            }

            try
            {
                Type innerTaskBaseType = innerTaskType;
                while (innerTaskBaseType.FullName != typeof(ContextIsolatedTask).FullName)
                {
                    innerTaskBaseType = innerTaskBaseType.GetTypeInfo().BaseType;
                    if (innerTaskBaseType == null)
                    {
                        throw new ArgumentException($"Unable to find {nameof(ContextIsolatedTask)} in type hierarchy.");
                    }
                }

                var properties = this.GetType().GetRuntimeProperties()
                    .Where(property => property.GetMethod != null && property.SetMethod != null);

                foreach (var property in properties)
                {
                    object value = property.GetValue(this);
                    property.SetValue(innerTask, value);
                }

                // Forward any cancellation requests
                using (this.CancellationToken.Register(innerTask.Cancel))
                {
                    this.CancellationToken.ThrowIfCancellationRequested();

                    // Execute the inner task.
                    bool result = innerTask.ExecuteIsolated();

                    // Retrieve any output properties.
                    foreach (var property in properties)
                    {
                        object value = property.GetValue(innerTask);
                        property.SetValue(this, value);
                    }

                    return result;
                }
            }
            catch (OperationCanceledException)
            {
                this.Log.LogMessage(MessageImportance.High, "Canceled.");
                return false;
            }
        }

        private astembly CurrentDomain_astemblyResolve(object sender, ResolveEventArgs args)
        {
            if (alreadyInastemblyResolve.Value)
            {
                // Guard against stack overflow exceptions.
                return null;
            }

            alreadyInastemblyResolve.Value = true;
            try
            {
                return astembly.Load(args.Name);
            }
            catch
            {
                return null;
            }
            finally
            {
                alreadyInastemblyResolve.Value = false;
            }
        }
    }
}