File: BackEnd\Components\RequestBuilder\TaskBuilder.cs
Web Access
Project: ..\..\..\src\Build\Microsoft.Build.csproj (Microsoft.Build)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Generic;
#if FEATURE_APARTMENT_STATE
using System.Diagnostics.CodeAnalysis;
#endif
using System.Linq;
using System.Reflection;
#if FEATURE_APARTMENT_STATE
using System.Runtime.ExceptionServices;
#endif
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Build.BackEnd.Components.RequestBuilder;
using Microsoft.Build.Collections;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Eventing;
using Microsoft.Build.Exceptions;
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using Microsoft.Build.Shared.FileSystem;
using ElementLocation = Microsoft.Build.Construction.ElementLocation;
using ProjectItemInstanceFactory = Microsoft.Build.Execution.ProjectItemInstance.TaskItem.ProjectItemInstanceFactory;
using ReservedPropertyNames = Microsoft.Build.Internal.ReservedPropertyNames;
using TargetLoggingContext = Microsoft.Build.BackEnd.Logging.TargetLoggingContext;
using TaskLoggingContext = Microsoft.Build.BackEnd.Logging.TaskLoggingContext;
 
#nullable disable
 
namespace Microsoft.Build.BackEnd
{
    /// <summary>
    /// The possible values for a task's ContinueOnError attribute.
    /// </summary>
    internal enum ContinueOnError
    {
        /// <summary>
        /// If the task fails, error and stop.
        /// </summary>
        ErrorAndStop,
 
        /// <summary>
        /// If the task fails, error and continue.
        /// </summary>
        ErrorAndContinue,
 
        /// <summary>
        /// If the task fails, warn and continue.
        /// </summary>
        WarnAndContinue
    }
 
    /// <summary>
    /// The TaskBuilder is one of two components related to building tasks, the other being the TaskExecutionHost.  The TaskBuilder is
    /// responsible for all parts dealing with the XML/task declaration.  It determines if the task is intrinsic or extrinsic,
    /// looks up the task in the task registry, determines the task parameters and requests them to be set, and requests outputs
    /// when task execution has been completed.  It is not responsible for reflection over the task instance or anything which
    /// requires dealing with the task instance directly - those actions are handled by the TaskExecutionHost.
    /// </summary>
    internal class TaskBuilder : ITaskBuilder, IBuildComponent
    {
        /// <summary>
        /// The Build Request Entry for which this task is executing.
        /// </summary>
        private BuildRequestEntry _buildRequestEntry;
 
        /// <summary>
        /// The cancellation token
        /// </summary>
        private CancellationToken _cancellationToken;
 
        /// <summary>
        /// The build component host.
        /// </summary>
        private IBuildComponentHost _componentHost;
 
        /// <summary>
        /// The original target child instance
        /// </summary>
        private ProjectTargetInstanceChild _targetChildInstance;
 
        /// <summary>
        /// The task instance for extrinsic tasks
        /// </summary>
        private ProjectTaskInstance _taskNode;
 
        /// <summary>
        /// Host callback for host-aware tasks.
        /// </summary>
        private ITaskHost _taskHostObject;
 
        /// <summary>
        /// indicates whether to ignore task execution failures
        /// </summary>
        private ContinueOnError _continueOnError;
 
        /// <summary>
        /// The logging context for the target in which we are executing.
        /// </summary>
        private TargetLoggingContext _targetLoggingContext;
 
        /// <summary>
        /// Full path to the project, for errors
        /// </summary>
        private string _projectFullPath;
 
        /// <summary>
        /// The target builder callback.
        /// </summary>
        private ITargetBuilderCallback _targetBuilderCallback;
 
        /// <summary>
        /// The task execution host for in-proc tasks.
        /// </summary>
        private TaskExecutionHost _taskExecutionHost;
 
        /// <summary>
        /// The object used to synchronize access to the task execution host.
        /// </summary>
        private Object _taskExecutionHostSync = new Object();
 
        /// <summary>
        /// Constructor
        /// </summary>
        internal TaskBuilder()
        {
        }
 
        /// <summary>
        /// Builds the task specified by the XML.
        /// </summary>
        /// <param name="loggingContext">The logging context of the target</param>
        /// <param name="requestEntry">The build request entry being built</param>
        /// <param name="targetBuilderCallback">The target builder callback.</param>
        /// <param name="taskInstance">The task instance.</param>
        /// <param name="mode">The mode in which to execute tasks.</param>
        /// <param name="inferLookup">The lookup to be used for inference.</param>
        /// <param name="executeLookup">The lookup to be used during execution.</param>
        /// <param name="cancellationToken">The <see cref="CancellationToken"/> to use when executing the task.</param>
        /// <returns>The result of running the task batch.</returns>
        /// <remarks>
        /// The ExecuteTask method takes a task as specified by XML and executes it.  This procedure is comprised
        /// of the following steps:
        /// 1. Loading the Task from its containing assembly by looking it up in the task registry
        /// 2. Determining if the task is batched.  If it is, create the batches and execute each as if it were a non-batched task
        /// 3. If the task is not batched, execute it.
        /// 4. If the task was batched, hold on to its Lookup until all of the natches are done, then merge them.
        /// </remarks>
        public async Task<WorkUnitResult> ExecuteTask(TargetLoggingContext loggingContext, BuildRequestEntry requestEntry, ITargetBuilderCallback targetBuilderCallback, ProjectTargetInstanceChild taskInstance, TaskExecutionMode mode, Lookup inferLookup, Lookup executeLookup, CancellationToken cancellationToken)
        {
            ErrorUtilities.VerifyThrow(taskInstance != null, "Need to specify the task instance.");
 
            _buildRequestEntry = requestEntry;
            _targetBuilderCallback = targetBuilderCallback;
            _cancellationToken = cancellationToken;
            _targetChildInstance = taskInstance;
 
            // In the case of Intrinsic tasks, taskNode will end up null.  Currently this is how we distinguish
            // intrinsic from extrinsic tasks.
            _taskNode = taskInstance as ProjectTaskInstance;
 
            if (_taskNode != null && requestEntry.Request.HostServices != null)
            {
                _taskHostObject = requestEntry.Request.HostServices.GetHostObject(requestEntry.RequestConfiguration.Project.FullPath, loggingContext.Target.Name, _taskNode.Name);
            }
 
            _projectFullPath = requestEntry.RequestConfiguration.Project.FullPath;
 
            // this.handleId = handleId; No handles
            // this.parentModule = parentModule; No task execution module
            _continueOnError = ContinueOnError.ErrorAndStop;
 
            _targetLoggingContext = loggingContext;
 
            WorkUnitResult taskResult = new WorkUnitResult(WorkUnitResultCode.Failed, WorkUnitActionCode.Stop, null);
            if ((mode & TaskExecutionMode.InferOutputsOnly) == TaskExecutionMode.InferOutputsOnly)
            {
                taskResult = await ExecuteTask(TaskExecutionMode.InferOutputsOnly, inferLookup);
            }
 
            if ((mode & TaskExecutionMode.ExecuteTaskAndGatherOutputs) == TaskExecutionMode.ExecuteTaskAndGatherOutputs)
            {
                taskResult = await ExecuteTask(TaskExecutionMode.ExecuteTaskAndGatherOutputs, executeLookup);
            }
 
            return taskResult;
        }
 
        #region IBuildComponent Members
 
        /// <summary>
        /// Sets the build component host.
        /// </summary>
        /// <param name="host">The component host.</param>
        public void InitializeComponent(IBuildComponentHost host)
        {
            _componentHost = host;
            _taskExecutionHost = new TaskExecutionHost(host);
        }
 
        /// <summary>
        /// Shuts down the component.
        /// </summary>
        public void ShutdownComponent()
        {
            lock (_taskExecutionHostSync)
            {
                ErrorUtilities.VerifyThrow(_taskExecutionHost != null, "taskExecutionHost not initialized.");
                _componentHost = null;
 
                IDisposable disposable = _taskExecutionHost as IDisposable;
                disposable?.Dispose();
 
                _taskExecutionHost = null;
            }
        }
 
        #endregion
 
        /// <summary>
        /// Class factory for component creation.
        /// </summary>
        internal static IBuildComponent CreateComponent(BuildComponentType type)
        {
            ErrorUtilities.VerifyThrow(type == BuildComponentType.TaskBuilder, "Cannot create components of type {0}", type);
            return new TaskBuilder();
        }
 
        #region Methods
 
        /// <summary>
        /// Build up a list of all parameters on the task, including those in any Output tags,
        /// in order to find batchable metadata references
        /// </summary>
        /// <returns>The list of parameter values</returns>
        private List<string> CreateListOfParameterValues()
        {
            if (_taskNode == null)
            {
                // This is an intrinsic task.  Batching is not handled here.
                return new List<string>();
            }
 
            List<string> taskParameters = new List<string>(_taskNode.ParametersForBuild.Count + _taskNode.Outputs.Count);
 
            foreach (KeyValuePair<string, (string, ElementLocation)> taskParameter in _taskNode.ParametersForBuild)
            {
                taskParameters.Add(taskParameter.Value.Item1);
            }
 
            // Add parameters on any output tags
            foreach (ProjectTaskInstanceChild taskOutputSpecification in _taskNode.Outputs)
            {
                ProjectTaskOutputItemInstance outputItemInstance = taskOutputSpecification as ProjectTaskOutputItemInstance;
                if (outputItemInstance != null)
                {
                    taskParameters.Add(outputItemInstance.TaskParameter);
                    taskParameters.Add(outputItemInstance.ItemType);
                }
 
                ProjectTaskOutputPropertyInstance outputPropertyInstance = taskOutputSpecification as ProjectTaskOutputPropertyInstance;
                if (outputPropertyInstance != null)
                {
                    taskParameters.Add(outputPropertyInstance.TaskParameter);
                    taskParameters.Add(outputPropertyInstance.PropertyName);
                }
 
                if (!String.IsNullOrEmpty(taskOutputSpecification.Condition))
                {
                    taskParameters.Add(taskOutputSpecification.Condition);
                }
            }
 
            if (!String.IsNullOrEmpty(_taskNode.Condition))
            {
                taskParameters.Add(_taskNode.Condition);
            }
 
            if (!String.IsNullOrEmpty(_taskNode.ContinueOnError))
            {
                taskParameters.Add(_taskNode.ContinueOnError);
            }
 
            return taskParameters;
        }
 
        /// <summary>
        /// Called to execute a task within a target. This method instantiates the task, sets its parameters, and executes it.
        /// </summary>
        /// <returns>true, if successful</returns>
        private async Task<WorkUnitResult> ExecuteTask(TaskExecutionMode mode, Lookup lookup)
        {
            ErrorUtilities.VerifyThrowArgumentNull(lookup);
 
            WorkUnitResult taskResult = new WorkUnitResult(WorkUnitResultCode.Failed, WorkUnitActionCode.Stop, null);
            TaskHost taskHost = null;
 
            List<ItemBucket> buckets = null;
 
            try
            {
                if (_taskNode != null)
                {
                    taskHost = new TaskHost(_componentHost, _buildRequestEntry, _targetChildInstance.Location, _targetBuilderCallback);
                    _taskExecutionHost.InitializeForTask(taskHost, _targetLoggingContext, _buildRequestEntry.RequestConfiguration.Project, _taskNode.Name, _taskNode.Location, _taskHostObject, _continueOnError != ContinueOnError.ErrorAndStop,
#if FEATURE_APPDOMAIN
                        taskHost.AppDomainSetup,
#endif
                        taskHost.IsOutOfProc, _cancellationToken);
                }
 
                List<string> taskParameterValues = CreateListOfParameterValues();
                buckets = BatchingEngine.PrepareBatchingBuckets(taskParameterValues, lookup, _targetChildInstance.Location, _targetLoggingContext);
 
                Dictionary<string, string> lookupHash = null;
 
                // Only create a hash table if there are more than one bucket as this is the only time a property can be overridden
                if (buckets.Count > 1)
                {
                    lookupHash ??= new Dictionary<string, string>(MSBuildNameIgnoreCaseComparer.Default);
                }
 
                WorkUnitResult aggregateResult = new WorkUnitResult();
 
                // Loop through each of the batch buckets and execute them one at a time
                for (int i = 0; i < buckets.Count; i++)
                {
                    // Execute the batch bucket, pass in which bucket we are executing so that we know when to get a new taskId for the bucket.
                    taskResult = await ExecuteBucket(taskHost, (ItemBucket)buckets[i], mode, lookupHash);
 
                    aggregateResult = aggregateResult.AggregateResult(taskResult);
 
                    if (aggregateResult.ActionCode == WorkUnitActionCode.Stop)
                    {
                        break;
                    }
                }
 
                taskResult = aggregateResult;
            }
            finally
            {
                _taskExecutionHost.CleanupForTask();
 
                taskHost?.MarkAsInactive();
 
                // Now all task batches are done, apply all item adds to the outer
                // target batch; we do this even if the task wasn't found (in that case,
                // no items or properties will have been added to the scope)
                if (buckets != null)
                {
                    foreach (ItemBucket bucket in buckets)
                    {
                        bucket.LeaveScope();
                    }
                }
            }
 
            return taskResult;
        }
 
        /// <summary>
        /// Execute a single bucket
        /// </summary>
        /// <returns>true if execution succeeded</returns>
        private async Task<WorkUnitResult> ExecuteBucket(TaskHost taskHost, ItemBucket bucket, TaskExecutionMode howToExecuteTask, Dictionary<string, string> lookupHash)
        {
            // On Intrinsic tasks, we do not allow batchable params, therefore metadata is excluded.
            ParserOptions parserOptions = (_taskNode == null) ? ParserOptions.AllowPropertiesAndItemLists : ParserOptions.AllowAll;
            WorkUnitResult taskResult = new WorkUnitResult(WorkUnitResultCode.Failed, WorkUnitActionCode.Stop, null);
 
            bool condition = ConditionEvaluator.EvaluateCondition(
                _targetChildInstance.Condition,
                parserOptions,
                bucket.Expander,
                ExpanderOptions.ExpandAll,
                _buildRequestEntry.ProjectRootDirectory,
                _targetChildInstance.ConditionLocation,
                FileSystems.Default,
                loggingContext: _targetLoggingContext);
 
            if (!condition)
            {
                LogSkippedTask(bucket, howToExecuteTask);
                return new WorkUnitResult(WorkUnitResultCode.Skipped, WorkUnitActionCode.Continue, null);
            }
 
            // Some tests do not provide an actual taskNode; checking if _taskNode == null prevents those tests from failing.
            // If this is an Intrinsic task, it gets handled in a special fashion.
            if (_taskNode == null)
            {
                try
                {
                    ExecuteIntrinsicTask(bucket);
                    taskResult = new WorkUnitResult(WorkUnitResultCode.Success, WorkUnitActionCode.Continue, null);
                }
                catch (InvalidProjectFileException e)
                {
                    // Make sure the Invalid Project error gets logged *before* TaskFinished.  Otherwise,
                    // the log is confusing.
                    _targetLoggingContext.LogInvalidProjectFileError(e);
                    _continueOnError = ContinueOnError.ErrorAndStop;
                    taskResult = new WorkUnitResult(WorkUnitResultCode.Failed, WorkUnitActionCode.Stop, e);
                }
            }
            else
            {
                if (_componentHost.BuildParameters.SaveOperatingEnvironment)
                {
                    // Change to the project root directory.
                    // If that directory does not exist, do nothing. (Do not check first as it is almost always there and it is slow)
                    // This is because if the project has not been saved, this directory may not exist, yet it is often useful to still be able to build the project.
                    // No errors are masked by doing this: errors loading the project from disk are reported at load time, if necessary.
                    NativeMethodsShared.SetCurrentDirectory(_buildRequestEntry.ProjectRootDirectory);
                }
 
                if (howToExecuteTask == TaskExecutionMode.ExecuteTaskAndGatherOutputs)
                {
                    // We need to find the task before logging the task started event so that the using task statement comes before the task started event
                    IDictionary<string, string> taskIdentityParameters = GatherTaskIdentityParameters(bucket.Expander);
                    (TaskRequirements? requirements, TaskFactoryWrapper taskFactoryWrapper) = _taskExecutionHost.FindTask(taskIdentityParameters);
                    string taskAssemblyLocation = taskFactoryWrapper?.TaskFactoryLoadedType?.Path;
 
                    if (requirements != null)
                    {
                        TaskLoggingContext taskLoggingContext = _targetLoggingContext.LogTaskBatchStarted(_projectFullPath, _targetChildInstance, taskAssemblyLocation);
                        MSBuildEventSource.Log.ExecuteTaskStart(_taskNode?.Name, taskLoggingContext.BuildEventContext.TaskId);
                        _buildRequestEntry.Request.CurrentTaskContext = taskLoggingContext.BuildEventContext;
 
                        try
                        {
                            if (
                                (requirements.Value & TaskRequirements.RequireSTAThread) == TaskRequirements.RequireSTAThread
#if FEATURE_APARTMENT_STATE
                                && (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA)
#endif
                                )
                            {
#if FEATURE_APARTMENT_STATE
                                taskResult = ExecuteTaskInSTAThread(bucket, taskLoggingContext, taskIdentityParameters, taskHost, howToExecuteTask);
#else
                                throw new PlatformNotSupportedException(TaskRequirements.RequireSTAThread.ToString());
#endif
                            }
                            else
                            {
                                taskResult = await InitializeAndExecuteTask(taskLoggingContext, bucket, taskIdentityParameters, taskHost, howToExecuteTask);
                            }
 
                            if (lookupHash != null)
                            {
                                List<string> overrideMessages = bucket.Lookup.GetPropertyOverrideMessages(lookupHash);
                                if (overrideMessages != null)
                                {
                                    foreach (string s in overrideMessages)
                                    {
                                        taskLoggingContext.LogCommentFromText(MessageImportance.Low, s);
                                    }
                                }
                            }
                        }
                        catch (InvalidProjectFileException e)
                        {
                            // Make sure the Invalid Project error gets logged *before* TaskFinished.  Otherwise,
                            // the log is confusing.
                            taskLoggingContext.LogInvalidProjectFileError(e);
                            _continueOnError = ContinueOnError.ErrorAndStop;
                            taskResult = new WorkUnitResult(WorkUnitResultCode.Failed, WorkUnitActionCode.Stop, e);
                        }
                        finally
                        {
                            _buildRequestEntry.Request.CurrentTaskContext = null;
 
                            // Flag the completion of the task.
                            taskLoggingContext.LogTaskBatchFinished(_projectFullPath, taskResult.ResultCode == WorkUnitResultCode.Success || taskResult.ResultCode == WorkUnitResultCode.Skipped);
 
                            if (taskResult.ResultCode == WorkUnitResultCode.Failed && _continueOnError == ContinueOnError.WarnAndContinue)
                            {
                                // We coerce the failing result to a successful result.
                                taskResult = new WorkUnitResult(WorkUnitResultCode.Success, taskResult.ActionCode, taskResult.Exception);
                            }
 
                            MSBuildEventSource.Log.ExecuteTaskStop(_taskNode?.Name, taskLoggingContext.BuildEventContext.TaskId);
                        }
                    }
                }
                else
                {
                    ErrorUtilities.VerifyThrow(howToExecuteTask == TaskExecutionMode.InferOutputsOnly, "should be inferring");
 
                    ErrorUtilities.VerifyThrow(
                        GatherTaskOutputs(null, howToExecuteTask, bucket),
                        "The method GatherTaskOutputs() should never fail when inferring task outputs.");
 
                    if (lookupHash != null)
                    {
                        List<string> overrideMessages = bucket.Lookup.GetPropertyOverrideMessages(lookupHash);
                        if (overrideMessages != null)
                        {
                            foreach (string s in overrideMessages)
                            {
                                _targetLoggingContext.LogCommentFromText(MessageImportance.Low, s);
                            }
                        }
                    }
 
                    taskResult = new WorkUnitResult(WorkUnitResultCode.Success, WorkUnitActionCode.Continue, null);
                }
            }
 
            return taskResult;
        }
 
        /// <summary>
        /// Returns the set of parameters that can contribute to a task's identity, and their values for this particular task.
        /// </summary>
        private IDictionary<string, string> GatherTaskIdentityParameters(Expander<ProjectPropertyInstance, ProjectItemInstance> expander)
        {
            ErrorUtilities.VerifyThrowInternalNull(_taskNode, "taskNode"); // taskNode should never be null when we're calling this method.
 
            string msbuildArchitecture = expander.ExpandIntoStringAndUnescape(_taskNode.MSBuildArchitecture ?? String.Empty, ExpanderOptions.ExpandAll, _taskNode.MSBuildArchitectureLocation ?? ElementLocation.EmptyLocation);
            string msbuildRuntime = expander.ExpandIntoStringAndUnescape(_taskNode.MSBuildRuntime ?? String.Empty, ExpanderOptions.ExpandAll, _taskNode.MSBuildRuntimeLocation ?? ElementLocation.EmptyLocation);
 
            IDictionary<string, string> taskIdentityParameters = null;
 
            // only bother to create a task identity parameter set if we're putting anything in there -- otherwise,
            // a null set will be treated as equivalent to all parameters being "don't care".
            if (msbuildRuntime != String.Empty || msbuildArchitecture != String.Empty)
            {
                taskIdentityParameters = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
 
                msbuildArchitecture = msbuildArchitecture == String.Empty ? XMakeAttributes.MSBuildArchitectureValues.any : msbuildArchitecture.Trim();
                msbuildRuntime = msbuildRuntime == String.Empty ? XMakeAttributes.MSBuildRuntimeValues.any : msbuildRuntime.Trim();
 
                taskIdentityParameters.Add(XMakeAttributes.runtime, msbuildRuntime);
                taskIdentityParameters.Add(XMakeAttributes.architecture, msbuildArchitecture);
            }
 
            return taskIdentityParameters;
        }
 
#if FEATURE_APARTMENT_STATE
        /// <summary>
        /// Executes the task using an STA thread.
        /// </summary>
        /// <comment>
        /// STA thread launching also being used in XMakeCommandLine\OutOfProcTaskAppDomainWrapperBase.cs, InstantiateAndExecuteTaskInSTAThread method.
        /// Any bug fixes made to this code, please ensure that you also fix that code.
        /// </comment>
        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exception is caught and rethrown in the correct thread.")]
        private WorkUnitResult ExecuteTaskInSTAThread(ItemBucket bucket, TaskLoggingContext taskLoggingContext, IDictionary<string, string> taskIdentityParameters, TaskHost taskHost, TaskExecutionMode howToExecuteTask)
        {
            WorkUnitResult taskResult = new WorkUnitResult(WorkUnitResultCode.Failed, WorkUnitActionCode.Stop, null);
            Thread staThread = null;
            ExceptionDispatchInfo exceptionFromExecution = null;
            ManualResetEvent taskRunnerFinished = new ManualResetEvent(false);
            try
            {
                ThreadStart taskRunnerDelegate = delegate ()
                {
                    Lookup.Scope scope = bucket.Lookup.EnterScope("STA Thread for Task");
                    try
                    {
                        taskResult = InitializeAndExecuteTask(taskLoggingContext, bucket, taskIdentityParameters, taskHost, howToExecuteTask).Result;
                    }
                    catch (Exception e) when (!ExceptionHandling.IsCriticalException(e))
                    {
                        exceptionFromExecution = ExceptionDispatchInfo.Capture(e);
                    }
                    finally
                    {
                        scope.LeaveScope();
                        taskRunnerFinished.Set();
                    }
                };
 
                staThread = new Thread(taskRunnerDelegate);
                staThread.SetApartmentState(ApartmentState.STA);
                staThread.Name = "MSBuild STA task runner thread";
                staThread.CurrentCulture = _componentHost.BuildParameters.Culture;
                staThread.CurrentUICulture = _componentHost.BuildParameters.UICulture;
                staThread.Start();
 
                // TODO: Why not just Join on the thread???
                taskRunnerFinished.WaitOne();
            }
            finally
            {
                taskRunnerFinished.Close();
                taskRunnerFinished = null;
            }
 
            exceptionFromExecution?.Throw();
 
            return taskResult;
        }
#endif
 
        /// <summary>
        /// Logs a task skipped message if necessary.
        /// </summary>
        private void LogSkippedTask(ItemBucket bucket, TaskExecutionMode howToExecuteTask)
        {
            // If this is an Intrinsic task, it does not log skips.
            if (_taskNode != null)
            {
                if (howToExecuteTask == TaskExecutionMode.ExecuteTaskAndGatherOutputs)
                {
                    if (!_targetLoggingContext.LoggingService.OnlyLogCriticalEvents)
                    {
                        // Expand the expression for the Log.  Since we know the condition evaluated to false, leave unexpandable properties in the condition so as not to cause an error
                        string expanded = bucket.Expander.ExpandIntoStringAndUnescape(_targetChildInstance.Condition, ExpanderOptions.ExpandAll | ExpanderOptions.LeavePropertiesUnexpandedOnError | ExpanderOptions.Truncate, _targetChildInstance.ConditionLocation);
 
                        // Whilst we are within the processing of the task, we haven't actually started executing it, so
                        // our skip task message needs to be in the context of the target. However any errors should be reported
                        // at the point where the task appears in the project.
                        _targetLoggingContext.LogComment(
                            MessageImportance.Low,
                            "TaskSkippedFalseCondition",
                            _taskNode.Name,
                            _targetChildInstance.Condition,
                            expanded);
                    }
                }
            }
        }
 
        /// <summary>
        /// Runs an intrinsic task.
        /// </summary>
        private void ExecuteIntrinsicTask(ItemBucket bucket)
        {
            IntrinsicTask task = IntrinsicTask.InstantiateTask(
                _targetChildInstance,
                _targetLoggingContext,
                _buildRequestEntry.RequestConfiguration.Project,
                _taskExecutionHost.LogTaskInputs);
 
            task.ExecuteTask(bucket.Lookup);
        }
 
        /// <summary>
        /// Initializes and executes the task.
        /// </summary>
        private async Task<WorkUnitResult> InitializeAndExecuteTask(TaskLoggingContext taskLoggingContext, ItemBucket bucket, IDictionary<string, string> taskIdentityParameters, TaskHost taskHost, TaskExecutionMode howToExecuteTask)
        {
            if (!_taskExecutionHost.InitializeForBatch(taskLoggingContext, bucket, taskIdentityParameters))
            {
                ProjectErrorUtilities.ThrowInvalidProject(_targetChildInstance.Location, "TaskDeclarationOrUsageError", _taskNode.Name);
            }
 
            using var assemblyLoadsTracker = AssemblyLoadsTracker.StartTracking(taskLoggingContext, AssemblyLoadingContext.TaskRun, _taskExecutionHost?.TaskInstance?.GetType());
 
            try
            {
                // UNDONE: Move this and the task host.
                taskHost.LoggingContext = taskLoggingContext;
                WorkUnitResult executionResult = await ExecuteInstantiatedTask(_taskExecutionHost, taskLoggingContext, taskHost, bucket, howToExecuteTask);
 
                ErrorUtilities.VerifyThrow(executionResult != null, "Unexpected null execution result");
 
                return executionResult;
            }
            finally
            {
                _taskExecutionHost.CleanupForBatch();
            }
        }
 
        /// <summary>
        /// Recomputes the task's "ContinueOnError" setting.
        /// </summary>
        /// <param name="bucket">The bucket being executed.</param>
        /// <param name="taskHost">The task host to use.</param>
        /// <remarks>
        /// There are four possible values:
        /// false - Error and stop if the task fails.
        /// true - Warn and continue if the task fails.
        /// ErrorAndContinue - Error and continue if the task fails.
        /// WarnAndContinue - Same as true.
        /// </remarks>
        private void UpdateContinueOnError(ItemBucket bucket, TaskHost taskHost)
        {
            string continueOnErrorAttribute = _taskNode.ContinueOnError;
            _continueOnError = ContinueOnError.ErrorAndStop;
 
            if (_taskNode.ContinueOnErrorLocation != null)
            {
                string expandedValue = bucket.Expander.ExpandIntoStringAndUnescape(continueOnErrorAttribute, ExpanderOptions.ExpandAll, _taskNode.ContinueOnErrorLocation); // expand embedded item vectors after expanding properties and item metadata
                try
                {
                    if (String.Equals(XMakeAttributes.ContinueOnErrorValues.errorAndContinue, expandedValue, StringComparison.OrdinalIgnoreCase))
                    {
                        _continueOnError = ContinueOnError.ErrorAndContinue;
                    }
                    else if (String.Equals(XMakeAttributes.ContinueOnErrorValues.warnAndContinue, expandedValue, StringComparison.OrdinalIgnoreCase))
                    {
                        _continueOnError = ContinueOnError.WarnAndContinue;
                    }
                    else if (String.Equals(XMakeAttributes.ContinueOnErrorValues.errorAndStop, expandedValue, StringComparison.OrdinalIgnoreCase))
                    {
                        _continueOnError = ContinueOnError.ErrorAndStop;
                    }
                    else
                    {
                        // if attribute doesn't exist, default to "false"
                        // otherwise, convert its value to a boolean
                        bool value = ConversionUtilities.ConvertStringToBool(expandedValue);
                        _continueOnError = value ? ContinueOnError.WarnAndContinue : ContinueOnError.ErrorAndStop;
                    }
                }
                catch (ArgumentException e)
                {
                    // handle errors in string-->bool conversion
                    ProjectErrorUtilities.ThrowInvalidProject(_taskNode.ContinueOnErrorLocation, "InvalidContinueOnErrorAttribute", _taskNode.Name, e.Message);
                }
            }
 
            // We need to access an internal method of the EngineProxy in order to update the value
            // of continueOnError that will be returned to the task when the task queries IBuildEngine for it
            taskHost.ContinueOnError = (_continueOnError != ContinueOnError.ErrorAndStop);
            taskHost.ConvertErrorsToWarnings = (_continueOnError == ContinueOnError.WarnAndContinue);
        }
 
        /// <summary>
        /// Execute a task object for a given bucket.
        /// </summary>
        /// <param name="taskExecutionHost">The host used to execute the task.</param>
        /// <param name="taskLoggingContext">The logging context.</param>
        /// <param name="taskHost">The task host for the task.</param>
        /// <param name="bucket">The batching bucket</param>
        /// <param name="howToExecuteTask">The task execution mode</param>
        /// <returns>The result of running the task.</returns>
        private async Task<WorkUnitResult> ExecuteInstantiatedTask(TaskExecutionHost taskExecutionHost, TaskLoggingContext taskLoggingContext, TaskHost taskHost, ItemBucket bucket, TaskExecutionMode howToExecuteTask)
        {
            UpdateContinueOnError(bucket, taskHost);
 
            bool taskResult = false;
 
            WorkUnitResultCode resultCode = WorkUnitResultCode.Success;
            WorkUnitActionCode actionCode = WorkUnitActionCode.Continue;
 
            if (!taskExecutionHost.SetTaskParameters(_taskNode.ParametersForBuild))
            {
                // The task cannot be initialized.
                ProjectErrorUtilities.ThrowInvalidProject(_targetChildInstance.Location, "TaskParametersError", _taskNode.Name, String.Empty);
            }
            else
            {
                bool taskReturned = false;
                Exception taskException = null;
 
                // If this is the MSBuild task, we need to execute it's special internal method.
                try
                {
                    if (taskExecutionHost.TaskInstance is MSBuild msbuildTask)
                    {
                        var undeclaredProjects = GetUndeclaredProjects(msbuildTask);
 
                        if (undeclaredProjects?.Count > 0)
                        {
                            _continueOnError = ContinueOnError.ErrorAndStop;
 
                            taskException = new InvalidProjectFileException(
                                taskHost.ProjectFileOfTaskNode,
                                taskHost.LineNumberOfTaskNode,
                                taskHost.ColumnNumberOfTaskNode,
                                0,
                                0,
                                ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword(
                                    "UndeclaredMSBuildTasksNotAllowedInIsolatedGraphBuilds",
                                    string.Join(";", undeclaredProjects.Select(p => $"\"{p}\"")),
                                    taskExecutionHost.ProjectInstance.FullPath),
                                null,
                                null,
                                null);
                        }
                        else
                        {
                            _targetBuilderCallback.EnterMSBuildCallbackState();
 
                            try
                            {
                                taskResult = await msbuildTask.ExecuteInternal();
                            }
                            finally
                            {
                                _targetBuilderCallback.ExitMSBuildCallbackState();
                            }
                        }
                    }
                    else if (taskExecutionHost.TaskInstance is CallTarget callTargetTask)
                    {
                        taskResult = await callTargetTask.ExecuteInternal();
                    }
                    else
                    {
#if FEATURE_FILE_TRACKER
                        using (FullTracking.Track(taskLoggingContext.TargetLoggingContext.Target.Name, _taskNode.Name, _buildRequestEntry.ProjectRootDirectory, _buildRequestEntry.RequestConfiguration.Project.PropertiesToBuildWith))
#endif
                        {
                            taskResult = taskExecutionHost.Execute();
                        }
                    }
                }
                catch (Exception ex)
                {
                    if (ExceptionHandling.IsCriticalException(ex) || Environment.GetEnvironmentVariable("MSBUILDDONOTCATCHTASKEXCEPTIONS") == "1")
                    {
                        taskLoggingContext.LogFatalTaskError(
                            ex,
                            new BuildEventFileInfo(_targetChildInstance.Location),
                            _taskNode.Name);
 
                        throw new CriticalTaskException(ex);
                    }
 
                    taskException = ex;
                }
 
                if (taskException == null)
                {
                    taskReturned = true;
 
                    // Set the property "MSBuildLastTaskResult" to reflect whether the task succeeded or not.
                    // The main use of this is if ContinueOnError is true -- so that the next task can consult the result.
                    // So we want it to be "false" even if ContinueOnError is true.
                    // The constants "true" and "false" should NOT be localized. They become property values.
                    bucket.Lookup.SetProperty(ProjectPropertyInstance.Create(ReservedPropertyNames.lastTaskResult, taskResult ? "true" : "false", true/* may be reserved */, _buildRequestEntry.RequestConfiguration.Project.IsImmutable));
                }
                else
                {
                    var type = taskException.GetType();
 
                    if (type == typeof(LoggerException))
                    {
                        // if a logger has failed, abort immediately
                        // Polite logger failure
                        _continueOnError = ContinueOnError.ErrorAndStop;
 
                        // Rethrow wrapped in order to avoid losing the callstack
                        throw new LoggerException(taskException.Message, taskException);
                    }
                    else if (type == typeof(InternalLoggerException))
                    {
                        // Logger threw arbitrary exception
                        _continueOnError = ContinueOnError.ErrorAndStop;
                        InternalLoggerException ex = taskException as InternalLoggerException;
 
                        // Rethrow wrapped in order to avoid losing the callstack
                        throw new InternalLoggerException(taskException.Message, taskException, ex.BuildEventArgs, ex.ErrorCode, ex.HelpKeyword, ex.InitializationException);
                    }
                    else if (type == typeof(ThreadAbortException))
                    {
#if !NET6_0_OR_GREATER && !NET6_0 // This is redundant but works around https://github.com/dotnet/sdk/issues/20700
                        Thread.ResetAbort();
#endif
                        _continueOnError = ContinueOnError.ErrorAndStop;
 
                        // Cannot rethrow wrapped as ThreadAbortException is sealed and has no appropriate constructor
                        // Stack will be lost
                        throw taskException;
                    }
                    else if (type == typeof(BuildAbortedException))
                    {
                        _continueOnError = ContinueOnError.ErrorAndStop;
 
                        // Rethrow wrapped in order to avoid losing the callstack
                        throw new BuildAbortedException(taskException.Message, (BuildAbortedException)taskException);
                    }
                    else if (type == typeof(CircularDependencyException))
                    {
                        _continueOnError = ContinueOnError.ErrorAndStop;
                        ProjectErrorUtilities.ThrowInvalidProject(taskLoggingContext.Task.Location, "CircularDependency", taskLoggingContext.TargetLoggingContext.Target.Name);
                    }
                    else if (type == typeof(InvalidProjectFileException))
                    {
                        // Just in case this came out of a task, make sure it's not
                        // marked as having been logged.
                        InvalidProjectFileException ipex = (InvalidProjectFileException)taskException;
                        ipex.HasBeenLogged = false;
 
                        if (_continueOnError != ContinueOnError.ErrorAndStop)
                        {
                            taskLoggingContext.LogInvalidProjectFileError(ipex);
                            taskLoggingContext.LogComment(MessageImportance.Normal, "ErrorConvertedIntoWarning");
                        }
                        else
                        {
                            // Rethrow wrapped in order to avoid losing the callstack
                            throw new InvalidProjectFileException(ipex.Message, ipex);
                        }
                    }
                    else if (type == typeof(Exception) || type.GetTypeInfo().IsSubclassOf(typeof(Exception)))
                    {
                        // Occasionally, when debugging a very uncommon task exception, it is useful to loop the build with
                        // a debugger attached to break on 2nd chance exceptions.
                        // That requires that there needs to be a way to not catch here, by setting an environment variable.
                        if (ExceptionHandling.IsCriticalException(taskException) || (Environment.GetEnvironmentVariable("MSBUILDDONOTCATCHTASKEXCEPTIONS") == "1"))
                        {
                            // Wrapping in an Exception will unfortunately mean that this exception would fly through any IsCriticalException above.
                            // However, we should not have any, also we should not have stashed such an exception anyway.
                            throw new Exception(taskException.Message, taskException);
                        }
 
                        Exception exceptionToLog = taskException;
 
                        if (exceptionToLog is TargetInvocationException)
                        {
                            exceptionToLog = exceptionToLog.InnerException;
                        }
 
                        // handle any exception thrown by the task during execution
                        // NOTE: We catch ALL exceptions here, to attempt to completely isolate the Engine
                        // from failures in the task.
                        if (_continueOnError == ContinueOnError.WarnAndContinue)
                        {
                            taskLoggingContext.LogTaskWarningFromException(
                                exceptionToLog,
                                new BuildEventFileInfo(_targetChildInstance.Location),
                                _taskNode.Name);
 
                            // Log a message explaining why we converted the previous error into a warning.
                            taskLoggingContext.LogComment(MessageImportance.Normal, "ErrorConvertedIntoWarning");
                        }
                        else
                        {
                            taskLoggingContext.LogFatalTaskError(
                                exceptionToLog,
                                new BuildEventFileInfo(_targetChildInstance.Location),
                                _taskNode.Name);
                        }
                    }
                    else
                    {
                        ErrorUtilities.ThrowInternalErrorUnreachable();
                    }
                }
 
                // When a task fails it must log an error. If a task fails to do so,
                // that is logged as an error. MSBuild tasks are an exception because
                // errors are not logged directly from them, but the tasks spawned by them.
                IBuildEngine be = taskExecutionHost.TaskInstance.BuildEngine;
                if (taskReturned // if the task returned
                    && !taskResult // and it returned false
                    && !taskLoggingContext.HasLoggedErrors // and it didn't log any errors
                    && (be is TaskHost th ? th.BuildRequestsSucceeded : false)
                    && !(_cancellationToken.CanBeCanceled && _cancellationToken.IsCancellationRequested)) // and it wasn't cancelled
                {
                    // Then decide how to log MSB4181
                    if (be is IBuildEngine7 be7 && be7.AllowFailureWithoutError)
                    {
                        // If it's allowed to fail without error, log as a message
                        taskLoggingContext.LogComment(MessageImportance.Normal, "TaskReturnedFalseButDidNotLogError", _taskNode.Name);
                    }
                    else if (_continueOnError == ContinueOnError.WarnAndContinue)
                    {
                        taskLoggingContext.LogWarning(null,
                            new BuildEventFileInfo(_targetChildInstance.Location),
                            "TaskReturnedFalseButDidNotLogError",
                            _taskNode.Name);
 
                        taskLoggingContext.LogComment(MessageImportance.Normal, "ErrorConvertedIntoWarning");
                    }
                    else
                    {
                        taskLoggingContext.LogError(new BuildEventFileInfo(_targetChildInstance.Location),
                            "TaskReturnedFalseButDidNotLogError",
                            _taskNode.Name);
                    }
                }
 
                // If the task returned attempt to gather its outputs.  If gathering outputs fails set the taskResults
                // to false
                if (taskReturned)
                {
                    taskResult = GatherTaskOutputs(taskExecutionHost, howToExecuteTask, bucket) && taskResult;
                }
 
                // If the taskResults are false look at ContinueOnError.  If ContinueOnError=false (default)
                // mark the taskExecutedSuccessfully=false.  Otherwise let the task succeed but log a normal
                // pri message that says this task is continuing because ContinueOnError=true
                resultCode = taskResult ? WorkUnitResultCode.Success : WorkUnitResultCode.Failed;
                actionCode = WorkUnitActionCode.Continue;
                if (resultCode == WorkUnitResultCode.Failed)
                {
                    if (_continueOnError == ContinueOnError.ErrorAndStop)
                    {
                        actionCode = WorkUnitActionCode.Stop;
                    }
                    else
                    {
                        // This is the ErrorAndContinue or WarnAndContinue case...
                        string settingString = "true";
                        if (_taskNode.ContinueOnErrorLocation != null)
                        {
                            settingString = bucket.Expander.ExpandIntoStringAndUnescape(_taskNode.ContinueOnError, ExpanderOptions.ExpandAll, _taskNode.ContinueOnErrorLocation); // expand embedded item vectors after expanding properties and item metadata
                        }
 
                        taskLoggingContext.LogComment(
                            MessageImportance.Normal,
                            "TaskContinuedDueToContinueOnError",
                            "ContinueOnError",
                            _taskNode.Name,
                            settingString);
 
                        actionCode = WorkUnitActionCode.Continue;
                    }
                }
            }
 
            WorkUnitResult result = new WorkUnitResult(resultCode, actionCode, null);
 
            return result;
        }
 
        private List<string> GetUndeclaredProjects(MSBuild msbuildTask)
        {
            ProjectIsolationMode isolateProjects = _componentHost.BuildParameters.ProjectIsolationMode;
            if (isolateProjects == ProjectIsolationMode.False || isolateProjects == ProjectIsolationMode.MessageUponIsolationViolation)
            {
                return null;
            }
 
            var projectReferenceItems = _buildRequestEntry.RequestConfiguration.Project.GetItems(ItemTypeNames.ProjectReference);
 
            var declaredProjects = new HashSet<string>(projectReferenceItems.Count + 1, FileUtilities.PathComparer);
 
            foreach (var projectReferenceItem in projectReferenceItems)
            {
                declaredProjects.Add(FileUtilities.NormalizePath(projectReferenceItem.EvaluatedInclude));
            }
 
            // allow a project to msbuild itself
            declaredProjects.Add(FileUtilities.NormalizePath(_taskExecutionHost.ProjectInstance.FullPath));
 
            List<string> undeclaredProjects = null;
 
            foreach (var msbuildProject in msbuildTask.Projects)
            {
                var normalizedMSBuildProject = FileUtilities.NormalizePath(msbuildProject.ItemSpec);
 
                if (
                    !(declaredProjects.Contains(normalizedMSBuildProject)
                      || _buildRequestEntry.RequestConfiguration.ShouldSkipIsolationConstraintsForReference(normalizedMSBuildProject)))
                {
                    undeclaredProjects ??= new List<string>(projectReferenceItems.Count);
                    undeclaredProjects.Add(normalizedMSBuildProject);
                }
            }
 
            return undeclaredProjects;
        }
 
        /// <summary>
        /// Gathers task outputs in two ways:
        /// 1) Given an instantiated task that has finished executing, it extracts the outputs using .NET reflection.
        /// 2) Otherwise, it parses the task's output specifications and (statically) infers the outputs.
        /// </summary>
        /// <param name="taskExecutionHost">The task execution host.</param>
        /// <param name="howToExecuteTask">The task execution mode</param>
        /// <param name="bucket">The bucket to which the task execution belongs.</param>
        /// <returns>true, if successful</returns>
        private bool GatherTaskOutputs(TaskExecutionHost taskExecutionHost, TaskExecutionMode howToExecuteTask, ItemBucket bucket)
        {
            bool gatheredTaskOutputsSuccessfully = true;
 
            foreach (ProjectTaskInstanceChild taskOutputSpecification in _taskNode.Outputs)
            {
                // if the task's outputs are supposed to be gathered
                bool condition = ConditionEvaluator.EvaluateCondition(
                    taskOutputSpecification.Condition,
                    ParserOptions.AllowAll,
                    bucket.Expander,
                    ExpanderOptions.ExpandAll,
                    _buildRequestEntry.ProjectRootDirectory,
                    taskOutputSpecification.ConditionLocation,
                    FileSystems.Default,
                    _targetLoggingContext);
 
                if (condition)
                {
                    string taskParameterName = null;
                    bool outputTargetIsItem = false;
                    string outputTargetName = null;
 
                    // check where the outputs are going -- into a vector, or a property?
                    ProjectTaskOutputItemInstance taskOutputItemInstance = taskOutputSpecification as ProjectTaskOutputItemInstance;
 
                    if (taskOutputItemInstance != null)
                    {
                        // expand all embedded properties, item metadata and item vectors in the item type name
                        outputTargetIsItem = true;
                        outputTargetName = bucket.Expander.ExpandIntoStringAndUnescape(taskOutputItemInstance.ItemType, ExpanderOptions.ExpandAll, taskOutputItemInstance.ItemTypeLocation);
                        taskParameterName = taskOutputItemInstance.TaskParameter;
 
                        ProjectErrorUtilities.VerifyThrowInvalidProject(
                            outputTargetName.Length > 0,
                            taskOutputItemInstance.ItemTypeLocation,
                            "InvalidEvaluatedAttributeValue",
                            outputTargetName,
                            taskOutputItemInstance.ItemType,
                            XMakeAttributes.itemName,
                            XMakeElements.output);
                    }
                    else
                    {
                        ProjectTaskOutputPropertyInstance taskOutputPropertyInstance = taskOutputSpecification as ProjectTaskOutputPropertyInstance;
                        outputTargetIsItem = false;
 
                        // expand all embedded properties, item metadata and item vectors in the property name
                        outputTargetName = bucket.Expander.ExpandIntoStringAndUnescape(taskOutputPropertyInstance.PropertyName, ExpanderOptions.ExpandAll, taskOutputPropertyInstance.PropertyNameLocation);
                        taskParameterName = taskOutputPropertyInstance.TaskParameter;
 
                        ProjectErrorUtilities.VerifyThrowInvalidProject(
                            outputTargetName.Length > 0,
                            taskOutputPropertyInstance.PropertyNameLocation,
                            "InvalidEvaluatedAttributeValue",
                            outputTargetName,
                            taskOutputPropertyInstance.PropertyName,
                            XMakeAttributes.propertyName,
                            XMakeElements.output);
                    }
 
                    string unexpandedTaskParameterName = taskParameterName;
                    taskParameterName = bucket.Expander.ExpandIntoStringAndUnescape(taskParameterName, ExpanderOptions.ExpandAll, taskOutputSpecification.TaskParameterLocation);
 
                    ProjectErrorUtilities.VerifyThrowInvalidProject(
                        taskParameterName.Length > 0,
                        taskOutputSpecification.TaskParameterLocation,
                        "InvalidEvaluatedAttributeValue",
                        taskParameterName,
                        unexpandedTaskParameterName,
                        XMakeAttributes.taskParameter,
                        XMakeElements.output);
 
                    // if we're gathering outputs by .NET reflection
                    if (howToExecuteTask == TaskExecutionMode.ExecuteTaskAndGatherOutputs)
                    {
                        gatheredTaskOutputsSuccessfully = taskExecutionHost.GatherTaskOutputs(taskParameterName, taskOutputSpecification.Location, outputTargetIsItem, outputTargetName);
                    }
                    else
                    {
                        // If we're inferring outputs based on information in the task and <Output> tags
                        ErrorUtilities.VerifyThrow(howToExecuteTask == TaskExecutionMode.InferOutputsOnly, "should be inferring");
 
                        // UNDONE: Refactor this method to use the same flag/string paradigm we use above, rather than two strings and the task output spec.
                        InferTaskOutputs(bucket.Lookup, taskOutputSpecification, taskParameterName, outputTargetName, outputTargetName, bucket);
                    }
                }
 
                if (!gatheredTaskOutputsSuccessfully)
                {
                    break;
                }
            }
 
            return gatheredTaskOutputsSuccessfully;
        }
 
        /// <summary>
        /// Uses the given task output specification to (statically) infer the task's outputs.
        /// </summary>
        /// <param name="lookup">The lookup</param>
        /// <param name="taskOutputSpecification">The task output specification</param>
        /// <param name="taskParameterName">The task parameter name</param>
        /// <param name="itemName">can be null</param>
        /// <param name="propertyName">can be null</param>
        /// <param name="bucket">The bucket for the batch.</param>
        private void InferTaskOutputs(
            Lookup lookup,
            ProjectTaskInstanceChild taskOutputSpecification,
            string taskParameterName,
            string itemName,
            string propertyName,
            ItemBucket bucket)
        {
            string taskParameterAttribute = _taskNode.GetParameter(taskParameterName);
 
            if (taskParameterAttribute != null)
            {
                ProjectTaskOutputItemInstance taskItemInstance = taskOutputSpecification as ProjectTaskOutputItemInstance;
                if (taskItemInstance != null)
                {
                    // This is an output item.
                    // Expand only with properties first, so that expressions like Include="@(foo)" will transfer the metadata of the "foo" items as well, not just their item specs.
                    var outputItemSpecs = bucket.Expander.ExpandIntoStringListLeaveEscaped(taskParameterAttribute, ExpanderOptions.ExpandPropertiesAndMetadata, taskItemInstance.TaskParameterLocation);
                    ProjectItemInstanceFactory itemFactory = new ProjectItemInstanceFactory(_buildRequestEntry.RequestConfiguration.Project, itemName);
 
                    foreach (string outputItemSpec in outputItemSpecs)
                    {
                        ICollection<ProjectItemInstance> items = bucket.Expander.ExpandIntoItemsLeaveEscaped(outputItemSpec, itemFactory, ExpanderOptions.ExpandItems, taskItemInstance.TaskParameterLocation);
 
                        lookup.AddNewItemsOfItemType(itemName, items);
                    }
                }
                else
                {
                    // This is an output property.
                    ProjectTaskOutputPropertyInstance taskPropertyInstance = (ProjectTaskOutputPropertyInstance)taskOutputSpecification;
 
                    string taskParameterValue = bucket.Expander.ExpandIntoStringAndUnescape(taskParameterAttribute, ExpanderOptions.ExpandAll, taskPropertyInstance.TaskParameterLocation);
 
                    if (!String.IsNullOrEmpty(taskParameterValue))
                    {
                        lookup.SetProperty(ProjectPropertyInstance.Create(propertyName, taskParameterValue, taskPropertyInstance.TaskParameterLocation, _buildRequestEntry.RequestConfiguration.Project.IsImmutable));
                    }
                }
            }
        }
 
        #endregion
    }
}