// 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.CodeDom;
using System.CodeDom.Compiler;
using System.Collections;
using System.Collections.Generic;
#if FEATURE_RESXREADER_LIVEDESERIALIZATION
using System.ComponentModel.Design;
#endif
#if FEATURE_SYSTEM_CONFIGURATION
using System.Configuration;
#endif
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Resources;
using System.Resources.Extensions;
using System.Reflection;
using System.Runtime.InteropServices;
#if FEATURE_APPDOMAIN
using System.Runtime.Remoting;
using System.Runtime.Serialization.Formatters.Binary;
#endif
using System.Runtime.Serialization;
#if !FEATURE_ASSEMBLYLOADCONTEXT
using System.Runtime.Versioning;
using System.Security;
#endif
using System.Text;
using System.Xml;
using System.Xml.Linq;

using Microsoft.Build.Eventing;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using Microsoft.Build.Shared.FileSystem;
using Microsoft.Build.Tasks.ResourceHandling;
using Microsoft.Build.Utilities;
#if FEATURE_RESXREADER_LIVEDESERIALIZATION
using Microsoft.Win32;
#endif

#nullable disable

namespace Microsoft.Build.Tasks
{
    /// <summary>
    /// This class defines the "GenerateResource" MSBuild task, which enables using resource APIs
    /// to transform resource files.
    /// </summary>
    [RequiredRuntime("v2.0")]
    public sealed partial class GenerateResource : TaskExtension, IIncrementalTask
    {

        #region Fields

        // This cache helps us track the linked resource files listed inside of a resx resource file
        private ResGenDependencies _cache;

        // This is where we store the list of input files/sources
        private ITaskItem[] _sources = null;

        // Indicates whether the resource reader should use the source file's
        // directory to resolve relative file paths.
        private bool _useSourcePath = false;

        // This is needed for the actual items from the project
        private ITaskItem[] _references = null;

        // Any additional inputs to dependency checking.
        private ITaskItem[] _additionalInputs = null;

        // This is the path/name of the dependency cache file
        private ITaskItem _stateFile = null;

        // This list is all of the resource file(s) generated by the task
        private ITaskItem[] _outputResources = null;

        // List of those output resources that were not actually created, due to an error
        private ArrayList _unsuccessfullyCreatedOutFiles = new ArrayList();

        // Storage for names of *all files* written to disk.
        private ArrayList _filesWritten = new ArrayList();

        // StronglyTypedLanguage
        private string _stronglyTypedLanguage = null;

        // StronglyTypedNamespace
        private string _stronglyTypedNamespace = null;

        // StronglyTypedManifestPrefix
        private string _stronglyTypedManifestPrefix = null;

        // StronglyTypedClassName
        private string _stronglyTypedClassName = null;

        // StronglyTypedFileName
        private string _stronglyTypedFileName = null;

        // Whether the STR class should have public members; default is false
        private bool _publicClass = false;

        // Did the CodeDOM succeed when creating any Strongly Typed Resource class?
        private bool _stronglyTypedResourceSuccessfullyCreated = false;

        // When true, a separate AppDomain is always created.
        private bool _neverLockTypeAssemblies = false;

        // Newest uncorrelated input,
        // or null if not yet determined
        private string _newestUncorrelatedInput;

        // Write time of newest uncorrelated input
        // DateTime.MinValue indicates "missing" iff _newestUncorrelatedInput != null
        private DateTime _newestUncorrelatedInputWriteTime;

        // The targets may pass in the path to the SDKToolsPath. If so this should be used to generate the commandline
        // for logging purposes.  Also, when ExecuteAsTool is true, it determines where the system goes looking for resgen.exe
        private string _sdkToolsPath;

        // True if the resource generation should be sent out-of-proc to resgen.exe; false otherwise.  Defaults to true
        // because we want to execute out-of-proc when ToolsVersion is < 4.0, and the earlier targets files don't know
        // about this property.
        private bool _executeAsTool = true;

        // Path to resgen.exe
        private string _resgenPath;

#if FEATURE_APPDOMAIN
        // table of already seen types by their typename
        // note the use of the ordinal comparer that matches the case sensitive Type.GetType usage
        private Dictionary<string, Type> _typeTable = new Dictionary<string, Type>(StringComparer.Ordinal);

        /// <summary>
        /// Table of aliases for types defined in resx / resw files
        /// Ordinal comparer matches ResXResourceReader's use of a HashTable.
        /// </summary>
        private Dictionary<string, string> _aliases = new Dictionary<string, string>(StringComparer.Ordinal);
#endif // FEATURE_APPDOMAIN

#if FEATURE_RESGEN
        // Our calculation is not quite correct. Using a number substantially less than 32768 in order to
        // be sure we don't exceed it.
        private const int s_maximumCommandLength = 28000;
#endif // FEATURE_RESGEN

        // Contains the list of paths from which inputs will not be taken into account during up-to-date check.
        private ITaskItem[] _excludedInputPaths;

#if FEATURE_APPDOMAIN
        /// <summary>
        /// The task items that we remoted across the appdomain boundary
        /// we use this list to disconnect the task items once we're done.
        /// </summary>
        private List<ITaskItem> _remotedTaskItems;
#endif

        /// <summary>
        /// Satellite input assemblies.
        /// </summary>
        private List<ITaskItem> _satelliteInputs;

        #endregion  // fields

        #region Properties

        /// <summary>
        /// The names of the items to be converted. The extension must be one of the
        /// following: .txt, .resx or .resources.
        /// </summary>
        [Required]
        [Output]
        public ITaskItem[] Sources
        {
            set { _sources = value; }
            get { return _sources; }
        }

        /// <summary>
        /// Indicates whether the resource reader should use the source file's directory to
        /// resolve relative file paths.
        /// </summary>
        public bool UseSourcePath
        {
            set { _useSourcePath = value; }
            get { return _useSourcePath; }
        }

        /// <summary>
        /// Resolves types in ResX files (XML resources) for Strongly Typed Resources
        /// </summary>
        public ITaskItem[] References
        {
            set { _references = value; }
            get { return _references; }
        }

        /// <summary>
        /// Indicates whether resources should be passed through in their current serialization
        /// format. .NET Core-targeted assemblies should use this; it's the only way to support
        /// non-string resources with MSBuild running on .NET Core.
        /// </summary>
        public bool UsePreserializedResources { get; set; } = false;

        /// <summary>
        /// Additional inputs to the dependency checking done by this task. For example,
        /// the project and targets files typically should be inputs, so that if they are updated,
        /// all resources are regenerated.
        /// </summary>
        public ITaskItem[] AdditionalInputs
        {
            set { _additionalInputs = value; }
            get { return _additionalInputs; }
        }

        /// <summary>
        /// This is the path/name of the file containing the dependency cache
        /// </summary>
        public ITaskItem StateFile
        {
            set { _stateFile = value; }
            get { return _stateFile; }
        }

        /// <summary>
        /// The name(s) of the resource file to create. If the user does not specify this
        /// attribute, the task will append a .resources extension to each input filename
        /// argument and write the file to the directory that contains the input file.
        /// Includes any output files that were already up to date, but not any output files
        /// that failed to be written due to an error.
        /// </summary>
        [Output]
        public ITaskItem[] OutputResources
        {
            set { _outputResources = value; }
            get { return _outputResources; }
        }

        /// <summary>
        /// Storage for names of *all files* written to disk.  This is part of the implementation
        /// for Clean, and contains the OutputResources items and the StateFile item.
        /// Includes any output files that were already up to date, but not any output files
        /// that failed to be written due to an error.
        /// </summary>
        [Output]
        public ITaskItem[] FilesWritten
        {
            get
            {
                return (ITaskItem[])_filesWritten.ToArray(typeof(ITaskItem));
            }
        }

        /// <summary>
        /// Gets or sets the language to use when generating the class source for the strongly typed resource.
        /// This parameter must match exactly one of the languages used by the CodeDomProvider.
        /// </summary>
        public string StronglyTypedLanguage
        {
            set
            {
                // Since this string is passed directly into the framework, we don't want to
                // try to validate it -- that might prevent future expansion of supported languages.
                _stronglyTypedLanguage = value;
            }

            get
            {
                return _stronglyTypedLanguage;
            }
        }

        // Indicates whether any BinaryFormatter use should lead to a warning.
        public bool WarnOnBinaryFormatterUse
        {
            get; set;
        }

        /// <summary>
        /// Specifies the namespace to use for the generated class source for the
        /// strongly typed resource. If left blank, no namespace is used.
        /// </summary>
        public string StronglyTypedNamespace
        {
            set { _stronglyTypedNamespace = value; }
            get { return _stronglyTypedNamespace; }
        }

        /// <summary>
        /// Specifies the resource namespace or manifest prefix to use in the generated
        /// class source for the strongly typed resource.
        /// </summary>
        public string StronglyTypedManifestPrefix
        {
            set { _stronglyTypedManifestPrefix = value; }
            get { return _stronglyTypedManifestPrefix; }
        }

        /// <summary>
        /// Specifies the class name for the strongly typed resource class.  If left blank, the base
        /// name of the resource file is used.
        /// </summary>
        [Output]
        public string StronglyTypedClassName
        {
            set { _stronglyTypedClassName = value; }
            get { return _stronglyTypedClassName; }
        }

        /// <summary>
        /// Specifies the filename for the source file.  If left blank, the name of the class is
        /// used as the base filename, with the extension dependent on the language.
        /// </summary>
        [Output]
        public string StronglyTypedFileName
        {
            set { _stronglyTypedFileName = value; }
            get { return _stronglyTypedFileName; }
        }

        /// <summary>
        /// Specifies whether the strongly typed class should be created public (with public methods)
        /// instead of the default internal. Analogous to resgen.exe's /publicClass switch.
        /// </summary>
        public bool PublicClass
        {
            set { _publicClass = value; }
            get { return _publicClass; }
        }

        /// <summary>
        /// Whether this rule is generating .resources files or extracting .ResW files from assemblies.
        /// Requires some additional input filtering.
        /// </summary>
        public bool ExtractResWFiles
        {
            get;
            set;
        }

        /// <summary>
        /// (default = false)
        /// When true, a new AppDomain is always created to evaluate the .resx files.
        /// When false, a new AppDomain is created only when it looks like a user's
        ///  assembly is referenced by the .resx.
        /// </summary>
        public bool NeverLockTypeAssemblies
        {
            set { _neverLockTypeAssemblies = value; }
            get { return _neverLockTypeAssemblies; }
        }

        /// <summary>
        /// Even though the generate resource task will do the processing in process, a logging message is still generated. This logging message
        /// will include the path to the windows SDK. Since the targets now will pass in the Windows SDK path we should use this for logging.
        /// </summary>
        public string SdkToolsPath
        {
            get { return _sdkToolsPath; }
            set { _sdkToolsPath = value; }
        }

        /// <summary>
        /// Property to allow multitargeting of ResolveComReferences:  If true, tlbimp.exe and
        /// aximp.exe from the appropriate target framework will be run out-of-proc to generate
        /// the necessary wrapper assemblies.
        /// </summary>
        public bool ExecuteAsTool
        {
            set { _executeAsTool = value; }
            get { return _executeAsTool; }
        }

        /// <summary>
        /// Array of equals-separated pairs of environment
        /// variables that should be passed to the spawned resgen.exe,
        /// in addition to (or selectively overriding) the regular environment block.
        /// These aren't currently used when resgen is run in-process.
        /// </summary>
        public string[] EnvironmentVariables
        {
            get;
            set;
        }

        /// <summary>
        /// That set of paths from which tracked inputs will be ignored during
        /// Up to date checking
        /// </summary>
        public ITaskItem[] ExcludedInputPaths
        {
            get { return _excludedInputPaths; }
            set { _excludedInputPaths = value; }
        }

        /// <summary>
        /// Property used to set whether tracked incremental build will be used. If true,
        /// incremental build is turned on; otherwise, a rebuild will be forced.
        /// </summary>
        public bool MinimalRebuildFromTracking
        {
            get
            {
                // not using tracking anymore
                return false;
            }

            set
            {
                // do nothing
            }
        }

        /// <summary>
        /// True if we should be tracking file access patterns - necessary for incremental
        /// build support.
        /// </summary>
        public bool TrackFileAccess
        {
            get
            {
                // not using tracking anymore
                return false;
            }
            set
            {
                // do nothing
            }
        }

        /// <summary>
        /// Names of the read tracking logs.
        /// </summary>
        [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "TLog", Justification = "Has now shipped as public API; plus it's unclear whether 'Tlog' or 'TLog' is the preferred casing")]
        public ITaskItem[] TLogReadFiles
        {
            get
            {
                return Array.Empty<ITaskItem>();
            }
        }

        /// <summary>
        /// Names of the write tracking logs.
        /// </summary>
        [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "TLog", Justification = "Has now shipped as public API; plus it's unclear whether 'Tlog' or 'TLog' is the preferred casing")]
        public ITaskItem[] TLogWriteFiles
        {
            get
            {
                return Array.Empty<ITaskItem>();
            }
        }

        /// <summary>
        /// Intermediate directory into which the tracking logs from running this task will be placed.
        /// </summary>
        public string TrackerLogDirectory
        {
            get
            {
                return String.Empty;
            }
            set
            {
                // do nothing
            }
        }

        /// <summary>
        /// Microsoft.Build.Utilities.ExecutableType of ResGen.exe.  Used to determine whether or not
        /// Tracker.exe needs to be used to spawn ResGen.exe.  If empty, uses a heuristic to determine
        /// a default architecture.
        /// </summary>
        public string ToolArchitecture
        {
            get
            {
                return String.Empty;
            }

            set
            {
                // do nothing
            }
        }

        /// <summary>
        /// Path to the appropriate .NET Framework location that contains FileTracker.dll.  If set, the user
        /// takes responsibility for making sure that the bitness of the FileTracker.dll that they pass matches
        /// the bitness of the ResGen.exe that they intend to use. If not set, the task decides the appropriate
        /// location based on the current .NET Framework version.
        /// </summary>
        /// <comments>
        /// Should only need to be used in partial or full checked in toolset scenarios.
        /// </comments>
        public string TrackerFrameworkPath
        {
            get
            {
                return String.Empty;
            }

            set
            {
                // do nothing
            }
        }

        /// <summary>
        /// Path to the appropriate Windows SDK location that contains Tracker.exe.  If set, the user takes
        /// responsibility for making sure that the bitness of the Tracker.exe that they pass matches the
        /// bitness of the ResGen.exe that they intend to use. If not set, the task decides the appropriate
        /// location based on the current Windows SDK.
        /// </summary>
        /// <comments>
        /// Should only need to be used in partial or full checked in toolset scenarios.
        /// </comments>
        public string TrackerSdkPath
        {
            get
            {
                return String.Empty;
            }

            set
            {
                // do nothing
            }
        }

        /// <summary>
        /// Where to extract ResW files.  (Could be the intermediate directory.)
        /// </summary>
        public string OutputDirectory
        {
            get;
            set;
        }

        #endregion // properties

        /// <summary>
        /// Simple public constructor.
        /// </summary>
        public GenerateResource()
        {
            // do nothing
        }

        public bool FailIfNotIncremental { get; set; }

        /// <summary>
        /// Logs a Resgen.exe command line that indicates what parameters were
        /// passed to this task. Since this task is replacing Resgen, and we used
        /// to log the Resgen.exe command line, we need to continue logging an
        /// equivalent command line.
        /// </summary>
        /// <param name="inputFiles"></param>
        /// <param name="outputFiles"></param>
        private void LogResgenCommandLine(List<ITaskItem> inputFiles, List<ITaskItem> outputFiles)
        {
            CommandLineBuilderExtension commandLineBuilder = new CommandLineBuilderExtension();

            // start the command line with the path to Resgen.exe
            commandLineBuilder.AppendFileNameIfNotNull(Path.Combine(_resgenPath, "resgen.exe"));

            GenerateResGenCommandLineWithoutResources(commandLineBuilder);

            if (StronglyTypedLanguage == null)
            {
                // append the resources to compile
                for (int i = 0; i < inputFiles.Count; ++i)
                {
                    if (!ExtractResWFiles)
                    {
                        commandLineBuilder.AppendFileNamesIfNotNull(
                            [inputFiles[i].ItemSpec, outputFiles[i].ItemSpec],
                            ",");
                    }
                    else
                    {
                        commandLineBuilder.AppendFileNameIfNotNull(inputFiles[i].ItemSpec);
                    }
                }
            }
            else
            {
                // append the resource to compile
                commandLineBuilder.AppendFileNamesIfNotNull(inputFiles.ToArray(), " ");
                commandLineBuilder.AppendFileNamesIfNotNull(outputFiles.ToArray(), " ");

                // append the strongly-typed resource details
                commandLineBuilder.AppendSwitchIfNotNull(
                    "/str:",
                    [StronglyTypedLanguage, StronglyTypedNamespace, StronglyTypedClassName, StronglyTypedFileName],
                    ",");
            }

            Log.LogCommandLine(MessageImportance.Low, commandLineBuilder.ToString());
        }

        /// <summary>
        /// Generate the parts of the resgen command line that are don't involve resgen.exe itself or the
        /// resources to be generated.
        /// </summary>
        /// <comments>
        /// Expects resGenCommand to be non-null -- otherwise, it doesn't get passed back to the caller, so it's
        /// useless anyway.
        /// </comments>
        /// <param name="resGenCommand"></param>
        private void GenerateResGenCommandLineWithoutResources(CommandLineBuilderExtension resGenCommand)
        {
            // Throw an internal error, since this method should only ever get called by other aspects of this task, not
            // anything that the user touches.
            ErrorUtilities.VerifyThrowInternalNull(resGenCommand);

            // append the /useSourcePath flag if requested.
            if (UseSourcePath)
            {
                resGenCommand.AppendSwitch("/useSourcePath");
            }

            // append the /publicClass flag if requested
            if (PublicClass)
            {
                resGenCommand.AppendSwitch("/publicClass");
            }

            // append the references, if any
            if (References != null)
            {
                foreach (ITaskItem reference in References)
                {
                    resGenCommand.AppendSwitchIfNotNull("/r:", reference);
                }
            }

            // append /compile switch if not creating strongly typed class
            if (String.IsNullOrEmpty(StronglyTypedLanguage))
            {
                resGenCommand.AppendSwitch("/compile");
            }
        }

        /// <summary>
        /// This is the main entry point for the GenerateResource task.
        /// </summary>
        /// <returns>true, if task executes successfully</returns>
        public override bool Execute()
        {
            bool outOfProcExecutionSucceeded = true;
            MSBuildEventSource.Log.GenerateResourceOverallStart();
            {
                // If we're extracting ResW files from assemblies (instead of building resources),
                // our Sources can contain PDB's, pictures, and other non-DLL's.  Prune that list.
                // .NET Framework assemblies are not included.  However, other Microsoft ones
                // such as MSTestFramework may be included (resolved from GetSDKReferenceFiles).
                if (ExtractResWFiles && Sources != null)
                {
                    _satelliteInputs = new List<ITaskItem>();

                    List<ITaskItem> newSources = new List<ITaskItem>();
                    foreach (ITaskItem item in Sources)
                    {
                        if (item.ItemSpec.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
                        {
                            if (item.ItemSpec.EndsWith(".resources.dll", StringComparison.OrdinalIgnoreCase))
                            {
                                _satelliteInputs.Add(item);
                            }
                            else
                            {
                                newSources.Add(item);
                            }
                        }
                    }
                    Sources = newSources.ToArray();
                }

                // If there are no sources to process, just return (with success) and report the condition.
                if ((Sources == null) || (Sources.Length == 0))
                {
                    Log.LogMessageFromResources(MessageImportance.Low, "GenerateResource.NoSources");
                    // Indicate we generated nothing
                    OutputResources = null;
                    return true;
                }

                if (!ValidateParameters())
                {
                    // Indicate we generated nothing
                    OutputResources = null;
                    return false;
                }

                // In the case that OutputResources wasn't set, build up the outputs by transforming the Sources
                // However if we are extracting ResW files, we cannot easily tell which files we'll produce up front,
                // without loading each assembly.
                if (!ExtractResWFiles && !CreateOutputResourcesNames())
                {
                    // Indicate we generated nothing
                    OutputResources = null;
                    return false;
                }

                List<ITaskItem> inputsToProcess;
                List<ITaskItem> outputsToProcess;
                List<ITaskItem> cachedOutputFiles;  // For incremental builds, this is the set of already-existing, up to date files.

                GetResourcesToProcess(out inputsToProcess, out outputsToProcess, out cachedOutputFiles);

                if (inputsToProcess.Count == 0)
                {
                    if (!Log.HasLoggedErrors)
                    {
                        if (cachedOutputFiles.Count > 0)
                        {
                            OutputResources = cachedOutputFiles.ToArray();
                        }

                        Log.LogMessageFromResources("GenerateResource.NothingOutOfDate");
                    }
                    else
                    {
                        // No valid sources found--failures should have been logged in GetResourcesToProcess
                        return false;
                    }
                }
                else if (FailIfNotIncremental)
                {
                    int maxCount = Math.Min(inputsToProcess.Count, outputsToProcess.Count);
                    maxCount = Math.Min(maxCount, 5);  // Limit to just 5

                    for (int index = 0; index < maxCount; index++)
                    {
                        Log.LogErrorFromResources("GenerateResource.ProcessingFile", inputsToProcess[index], outputsToProcess[index]);
                    }

                    return false;
                }
                else
                {
                    if (!ComputePathToResGen())
                    {
                        // unable to compute the path to resgen.exe and that is necessary to
                        // continue forward, so return now.
                        return false;
                    }

                    // Check for the mark of the web on all possibly-exploitable files
                    // to be processed.
                    bool dangerousResourceFound = false;

                    foreach (ITaskItem source in _sources)
                    {
                        if (IsDangerous(source.ItemSpec))
                        {
                            Log.LogErrorWithCodeFromResources("GenerateResource.MOTW", source.ItemSpec);
                            dangerousResourceFound = true;
                        }
                    }

                    if (dangerousResourceFound)
                    {
                        // Do no further processing
                        return false;
                    }

                    if (ExecuteAsTool)
                    {
                        outOfProcExecutionSucceeded = GenerateResourcesUsingResGen(inputsToProcess, outputsToProcess);
                    }
                    else // Execute in-proc (or in a separate appdomain if necessary)
                    {
                        // Log equivalent command line as this is a convenient way to log all the references, etc
                        // even though we're not actually running resgen.exe
                        LogResgenCommandLine(inputsToProcess, outputsToProcess);

#if FEATURE_APPDOMAIN
                        // Figure out whether a separate AppDomain is required because an assembly would be locked.
                        bool needSeparateAppDomain = NeedSeparateAppDomain();

                        AppDomain appDomain = null;
#endif
                        ProcessResourceFiles process = null;

                        try
                        {
#if FEATURE_APPDOMAIN
                            if (needSeparateAppDomain)
                            {
                                // we're going to be remoting across the appdomain boundary, so
                                // create the list that we'll use to disconnect the taskitems once we're done
                                _remotedTaskItems = new List<ITaskItem>();

                                appDomain = AppDomain.CreateDomain(
                                    "generateResourceAppDomain",
                                    null,
                                    AppDomain.CurrentDomain.SetupInformation);

                                object obj = appDomain.CreateInstanceFromAndUnwrap(
                                       typeof(ProcessResourceFiles).Module.FullyQualifiedName,
                                       typeof(ProcessResourceFiles).FullName);

                                Type processType = obj.GetType();
                                ErrorUtilities.VerifyThrow(processType == typeof(ProcessResourceFiles), "Somehow got a wrong and possibly incompatible type for ProcessResourceFiles.");

                                process = (ProcessResourceFiles)obj;

                                RecordItemsForDisconnectIfNecessary(_references);
                                RecordItemsForDisconnectIfNecessary(inputsToProcess);
                                RecordItemsForDisconnectIfNecessary(outputsToProcess);
                            }
                            else
#endif
                            {
                                process = new ProcessResourceFiles();
                            }

                            process.Run(Log,
                                        _references,
                                        inputsToProcess,
                                        _satelliteInputs,
                                        outputsToProcess,
                                        UseSourcePath,
                                        UsePreserializedResources,
                                        StronglyTypedLanguage,
                                        _stronglyTypedNamespace,
                                        _stronglyTypedManifestPrefix,
                                        StronglyTypedFileName,
                                        StronglyTypedClassName,
                                        PublicClass,
                                        ExtractResWFiles,
                                        OutputDirectory,
                                        WarnOnBinaryFormatterUse);

                            this.StronglyTypedClassName = process.StronglyTypedClassName; // in case a default was chosen
                            this.StronglyTypedFileName = process.StronglyTypedFilename;   // in case a default was chosen
                            _stronglyTypedResourceSuccessfullyCreated = process.StronglyTypedResourceSuccessfullyCreated;
                            if (process.UnsuccessfullyCreatedOutFiles != null)
                            {
                                foreach (string item in process.UnsuccessfullyCreatedOutFiles)
                                {
                                    _unsuccessfullyCreatedOutFiles.Add(item);
                                }
                            }

                            if (ExtractResWFiles)
                            {
                                ITaskItem[] outputResources = process.ExtractedResWFiles.ToArray();
#if FEATURE_APPDOMAIN
                                if (needSeparateAppDomain)
                                {
                                    // Ensure we can unload the other AppDomain, yet still use the
                                    // ITaskItems we got back.  Clone them.
                                    outputResources = CloneValuesInThisAppDomain(outputResources);
                                }
#endif

                                if (cachedOutputFiles.Count > 0)
                                {
                                    OutputResources = new ITaskItem[outputResources.Length + cachedOutputFiles.Count];
                                    outputResources.CopyTo(OutputResources, 0);
                                    cachedOutputFiles.CopyTo(OutputResources, outputResources.Length);
                                }
                                else
                                {
                                    OutputResources = outputResources;
                                }

                                // Get portable library cache info (and if needed, marshal it to this AD).
                                List<ResGenDependencies.PortableLibraryFile> portableLibraryCacheInfo = process.PortableLibraryCacheInfo;
                                for (int i = 0; i < portableLibraryCacheInfo.Count; i++)
                                {
                                    _cache.UpdatePortableLibrary(portableLibraryCacheInfo[i]);
                                }
                            }

                            process = null;
                        }
                        finally
                        {
#if FEATURE_APPDOMAIN
                            if (needSeparateAppDomain && appDomain != null)
                            {
                                Log.MarkAsInactive();

                                AppDomain.Unload(appDomain);
                                process = null;
                                appDomain = null;

                                // if we've been asked to remote these items then
                                // we need to disconnect them from .NET Remoting now we're all done with them
                                if (_remotedTaskItems != null)
                                {
                                    foreach (ITaskItem item in _remotedTaskItems)
                                    {
                                        if (item is MarshalByRefObject marshalByRefObject)
                                        {
                                            // Tell remoting to forget connections to the taskitem
                                            RemotingServices.Disconnect(marshalByRefObject);
                                        }
                                    }
                                }

                                _remotedTaskItems = null;
                            }
#endif
                        }
                    }
                }

                // And now we serialize the cache to save our resgen linked file resolution for later use.
                WriteStateFile();

                RemoveUnsuccessfullyCreatedResourcesFromOutputResources();

                RecordFilesWritten();
            }

            MSBuildEventSource.Log.GenerateResourceOverallStop();

            return !Log.HasLoggedErrors && outOfProcExecutionSucceeded;
        }

#if FEATURE_RESXREADER_LIVEDESERIALIZATION
        private static readonly bool AllowMOTW = !NativeMethodsShared.IsWindows || (Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\SDK", "AllowProcessOfUntrustedResourceFiles", null) is string allowUntrustedFiles && allowUntrustedFiles.Equals("true", StringComparison.OrdinalIgnoreCase));

        private const string CLSID_InternetSecurityManager = "7b8a2d94-0ac9-11d1-896c-00c04fb6bfc4";
        private const uint ZoneInternet = 3;
        private static IInternetSecurityManager internetSecurityManager = null;
#endif

        // Resources can have arbitrarily serialized objects in them which can execute arbitrary code
        // so check to see if we should trust them before analyzing them
        private bool IsDangerous(String filename)
        {
#if !FEATURE_RESXREADER_LIVEDESERIALIZATION
            return false;
        }
#else
            // If they are opted out, there's no work to do
            if (AllowMOTW)
            {
                return false;
            }

            // First check the zone, if they are not an untrusted zone, they aren't dangerous
            if (internetSecurityManager == null)
            {
                Type iismType = Type.GetTypeFromCLSID(new Guid(CLSID_InternetSecurityManager));
                internetSecurityManager = (IInternetSecurityManager)Activator.CreateInstance(iismType);
            }

            int zone;
            internetSecurityManager.MapUrlToZone(Path.GetFullPath(filename), out zone, 0);
            if (zone < ZoneInternet)
            {
                return false;
            }

            // By default all file types that get here are considered dangerous
            bool dangerous = true;

            string extension = Path.GetExtension(filename);
            if (String.Equals(extension, ".resx", StringComparison.OrdinalIgnoreCase) ||
                String.Equals(extension, ".resw", StringComparison.OrdinalIgnoreCase))
            {
                // XML files are only dangerous if there are unrecognized objects in them
                dangerous = false;

                using FileStream stream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
                using XmlTextReader reader = new XmlTextReader(stream);
                reader.DtdProcessing = DtdProcessing.Ignore;
                reader.XmlResolver = null;
                try
                {
                    while (reader.Read())
                    {
                        if (reader.NodeType == XmlNodeType.Element)
                        {
                            string s = reader.LocalName;

                            // We only want to parse data nodes,
                            // the mimetype attribute gives the serializer
                            // that's requested.
                            if (reader.LocalName.Equals("data"))
                            {
                                if (reader["mimetype"] != null)
                                {
                                    dangerous = true;
                                }
                            }
                            else if (reader.LocalName.Equals("metadata"))
                            {
                                if (reader["mimetype"] != null)
                                {
                                    dangerous = true;
                                }
                            }
                        }
                    }
                }
                catch
                {
                    // If we hit an error while parsing assume there's a dangerous type in this file.
                    dangerous = true;
                }
                stream.Close();
            }

            return dangerous;
        }

        /// <summary>
        /// For setting OutputResources and ensuring it can be read after the second AppDomain has been unloaded.
        /// </summary>
        /// <param name="remoteValues">ITaskItems in another AppDomain</param>
        /// <returns></returns>
        private static ITaskItem[] CloneValuesInThisAppDomain(IList<ITaskItem> remoteValues)
        {
            ITaskItem[] clonedOutput = new ITaskItem[remoteValues.Count];
            for (int i = 0; i < remoteValues.Count; i++)
            {
                clonedOutput[i] = new TaskItem(remoteValues[i]);
            }

            return clonedOutput;
        }

        /// <summary>
        /// Remember this TaskItem so that we can disconnect it when this Task has finished executing
        /// Only if we're passing TaskItems to another AppDomain is this necessary. This call
        /// Will make that determination for you.
        /// </summary>
        private void RecordItemsForDisconnectIfNecessary(IEnumerable<ITaskItem> items)
        {
            if (_remotedTaskItems != null && items != null)
            {
                // remember that we need to disconnect these items
                _remotedTaskItems.AddRange(items);
            }
        }
#endif // FEATURE_APPDOMAIN

        /// <summary>
        /// Computes the path to ResGen.exe for use in logging and for passing to the
        /// nested ResGen task.
        /// </summary>
        /// <returns>True if the path is found (or it doesn't matter because we're executing in memory), false otherwise</returns>
        private bool ComputePathToResGen()
        {
#if FEATURE_RESGEN
            _resgenPath = null;

            if (String.IsNullOrEmpty(_sdkToolsPath))
            {
                // Important: the GenerateResource task is declared twice in Microsoft.Common.CurrentVersion.targets:
                // https://github.com/dotnet/msbuild/blob/369631b4b21ef485f4d6f35e16b0c839a971b0e9/src/Tasks/Microsoft.Common.CurrentVersion.targets#L3177-L3178
                // First for CLR >= 4.0, where SdkToolsPath is passed $(ResgenToolPath) which in turn is set to
                // $(TargetFrameworkSDKToolsDirectory).
                // But for CLR < 4.0 the SdkToolsPath is not passed, so we need to explicitly assume 3.5:
                var version = TargetDotNetFrameworkVersion.Version35;

                _resgenPath = ToolLocationHelper.GetPathToDotNetFrameworkSdkFile("resgen.exe", version);

                if (_resgenPath == null && ExecuteAsTool)
                {
                    Log.LogErrorWithCodeFromResources("General.PlatformSDKFileNotFound", "resgen.exe",
                        ToolLocationHelper.GetDotNetFrameworkSdkInstallKeyValue(version),
                        ToolLocationHelper.GetDotNetFrameworkSdkRootRegistryKey(version));
                }
            }
            else
            {
                _resgenPath = SdkToolsPathUtility.GeneratePathToTool(
                    SdkToolsPathUtility.FileInfoExists,
                    Microsoft.Build.Utilities.ProcessorArchitecture.CurrentProcessArchitecture,
                    SdkToolsPath,
                    "resgen.exe",
                    Log,
                    ExecuteAsTool);
            }

            if (_resgenPath == null && !ExecuteAsTool)
            {
                // if Resgen.exe is not installed, just use the filename
                _resgenPath = String.Empty;
                return true;
            }

            // We may be passing this to the ResGen task (wrapper around resgen.exe), in which case
            // we want to pass just the path -- ResGen will attach the "resgen.exe" onto the end
            // itself.
            if (_resgenPath != null)
            {
                _resgenPath = Path.GetDirectoryName(_resgenPath);
            }

            return _resgenPath != null;
#else
            _resgenPath = string.Empty;
            return true;
#endif
        }

        /// <summary>
        /// Wrapper around the call to the ResGen task that handles setting up the
        /// task to run properly.
        /// </summary>
        /// <param name="inputsToProcess">Array of names of inputs to be processed</param>
        /// <param name="outputsToProcess">Array of output names corresponding to the inputs</param>
        private bool GenerateResourcesUsingResGen(List<ITaskItem> inputsToProcess, List<ITaskItem> outputsToProcess)
        {
#if FEATURE_RESGEN
            bool resGenSucceeded = false;

            if (StronglyTypedLanguage != null)
            {
                resGenSucceeded = GenerateStronglyTypedResourceUsingResGen(inputsToProcess, outputsToProcess);
            }
            else
            {
                resGenSucceeded = TransformResourceFilesUsingResGen(inputsToProcess, outputsToProcess);
            }

            return resGenSucceeded;
#else
            Log.LogError("ResGen.exe not supported on .NET Core MSBuild");
            return false;
#endif
        }

#if FEATURE_RESGEN
        /// <summary>
        /// Given an instance of the ResGen task with everything but the strongly typed
        /// resource-related parameters filled out, execute the task and return the result
        /// </summary>
        /// <param name="inputsToProcess">Input files to give to resgen.exe.</param>
        /// <param name="outputsToProcess">Output files to give to resgen.exe.</param>
        private bool TransformResourceFilesUsingResGen(List<ITaskItem> inputsToProcess, List<ITaskItem> outputsToProcess)
        {
            ErrorUtilities.VerifyThrow(inputsToProcess.Count != 0, "There should be resource files to process");
            ErrorUtilities.VerifyThrow(inputsToProcess.Count == outputsToProcess.Count, "The number of inputs and outputs should be equal");

            bool succeeded = true;

            // We need to do a whole lot of work to make sure that we're not overrunning the command line ... UNLESS
            // we're running ResGen 4.0 or later, which supports response files.
            if (!_resgenPath.Equals(Path.GetDirectoryName(NativeMethodsShared.GetLongFilePath(ToolLocationHelper.GetPathToDotNetFrameworkSdkFile("resgen.exe", TargetDotNetFrameworkVersion.Version35))), StringComparison.OrdinalIgnoreCase))
            {
                ResGen resGen = CreateResGenTaskWithDefaultParameters();

                resGen.InputFiles = inputsToProcess.ToArray();
                resGen.OutputFiles = outputsToProcess.ToArray();

                ITaskItem[] outputFiles = resGen.OutputFiles;

                succeeded = resGen.Execute();

                if (!succeeded)
                {
                    foreach (ITaskItem outputFile in outputFiles)
                    {
                        if (!FileSystems.Default.FileExists(outputFile.ItemSpec))
                        {
                            _unsuccessfullyCreatedOutFiles.Add(outputFile.ItemSpec);
                        }
                    }
                }
            }
            else
            {
                int initialResourceIndex = 0;
                bool doneProcessingResources = false;
                CommandLineBuilderExtension resourcelessCommandBuilder = new CommandLineBuilderExtension();
                string resourcelessCommand = null;

                GenerateResGenCommandLineWithoutResources(resourcelessCommandBuilder);

                if (resourcelessCommandBuilder.Length > 0)
                {
                    resourcelessCommand = resourcelessCommandBuilder.ToString();
                }

                while (!doneProcessingResources)
                {
                    int numberOfResourcesToAdd = CalculateResourceBatchSize(inputsToProcess, outputsToProcess, resourcelessCommand, initialResourceIndex);
                    ResGen resGen = CreateResGenTaskWithDefaultParameters();

                    resGen.InputFiles = inputsToProcess.GetRange(initialResourceIndex, numberOfResourcesToAdd).ToArray();
                    resGen.OutputFiles = outputsToProcess.GetRange(initialResourceIndex, numberOfResourcesToAdd).ToArray();

                    ITaskItem[] outputFiles = resGen.OutputFiles;

                    bool thisBatchSucceeded = resGen.Execute();

                    // This batch failed, so add the failing resources from this batch to the list of failures
                    if (!thisBatchSucceeded)
                    {
                        foreach (ITaskItem outputFile in outputFiles)
                        {
                            if (!FileSystems.Default.FileExists(outputFile.ItemSpec))
                            {
                                _unsuccessfullyCreatedOutFiles.Add(outputFile.ItemSpec);
                            }
                        }
                    }

                    initialResourceIndex += numberOfResourcesToAdd;
                    doneProcessingResources = initialResourceIndex == inputsToProcess.Count;
                    succeeded = succeeded && thisBatchSucceeded;
                }
            }

            return succeeded;
        }

        /// <summary>
        /// Given the list of inputs and outputs, returns the number of resources (starting at the provided initial index)
        /// that can fit onto the commandline without exceeding MaximumCommandLength.
        /// </summary>
        private int CalculateResourceBatchSize(List<ITaskItem> inputsToProcess, List<ITaskItem> outputsToProcess, string resourcelessCommand, int initialResourceIndex)
        {
            CommandLineBuilderExtension currentCommand = new CommandLineBuilderExtension();

            if (!String.IsNullOrEmpty(resourcelessCommand))
            {
                currentCommand.AppendTextUnquoted(resourcelessCommand);
            }

            int i = initialResourceIndex;
            while (currentCommand.Length < s_maximumCommandLength && i < inputsToProcess.Count)
            {
                currentCommand.AppendFileNamesIfNotNull(
                        [inputsToProcess[i], outputsToProcess[i]],
                        ",");
                i++;
            }

            int numberOfResourcesToAdd;
            if (currentCommand.Length <= s_maximumCommandLength)
            {
                // We've successfully added all the rest.
                numberOfResourcesToAdd = i - initialResourceIndex;
            }
            else
            {
                // The last one added tossed us over the edge.
                numberOfResourcesToAdd = i - initialResourceIndex - 1;
            }

            return numberOfResourcesToAdd;
        }

        /// <summary>
        /// Given an instance of the ResGen task with everything but the strongly typed
        /// resource-related parameters filled out, execute the task and return the result
        /// </summary>
        /// <param name="inputsToProcess">Input files to give to resgen.exe.</param>
        /// <param name="outputsToProcess">Output files to give to resgen.exe.</param>
        private bool GenerateStronglyTypedResourceUsingResGen(List<ITaskItem> inputsToProcess, List<ITaskItem> outputsToProcess)
        {
            ErrorUtilities.VerifyThrow(inputsToProcess.Count == 1 && outputsToProcess.Count == 1, "For STR, there should only be one input and one output.");

            ResGen resGen = CreateResGenTaskWithDefaultParameters();

            resGen.InputFiles = inputsToProcess.ToArray();
            resGen.OutputFiles = outputsToProcess.ToArray();

            resGen.StronglyTypedLanguage = StronglyTypedLanguage;
            resGen.StronglyTypedNamespace = StronglyTypedNamespace;
            resGen.StronglyTypedClassName = StronglyTypedClassName;
            resGen.StronglyTypedFileName = StronglyTypedFileName;

            // Save the output file name -- ResGen will delete failing files
            ITaskItem outputFile = resGen.OutputFiles[0];

            _stronglyTypedResourceSuccessfullyCreated = resGen.Execute();

            if (!_stronglyTypedResourceSuccessfullyCreated && (resGen.OutputFiles == null || resGen.OutputFiles.Length == 0))
            {
                _unsuccessfullyCreatedOutFiles.Add(outputFile.ItemSpec);
            }

            // now need to set the defaults (if defaults were chosen) so that they can be
            // consumed by outside users
            StronglyTypedClassName = resGen.StronglyTypedClassName;
            StronglyTypedFileName = resGen.StronglyTypedFileName;

            return _stronglyTypedResourceSuccessfullyCreated;
        }

        /// <summary>
        /// Factoring out the setting of the default parameters to the
        /// ResGen task.
        /// </summary>
        private ResGen CreateResGenTaskWithDefaultParameters()
        {
            ResGen resGen = new ResGen();

            resGen.BuildEngine = BuildEngine;
            resGen.SdkToolsPath = _resgenPath;
            resGen.PublicClass = PublicClass;
            resGen.References = References;
            resGen.UseSourcePath = UseSourcePath;
            resGen.EnvironmentVariables = EnvironmentVariables;

            return resGen;
        }
#endif

        /// <summary>
        /// Check for parameter errors.
        /// </summary>
        /// <returns>true if parameters are valid</returns>
        private bool ValidateParameters()
        {
            // make sure that if the output resources were set, they exactly match the number of input sources
            if ((OutputResources != null) && (OutputResources.Length != Sources.Length))
            {
                Log.LogErrorWithCodeFromResources("General.TwoVectorsMustHaveSameLength", Sources.Length, OutputResources.Length, "Sources", "OutputResources");
                return false;
            }

            // Creating an STR is triggered merely by setting the language
            if (_stronglyTypedLanguage != null)
            {
                // Like Resgen.exe, only a single Sources is allowed if you are generating STR.
                // Otherwise, each STR class overwrites the previous one. In theory we could generate separate
                // STR classes for each input, but then the class name and file name parameters would have to be vectors.
                if (Sources.Length != 1)
                {
                    Log.LogErrorWithCodeFromResources("GenerateResource.STRLanguageButNotExactlyOneSourceFile");
                    return false;
                }
            }
            else
            {
                if (StronglyTypedClassName != null || StronglyTypedNamespace != null || StronglyTypedFileName != null || StronglyTypedManifestPrefix != null)
                {
                    // We have no language to generate a STR, but nevertheless the user passed us a class,
                    // namespace, and/or filename. Let them know that they probably wanted to pass a language too.
                    Log.LogErrorWithCodeFromResources("GenerateResource.STRClassNamespaceOrFilenameWithoutLanguage");
                    return false;
                }
            }

            if (ExtractResWFiles && ExecuteAsTool)
            {
                // This combination isn't currently supported, because ResGen may produce some not-easily-predictable
                // set of ResW files and we don't have any logic to get that set of files back into GenerateResource
                // at the moment.  This could be solved fixed with some engineering effort.
                Log.LogErrorWithCodeFromResources("GenerateResource.ExecuteAsToolAndExtractResWNotSupported");
                return false;
            }

            return true;
        }

        /// <summary>
        /// Returns true if everything is up to date and we don't need to do any work.
        /// </summary>
        /// <returns></returns>
        private void GetResourcesToProcess(out List<ITaskItem> inputsToProcess, out List<ITaskItem> outputsToProcess, out List<ITaskItem> cachedOutputFiles)
        {
            // First we look to see if we have a resgen linked files cache.  If so, then we can use that
            // cache to speed up processing.
            ReadStateFile();

            bool nothingOutOfDate = true;
            inputsToProcess = new List<ITaskItem>();
            outputsToProcess = new List<ITaskItem>();
            cachedOutputFiles = new List<ITaskItem>();

            // decide what sources we need to build
            for (int i = 0; i < Sources.Length; ++i)
            {
                if (ExtractResWFiles)
                {
                    // We can't cheaply predict the output files, since that would require
                    // loading each assembly.  So don't even try guessing what they will be.
                    // However, our cache will sometimes record all the info we need (for incremental builds).
                    string sourceFileName = Sources[i].ItemSpec;
                    ResGenDependencies.PortableLibraryFile library = _cache.TryGetPortableLibraryInfo(sourceFileName);
                    if (library?.AllOutputFilesAreUpToDate() == true)
                    {
                        AppendCachedOutputTaskItems(library, cachedOutputFiles);
                    }
                    else
                    {
                        inputsToProcess.Add(Sources[i]);
                    }

                    continue;
                }

                // Attributes from input items are forwarded to output items.
                Sources[i].CopyMetadataTo(OutputResources[i]);
                Sources[i].SetMetadata("OutputResource", OutputResources[i].ItemSpec);

                if (!FileSystems.Default.FileExists(Sources[i].ItemSpec))
                {
                    // Error but continue with the files that do exist
                    Log.LogErrorWithCodeFromResources("GenerateResource.ResourceNotFound", Sources[i].ItemSpec);
                    _unsuccessfullyCreatedOutFiles.Add(OutputResources[i].ItemSpec);
                }
                else
                {
                    // check to see if the output resources file (and, if it is a .resx, any linked files)
                    // is up to date compared to the input file
                    if (ShouldRebuildResgenOutputFile(Sources[i].ItemSpec, OutputResources[i].ItemSpec))
                    {
                        nothingOutOfDate = false;
                        inputsToProcess.Add(Sources[i]);
                        outputsToProcess.Add(OutputResources[i]);
                    }
                }
            }

            // If the STR class file is out of date (for example, it's missing) we don't want to skip
            // resource generation.
            if (_stronglyTypedLanguage != null)
            {
                // We're generating a STR class file, so there must be exactly one input resource file.
                // If that resource file itself is out of date, the STR class file is going to get generated anyway.
                if (nothingOutOfDate && FileSystems.Default.FileExists(Sources[0].ItemSpec))
                {
                    GetStronglyTypedResourceToProcess(ref inputsToProcess, ref outputsToProcess);
                }
            }
        }

        /// <summary>
        /// Given a cached portable library that is up to date, create ITaskItems to represent the output of the task, as if we did real work.
        /// </summary>
        /// <param name="library">The portable library cache entry to extract output files and metadata from.</param>
        /// <param name="cachedOutputFiles">List of output files produced from the cache.</param>
        private void AppendCachedOutputTaskItems(ResGenDependencies.PortableLibraryFile library, List<ITaskItem> cachedOutputFiles)
        {
            foreach (string outputFileName in library.OutputFiles)
            {
                ITaskItem item = new TaskItem(outputFileName);
                item.SetMetadata("ResourceIndexName", library.AssemblySimpleName);
                if (library.NeutralResourceLanguage != null)
                {
                    item.SetMetadata("NeutralResourceLanguage", library.NeutralResourceLanguage);
                }

                cachedOutputFiles.Add(item);
            }
        }

        /// <summary>
        /// Checks if this list contain any duplicates.  Do this so we don't have any races where we have two
        /// threads trying to write to the same file simultaneously.
        /// </summary>
        /// <param name="originalList">A list that may have duplicates</param>
        /// <returns>Were there duplicates?</returns>
        private bool ContainsDuplicates(IList<ITaskItem> originalList)
        {
            HashSet<string> set = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
            foreach (ITaskItem item in originalList)
            {
                try
                {
                    // Get the fully qualified path, to ensure two file names don't end up pointing to the same directory.
                    if (!set.Add(item.GetMetadata("FullPath")))
                    {
                        Log.LogErrorWithCodeFromResources("GenerateResource.DuplicateOutputFilenames", item.ItemSpec);
                        return true;
                    }
                }
                catch (InvalidOperationException e)
                {
                    Log.LogErrorWithCodeFromResources("GenerateResource.InvalidFilename", item.ItemSpec, e.Message);
                    // Returning true causes us to not continue executing.
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Determines if the given output file is up to date with respect to the
        /// the given input file by comparing timestamps of the two files as well as
        /// (if the source is a .resx) the linked files inside the .resx file itself
        /// </summary>
        /// <param name="sourceFilePath"></param>
        /// <param name="outputFilePath"></param>
        /// <returns></returns>
        private bool ShouldRebuildResgenOutputFile(string sourceFilePath, string outputFilePath)
        {
            // See if any uncorrelated inputs are missing before checking source and output file timestamps.
            // Only do this if we already checked the uncorrelated inputs, since
            // typically, it's the .resx's that are out of date so we want to check those first.
            if (_newestUncorrelatedInput != null && _newestUncorrelatedInputWriteTime == DateTime.MinValue)
            {
                // An uncorrelated input is missing; need to build
                // We logged this once already, when we found it
                return true;
            }

            DateTime outputTime = NativeMethodsShared.GetLastWriteFileUtcTime(outputFilePath);

            // Quick check to see if any uncorrelated input is newer in which case we can avoid checking source file timestamp
            if (_newestUncorrelatedInput != null && _newestUncorrelatedInputWriteTime > outputTime)
            {
                // An uncorrelated input is newer, need to build
                Log.LogMessageFromResources(MessageImportance.Low, "GenerateResource.InputNewer", _newestUncorrelatedInput, outputFilePath);
                return true;
            }

            DateTime sourceTime = NativeMethodsShared.GetLastWriteFileUtcTime(sourceFilePath);

            string extension = Path.GetExtension(sourceFilePath);
            if (!String.Equals(extension, ".resx", StringComparison.OrdinalIgnoreCase) &&
                !String.Equals(extension, ".resw", StringComparison.OrdinalIgnoreCase))
            {
                // If source file is NOT a .resx, for example a .restext file,
                // timestamp checking is simple, because there's no linked files to examine, and no references.
                return NeedToRebuildSourceFile(sourceFilePath, sourceTime, outputFilePath, outputTime);
            }

            // OK, we have a .resx file

            // PERF: Regardless of whether the outputFile exists, if the source file is a .resx
            // go ahead and retrieve it from the cache. This is because we want the cache
            // to be populated so that incremental builds can be fast.
            // Note that this is a trade-off: clean builds will be slightly slower. However,
            // for clean builds we're about to read in this very same .resx file so reading
            // it now will page it in. The second read should be cheap.
            ResGenDependencies.ResXFile resxFileInfo;
            try
            {
                resxFileInfo = _cache.GetResXFileInfo(sourceFilePath, UsePreserializedResources, Log, WarnOnBinaryFormatterUse);
            }
            catch (Exception e) when (!ExceptionHandling.NotExpectedIoOrXmlException(e) || e is MSBuildResXException)
            {
                // Return true, so that resource processing will display the error
                // No point logging a duplicate error here as well
                return true;
            }

            // if the .resources file is out of date even just with respect to the .resx or
            // the additional inputs, we don't need to go to the point of checking the linked files.
            if (NeedToRebuildSourceFile(sourceFilePath, sourceTime, outputFilePath, outputTime))
            {
                // This already logged the reason
                return true;
            }

            // The .resources is up to date with respect to the .resx file -
            // we need to compare timestamps for each linked file inside
            // the .resx file itself
            if (resxFileInfo.LinkedFiles != null)
            {
                foreach (string linkedFilePath in resxFileInfo.LinkedFiles)
                {
                    DateTime linkedFileTime = NativeMethodsShared.GetLastWriteFileUtcTime(linkedFilePath);

                    if (linkedFileTime == DateTime.MinValue)
                    {
                        // Linked file is missing - force a build, so that resource generation
                        // will produce a nice error message
                        Log.LogMessageFromResources(MessageImportance.Low, "GenerateResource.LinkedInputDoesntExist", linkedFilePath);
                        return true;
                    }

                    if (linkedFileTime > outputTime)
                    {
                        // Linked file is newer, need to build
                        Log.LogMessageFromResources(MessageImportance.Low, "GenerateResource.LinkedInputNewer", linkedFilePath, outputFilePath);
                        return true;
                    }
                }
            }

            return false;
        }

        /// <summary>
        /// Returns true if the output does not exist, if the provided source is newer than the output,
        /// or if any of the set of additional inputs is newer than the output.  Otherwise, returns false.
        /// </summary>
        private bool NeedToRebuildSourceFile(string sourceFilePath, DateTime sourceTime, string outputFilePath, DateTime outputTime)
        {
            if (outputTime == DateTime.MinValue)
            {
                // Output file is missing, need to build
                Log.LogMessageFromResources(MessageImportance.Low, "GenerateResource.OutputDoesntExist", outputFilePath);
                return true;
            }

            if (sourceTime > outputTime)
            {
                // Source file is newer, need to build
                Log.LogMessageFromResources(MessageImportance.Low, "GenerateResource.InputNewer", sourceFilePath, outputFilePath);
                return true;
            }

            UpdateNewestUncorrelatedInputWriteTime();
            if (_newestUncorrelatedInput != null && _newestUncorrelatedInputWriteTime > outputTime)
            {
                // An uncorrelated input is newer, need to build
                Log.LogMessageFromResources(MessageImportance.Low, "GenerateResource.InputNewer", _newestUncorrelatedInput, outputFilePath);
                return true;
            }

            return false;
        }

        /// <summary>
        /// Add the strongly typed resource to the set of resources to process if it is out of date.
        /// </summary>
        private void GetStronglyTypedResourceToProcess(ref List<ITaskItem> inputsToProcess, ref List<ITaskItem> outputsToProcess)
        {
            bool needToRebuildSTR = false;
            CodeDomProvider provider = null;

            // The resource file isn't out of date. So check whether the STR class file is.
            try
            {
                if (StronglyTypedFileName == null)
                {
                    if (ProcessResourceFiles.TryCreateCodeDomProvider(Log, StronglyTypedLanguage, out provider))
                    {
                        StronglyTypedFileName = ProcessResourceFiles.GenerateDefaultStronglyTypedFilename(provider, OutputResources[0].ItemSpec);
                    }
                }
            }
            catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e))
            {
                Log.LogErrorWithCodeFromResources("GenerateResource.CannotWriteSTRFile", StronglyTypedFileName, e.Message);
                // Now that we've logged an error, we know we're not going to do anything more, so
                // don't bother to correctly populate the inputs / outputs.
                _unsuccessfullyCreatedOutFiles.Add(OutputResources[0].ItemSpec);
                _stronglyTypedResourceSuccessfullyCreated = false;
                return;
            }
            finally
            {
                provider?.Dispose();
            }

            // Now we have the filename, check if it's up to date
            DateTime sourceTime = NativeMethodsShared.GetLastWriteFileUtcTime(Sources[0].ItemSpec);
            DateTime outputTime = NativeMethodsShared.GetLastWriteFileUtcTime(StronglyTypedFileName);

            if (sourceTime == DateTime.MinValue)
            {
                // Source file is missing - force a build, so that resource generation
                // will produce a nice error message
                Log.LogMessageFromResources(MessageImportance.Low, "GenerateResource.InputDoesntExist", Sources[0].ItemSpec);
                needToRebuildSTR = true;
            }
            else if (outputTime == DateTime.MinValue)
            {
                // Output file is missing, need to build it
                Log.LogMessageFromResources(MessageImportance.Low, "GenerateResource.OutputDoesntExist", StronglyTypedFileName);
                needToRebuildSTR = true;
            }
            else if (sourceTime > outputTime)
            {
                // Source file is newer, need to build
                Log.LogMessageFromResources(MessageImportance.Low, "GenerateResource.InputNewer", Sources[0].ItemSpec, StronglyTypedFileName);
                needToRebuildSTR = true;
            }

            if (needToRebuildSTR)
            {
                // We know there's only one input, just make sure it gets processed and that will cause
                // the STR class file to get updated
                if (inputsToProcess.Count == 0)
                {
                    inputsToProcess.Add(Sources[0]);
                    outputsToProcess.Add(OutputResources[0]);
                }
            }
            else
            {
                // If the STR class file is up to date and was skipped then we
                // should consider the file written and add it to FilesWritten output
                _stronglyTypedResourceSuccessfullyCreated = true;
            }
        }

        /// <summary>
        /// Returns the newest last write time among the references and additional inputs.
        /// If any do not exist, returns DateTime.MaxValue so that resource generation produces a nice error.
        /// </summary>
        private void UpdateNewestUncorrelatedInputWriteTime()
        {
            if (_newestUncorrelatedInput != null)
            {
                // We already did this
                return;
            }

            // Check the timestamp of each of the passed-in references to find the newest;
            // and then the additional inputs
            var inputs = (this.References ?? []).Concat(this.AdditionalInputs ?? []);

            foreach (ITaskItem input in inputs)
            {
                DateTime time = NativeMethodsShared.GetLastWriteFileUtcTime(input.ItemSpec);

                if (time == DateTime.MinValue)
                {
                    // File does not exist: force a build to produce an error message
                    _newestUncorrelatedInput = input.ItemSpec;
                    _newestUncorrelatedInputWriteTime = time;
                    // Log it here so it's logged only once for all inputs
                    Log.LogMessageFromResources(MessageImportance.Low, "GenerateResource.InputDoesntExist", _newestUncorrelatedInput);
                    return;
                }

                if (time > _newestUncorrelatedInputWriteTime)
                {
                    _newestUncorrelatedInput = input.ItemSpec;
                    _newestUncorrelatedInputWriteTime = time;
                }
            }
        }

#if FEATURE_APPDOMAIN
        /// <summary>
        /// Make the decision about whether a separate AppDomain is needed.
        /// If this algorithm is unsure about whether a separate AppDomain is
        /// needed, it should always err on the side of returning 'true'. This
        /// is because a separate AppDomain, while slow to create, is always safe.
        /// </summary>
        /// <returns></returns>
        private bool NeedSeparateAppDomain()
        {
            if (NeverLockTypeAssemblies)
            {
                Log.LogMessageFromResources(MessageImportance.Low, "GenerateResource.SeparateAppDomainBecauseNeverLockTypeAssembliesTrue");
                return true;
            }

            foreach (ITaskItem source in _sources)
            {
                string extension = Path.GetExtension(source.ItemSpec);

                if (String.Equals(extension, ".resources.dll", StringComparison.OrdinalIgnoreCase) ||
                    String.Equals(extension, ".dll", StringComparison.OrdinalIgnoreCase) ||
                    String.Equals(extension, ".exe", StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }

                if (String.Equals(extension, ".resx", StringComparison.OrdinalIgnoreCase) ||
                    String.Equals(extension, ".resw", StringComparison.OrdinalIgnoreCase))
                {
                    XmlReader reader = null;
                    string name = null;

                    try
                    {
                        XmlReaderSettings readerSettings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore, CloseInput = true };

                        FileStream fs = File.OpenRead(source.ItemSpec);
                        reader = XmlReader.Create(fs, readerSettings);

                        while (reader.Read())
                        {
                            // Look for the <data> section
                            if (reader.NodeType == XmlNodeType.Element)
                            {
                                if (String.Equals(reader.Name, "data", StringComparison.OrdinalIgnoreCase))
                                {
                                    // Is there an attribute called type?
                                    string typeName = reader.GetAttribute("type");
                                    name = reader.GetAttribute("name");

                                    if (typeName != null)
                                    {
                                        Type type;

                                        // It is likely that we've seen this type before
                                        // we'll try our table of previously seen types
                                        // since it is *much* faster to do that than
                                        // call Type.GetType needlessly!
                                        if (!_typeTable.TryGetValue(typeName, out type))
                                        {
                                            string resolvedTypeName = typeName;

                                            // This type name might be an alias, so first resolve that if any.
                                            int indexOfSeperator = typeName.IndexOf(',');

                                            if (indexOfSeperator != -1)
                                            {
                                                string typeFromTypeName = typeName.Substring(0, indexOfSeperator);
                                                string maybeAliasFromTypeName = typeName.Substring(indexOfSeperator + 1);

                                                if (!String.IsNullOrWhiteSpace(maybeAliasFromTypeName))
                                                {
                                                    maybeAliasFromTypeName = maybeAliasFromTypeName.Trim();

                                                    string fullName = null;
                                                    if (_aliases.TryGetValue(maybeAliasFromTypeName, out fullName))
                                                    {
                                                        resolvedTypeName = typeFromTypeName + ", " + fullName;
                                                    }
                                                }
                                            }

                                            // Can this type be found in the GAC?
                                            type = Type.GetType(resolvedTypeName, throwOnError: false, ignoreCase: false);

                                            // Remember our resolved type
                                            _typeTable[typeName] = type;
                                        }

                                        if (type == null)
                                        {
                                            // If the type could not be found in the GAC, then we're going to need
                                            // to load the referenced assemblies (those passed in through the
                                            // "References" parameter during the building of this .RESX.  Therefore,
                                            // we should create a separate app-domain, so that those assemblies
                                            // can be unlocked when the task is finished.
                                            // The type didn't start with "System." so return true.
                                            Log.LogMessageFromResources(
                                                MessageImportance.Low,
                                                "GenerateResource.SeparateAppDomainBecauseOfType",
                                                name ?? String.Empty,
                                                typeName,
                                                source.ItemSpec,
                                                ((IXmlLineInfo)reader).LineNumber);

                                            return true;
                                        }

                                        // If there's a type, we don't need to look at any mimetype
                                        continue;
                                    }

                                    // DDB #9825.
                                    // There's no type attribute on this <data> -- if there's a MimeType, it's a serialized
                                    // object of unknown type, and we have to assume it will need a new app domain.
                                    // The possible mimetypes ResXResourceReader understands are:
                                    //
                                    // application/x-microsoft.net.object.binary.base64
                                    // application/x-microsoft.net.object.bytearray.base64
                                    // application/x-microsoft.net.object.binary.base64
                                    // application/x-microsoft.net.object.soap.base64
                                    // text/microsoft-urt/binary-serialized/base64
                                    // text/microsoft-urt/psuedoml-serialized/base64
                                    // text/microsoft-urt/soap-serialized/base64
                                    //
                                    // Of these, application/x-microsoft.net.object.bytearray.base64 usually has a type attribute
                                    // as well; ResxResourceReader will use that Type, which may not need a new app domain. So
                                    // if there's a type attribute, we don't look at mimetype.
                                    //
                                    // If there is a mimetype and no type, we can't tell the type without deserializing and loading it,
                                    // so we assume a new appdomain is needed.
                                    //
                                    // Actually, if application/x-microsoft.net.object.bytearray.base64 doesn't have a Type attribute,
                                    // ResxResourceReader assumes System.String, but for safety we don't assume that here.

                                    string mimeType = reader.GetAttribute("mimetype");

                                    if (mimeType != null)
                                    {
                                        if (NeedSeparateAppDomainBasedOnSerializedType(reader))
                                        {
                                            Log.LogMessageFromResources(
                                                MessageImportance.Low,
                                                "GenerateResource.SeparateAppDomainBecauseOfMimeType",
                                                name ?? String.Empty,
                                                mimeType,
                                                source.ItemSpec,
                                                ((IXmlLineInfo)reader).LineNumber);

                                            return true;
                                        }
                                    }
                                }
                                else if (String.Equals(reader.Name, "assembly", StringComparison.OrdinalIgnoreCase))
                                {
                                    string alias = reader.GetAttribute("alias");
                                    string fullName = reader.GetAttribute("name");

                                    if (!String.IsNullOrWhiteSpace(alias) && !String.IsNullOrWhiteSpace(fullName))
                                    {
                                        alias = alias.Trim();
                                        fullName = fullName.Trim();

                                        _aliases[alias] = fullName;
                                    }
                                }
                            }
                        }
                    }
                    catch (XmlException e)
                    {
                        Log.LogMessageFromResources(
                                        MessageImportance.Low,
                                        "GenerateResource.SeparateAppDomainBecauseOfExceptionLineNumber",
                                        source.ItemSpec,
                                        ((IXmlLineInfo)reader).LineNumber,
                                        e.Message);

                        return true;
                    }
                    catch (SerializationException e)
                    {
                        Log.LogMessageFromResources(
                                        MessageImportance.Low,
                                        "GenerateResource.SeparateAppDomainBecauseOfErrorDeserializingLineNumber",
                                        source.ItemSpec,
                                        name ?? String.Empty,
                                        ((IXmlLineInfo)reader).LineNumber,
                                        e.Message);

                        return true;
                    }
                    catch (Exception e) when (!ExceptionHandling.IsCriticalException(e))
                    {
                        // DDB#9819
                        // Customers have reported the following exceptions coming out of this method's call to GetType():
                        //      System.Runtime.InteropServices.COMException (0x8000000A): The data necessary to complete this operation is not yet available. (Exception from HRESULT: 0x8000000A)
                        //      System.NullReferenceException: Object reference not set to an instance of an object.
                        //      System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
                        // We don't have reproes, but probably the right thing to do is to assume a new app domain is needed on almost any exception.
                        // Any problem loading the type will get logged later when the resource reader tries it.
                        //
                        // XmlException or an IO exception is also possible from an invalid input file.

                        // If there was any problem parsing the .resx then log a message and
                        // fall back to using a separate AppDomain.
                        Log.LogMessageFromResources(
                                        MessageImportance.Low,
                                        "GenerateResource.SeparateAppDomainBecauseOfException",
                                        source.ItemSpec,
                                        e.Message);

                        // In case we need more information from the customer (given this has been heavily reported
                        // and we don't understand it properly) let the usual debug switch dump the stack.
                        if (Environment.GetEnvironmentVariable("MSBUILDDEBUG") == "1")
                        {
                            Log.LogErrorFromException(e, /* stack */ true, /* inner exceptions */ true, null);
                        }

                        return true;
                    }
                    finally
                    {
                        reader?.Close();
                    }
                }
            }

            return false;
        }

        /// <summary>
        /// Finds the "value" element expected to be the next element read from the supplied reader.
        /// Deserializes the data content in order to figure out whether it implies a new app domain
        /// should be used to process resources.
        /// </summary>
        private bool NeedSeparateAppDomainBasedOnSerializedType(XmlReader reader)
        {
            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element)
                {
                    if (!String.Equals(reader.Name, "value", StringComparison.OrdinalIgnoreCase))
                    {
                        // <data> claimed it was serialized, but didn't have a <value>;
                        // return true to err on side of caution
                        return true;
                    }

                    // Found "value" element
                    string data = reader.ReadElementContentAsString();

                    bool isSerializedObjectLoadable = DetermineWhetherSerializedObjectLoads(data);

                    // If it's not loadable, it's presumably a user type, so create a new app domain
                    return !isSerializedObjectLoadable;
                }
            }

            // We didn't find any element at all -- the .resx is malformed.
            // Return true to err on the side of caution. Error will appear later.
            return true;
        }

        /// <summary>
        /// Deserializes a base64 block from a resx in order to figure out if its type is in the GAC.
        /// Because we're not providing any assembly resolution callback, deserialization
        /// will attempt to load the object's type using fusion rules, which essentially means
        /// the GAC. So, if the object is null, it's definitely not in the GAC.
        /// </summary>
        private bool DetermineWhetherSerializedObjectLoads(string data)
        {
            byte[] serializedData = ByteArrayFromBase64WrappedString(data);

            BinaryFormatter binaryFormatter = new();

            using (MemoryStream memoryStream = new MemoryStream(serializedData))
            {
                // CodeQL [SM03722] required trust of BinaryFormatter-serialized resources documented at https://learn.microsoft.com/visualstudio/msbuild/generateresource-task
                // CodeQL [SM04191] required trust of BinaryFormatter-serialized resources documented at https://learn.microsoft.com/visualstudio/msbuild/generateresource-task
                object result = binaryFormatter.Deserialize(memoryStream);

                return result != null;
            }
        }

        /// <summary>
        /// Turns the nicely justified block of base64 found in a resx into a byte array.
        /// Copied from fx\src\winforms\managed\system\winforms\control.cs
        /// </summary>
        private static byte[] ByteArrayFromBase64WrappedString(string text)
        {
            if (text.AsSpan().IndexOfAny(' ', '\r', '\n') != -1) // Chars that should be ignored in the nicely justified block of base64
            {
                StringBuilder sb = new StringBuilder(text.Length);
                for (int i = 0; i < text.Length; i++)
                {
                    switch (text[i])
                    {
                        case ' ':
                        case '\r':
                        case '\n':
                            break;
                        default:
                            sb.Append(text[i]);
                            break;
                    }
                }
                return Convert.FromBase64String(sb.ToString());
            }
            else
            {
                return Convert.FromBase64String(text);
            }
        }
#endif

        /// <summary>
        /// Make sure that OutputResources has 1 file name for each name in Sources.
        /// </summary>
        private bool CreateOutputResourcesNames()
        {
            if (OutputResources == null)
            {
                OutputResources = new ITaskItem[Sources.Length];
                int i = 0;
                try
                {
                    for (i = 0; i < Sources.Length; ++i)
                    {
                        OutputResources[i] = new TaskItem(Path.ChangeExtension(Sources[i].ItemSpec, ".resources"));
                    }
                }
                catch (ArgumentException e)
                {
                    Log.LogErrorWithCodeFromResources("GenerateResource.InvalidFilename", Sources[i].ItemSpec, e.Message);
                    return false;
                }
            }

            // Now check for duplicates.
            if (ContainsDuplicates(OutputResources))
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// Remove any output resources that we didn't successfully create (due to error) from the
        /// OutputResources list. Keeps the ordering of OutputResources the same.
        /// </summary>
        /// <remarks>
        /// Q: Why didn't we keep a "successfully created" list instead, like in the Copy task does, which
        /// would save us doing the removal algorithm below?
        /// A: Because we want the ordering of OutputResources to be the same as the ordering passed in.
        /// Some items (the up to date ones) would be added to the successful output list first, and the other items
        /// are added during processing, so the ordering would change. We could fix that up, but it's better to do
        /// the fix up only in the rarer error case. If there were no errors, the algorithm below skips.</remarks>
        private void RemoveUnsuccessfullyCreatedResourcesFromOutputResources()
        {
            // Normally, there aren't any unsuccessful conversions.
            if (_unsuccessfullyCreatedOutFiles == null ||
                _unsuccessfullyCreatedOutFiles.Count == 0)
            {
                return;
            }

            ErrorUtilities.VerifyThrow(OutputResources != null && OutputResources.Length != 0, "Should be at least one output resource");

            // We only get here if there was at least one resource generation error.
            ITaskItem[] temp = new ITaskItem[OutputResources.Length - _unsuccessfullyCreatedOutFiles.Count];
            int copied = 0;
            int removed = 0;
            for (int i = 0; i < Sources.Length; i++)
            {
                // Check whether this one is in the bad list.
                if (removed < _unsuccessfullyCreatedOutFiles.Count &&
                    _unsuccessfullyCreatedOutFiles.Contains(OutputResources[i].ItemSpec))
                {
                    removed++;
                    Sources[i].SetMetadata("OutputResource", String.Empty);
                }
                else
                {
                    // Copy it to the okay list.
                    temp[copied] = OutputResources[i];
                    copied++;
                }
            }
            OutputResources = temp;
        }

        /// <summary>
        /// Record the list of file that will be written to disk.
        /// </summary>
        private void RecordFilesWritten()
        {
            // Add any output resources that were successfully created,
            // or would have been if they weren't already up to date (important for Clean)
            if (this.OutputResources != null)
            {
                foreach (ITaskItem item in this.OutputResources)
                {
                    _filesWritten.Add(item);
                }
            }

            // Add any state file
            if (StateFile?.ItemSpec.Length > 0)
            {
                // It's possible the file wasn't actually written (eg the path was invalid)
                // We can't easily tell whether that happened here, and I think it's fine to add it anyway.
                _filesWritten.Add(StateFile);
            }

            // Only add the STR class file if the CodeDOM succeeded, or if it exists and is up to date.
            // Otherwise, we probably didn't write it successfully.
            if (_stronglyTypedResourceSuccessfullyCreated)
            {
                if (StronglyTypedFileName == null)
                {
                    CodeDomProvider provider = null;
                    try
                    {
                        if (ProcessResourceFiles.TryCreateCodeDomProvider(Log, StronglyTypedLanguage, out provider))
                        {
                            StronglyTypedFileName = ProcessResourceFiles.GenerateDefaultStronglyTypedFilename(
                                provider, OutputResources[0].ItemSpec);
                        }
                    }
                    finally
                    {
                        provider?.Dispose();
                    }
                }

                _filesWritten.Add(new TaskItem(this.StronglyTypedFileName));
            }
        }

        /// <summary>
        /// Read the state file if able.
        /// </summary>
        private void ReadStateFile()
        {
            // First we look to see if we have a resgen linked files cache.  If so, then we can use that
            // cache to speed up processing.  If there's a problem reading the cache file (or it
            // just doesn't exist, then this method will return a brand new cache object.

            // This method eats IO Exceptions
            _cache = ResGenDependencies.DeserializeCache(StateFile?.ItemSpec, UseSourcePath, Log);
            ErrorUtilities.VerifyThrow(_cache != null, "We did not create a cache!");
        }

        /// <summary>
        /// Write the state file if there is one to be written.
        /// </summary>
        private void WriteStateFile()
        {
            if (_cache.IsDirty)
            {
                // And now we serialize the cache to save our resgen linked file resolution for later use.
                _cache.SerializeCache(StateFile?.ItemSpec, Log);
            }
        }
    }

    /// <summary>
    /// This class handles the processing of source resource files into compiled resource files.
    /// Its designed to be called from a separate AppDomain so that any files locked by ResXResourceReader
    /// can be released.
    /// </summary>
    internal sealed class ProcessResourceFiles
#if FEATURE_APPDOMAIN
        : MarshalByRefObject
#endif
    {
        #region fields
        /// <summary>
        /// List of readers used for input.
        /// </summary>
        private List<ReaderInfo> _readers = new List<ReaderInfo>();

        /// <summary>
        /// Logger for any messages or errors
        /// </summary>
        private TaskLoggingHelper _logger = null;

        /// <summary>
        /// Language for the strongly typed resources.
        /// </summary>
        private string _stronglyTypedLanguage;

        /// <summary>
        /// Filename for the strongly typed resources.
        /// Getter provided since the processor may choose a default.
        /// </summary>
        internal string StronglyTypedFilename
        {
            get
            {
                return _stronglyTypedFilename;
            }
        }
        private string _stronglyTypedFilename;

        /// <summary>
        /// Namespace for the strongly typed resources.
        /// </summary>
        private string _stronglyTypedNamespace;

        /// <summary>
        /// ResourceNamespace for the strongly typed resources.
        /// </summary>
        private string _stronglyTypedResourcesNamespace;

        /// <summary>
        /// Class name for the strongly typed resources.
        /// Getter provided since the processor may choose a default.
        /// </summary>
        internal string StronglyTypedClassName
        {
            get
            {
                return _stronglyTypedClassName;
            }
        }
        private string _stronglyTypedClassName;

        /// <summary>
        /// Whether the fields in the STR class should be public, rather than internal
        /// </summary>
        private bool _stronglyTypedClassIsPublic;

#if !FEATURE_ASSEMBLYLOADCONTEXT
        /// <summary>
        /// Class that gets called by the ResxResourceReader to resolve references
        /// to assemblies within the .RESX.
        /// </summary>
        private AssemblyNamesTypeResolutionService _typeResolver = null;

        /// <summary>
        /// Handles assembly resolution events.
        /// </summary>
        private ResolveEventHandler _eventHandler;
#endif

        /// <summary>
        /// The referenced assemblies
        /// </summary>
        private ITaskItem[] _assemblyFiles;

        /// <summary>
        /// The AssemblyNameExtensions for each of the referenced assemblies in "assemblyFiles".
        /// This is populated lazily.
        /// </summary>
        private AssemblyNameExtension[] _assemblyNames;

        /// <summary>
        /// List of input files to process.
        /// </summary>
        private List<ITaskItem> _inFiles;

#if !FEATURE_ASSEMBLYLOADCONTEXT
        /// <summary>
        /// List of satellite input files to process.
        /// </summary>
        private List<ITaskItem> _satelliteInFiles;
#endif

        /// <summary>
        /// List of output files to process.
        /// </summary>
        private List<ITaskItem> _outFiles;

        /// <summary>
        /// Whether we are extracting ResW files from an assembly, instead of creating .resources files.
        /// </summary>
        private bool _extractResWFiles;

        private bool _usePreserializedResources;

        internal List<ITaskItem> ExtractedResWFiles
        {
            get
            {
                if (_extractedResWFiles == null)
                {
                    _extractedResWFiles = new List<ITaskItem>();
                }
                return _extractedResWFiles;
            }
        }
        private List<ITaskItem> _extractedResWFiles;

        /// <summary>
        /// Record all the information about outputs here to avoid future incremental builds.
        /// </summary>
        internal List<ResGenDependencies.PortableLibraryFile> PortableLibraryCacheInfo
        {
            get { return _portableLibraryCacheInfo; }
        }
        private List<ResGenDependencies.PortableLibraryFile> _portableLibraryCacheInfo;

        /// <summary>
        /// List of output files that we failed to create due to an error.
        /// See note in RemoveUnsuccessfullyCreatedResourcesFromOutputResources()
        /// </summary>
        internal ArrayList UnsuccessfullyCreatedOutFiles
        {
            get
            {
                if (_unsuccessfullyCreatedOutFiles == null)
                {
                    _unsuccessfullyCreatedOutFiles = new ArrayList();
                }
                return _unsuccessfullyCreatedOutFiles;
            }
        }
        private ArrayList _unsuccessfullyCreatedOutFiles;

        /// <summary>
        /// Whether we successfully created the STR class
        /// </summary>
        internal bool StronglyTypedResourceSuccessfullyCreated
        {
            get
            {
                return _stronglyTypedResourceSuccessfullyCreated;
            }
        }
        private bool _stronglyTypedResourceSuccessfullyCreated = false;

        /// <summary>
        /// Indicates whether the resource reader should use the source file's
        /// directory to resolve relative file paths.
        /// </summary>
        private bool _useSourcePath = false;

        private bool _logWarningForBinaryFormatter = false;

        #endregion

        /// <summary>
        /// Process all files.
        /// </summary>
        internal void Run(
            TaskLoggingHelper log,
            ITaskItem[] assemblyFilesList,
            List<ITaskItem> inputs,
            List<ITaskItem> satelliteInputs,
            List<ITaskItem> outputs,
            bool sourcePath,
            bool usePreserializedResources,
            string language,
            string namespacename,
            string resourcesNamespace,
            string filename,
            string classname,
            bool publicClass,
            bool extractingResWFiles,
            string resWOutputDirectory,
            bool logWarningForBinaryFormatter)
        {
            _logger = log;
            _assemblyFiles = assemblyFilesList;
            _inFiles = inputs;
#if !FEATURE_ASSEMBLYLOADCONTEXT
            _satelliteInFiles = satelliteInputs;
#endif
            _outFiles = outputs;
            _useSourcePath = sourcePath;
            _stronglyTypedLanguage = language;
            _stronglyTypedNamespace = namespacename;
            _stronglyTypedResourcesNamespace = resourcesNamespace;
            _stronglyTypedFilename = filename;
            _stronglyTypedClassName = classname;
            _stronglyTypedClassIsPublic = publicClass;
            _readers = new List<ReaderInfo>();
            _extractResWFiles = extractingResWFiles;
            _portableLibraryCacheInfo = new List<ResGenDependencies.PortableLibraryFile>();
            _usePreserializedResources = usePreserializedResources;
            _logWarningForBinaryFormatter = logWarningForBinaryFormatter;

#if !FEATURE_ASSEMBLYLOADCONTEXT
            // If references were passed in, we will have to give the ResxResourceReader an object
            // by which it can resolve types that are referenced from within the .RESX.
            if ((_assemblyFiles?.Length > 0))
            {
                _typeResolver = new AssemblyNamesTypeResolutionService(_assemblyFiles);
            }
#endif

            try
            {
#if !FEATURE_ASSEMBLYLOADCONTEXT
                // Install assembly resolution event handler.
                _eventHandler = new ResolveEventHandler(ResolveAssembly);
                AppDomain.CurrentDomain.AssemblyResolve += _eventHandler;
#endif

                for (int i = 0; i < _inFiles.Count; ++i)
                {
                    string outputSpec = _extractResWFiles ? resWOutputDirectory : _outFiles[i].ItemSpec;
                    if (!ProcessFile(_inFiles[i].ItemSpec, outputSpec))
                    {
                        // Since we failed, remove items from OutputResources.  Note when extracting ResW
                        // files, we won't have added anything to OutputResources up front though.
                        if (!_extractResWFiles)
                        {
                            UnsuccessfullyCreatedOutFiles.Add(outputSpec);
                        }
                    }
                }
            }
            finally
            {
#if !FEATURE_ASSEMBLYLOADCONTEXT
                // Remove the event handler.
                AppDomain.CurrentDomain.AssemblyResolve -= _eventHandler;
                _eventHandler = null;
#endif
            }
        }

#if !FEATURE_ASSEMBLYLOADCONTEXT
        /// <summary>
        /// Callback to resolve assembly names to assemblies.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        internal Assembly ResolveAssembly(object sender, ResolveEventArgs args)
        {
            AssemblyNameExtension requestedAssemblyName = new AssemblyNameExtension(args.Name);

            if (_assemblyFiles != null)
            {
                PopulateAssemblyNames();

                // Loop through all the references passed in, and see if any of them have an assembly
                // name that exactly matches the requested one.
                for (int i = 0; i < _assemblyNames.Length; i++)
                {
                    AssemblyNameExtension candidateAssemblyName = _assemblyNames[i];

                    if (candidateAssemblyName != null)
                    {
                        if (candidateAssemblyName.CompareTo(requestedAssemblyName) == 0)
                        {
                            return Assembly.UnsafeLoadFrom(_assemblyFiles[i].ItemSpec);
                        }
                    }
                }

                // If none of the referenced assembly names matches exactly, try to find one that
                // has the same base name.  This is here to fix bug where the
                // serialized data inside the .RESX referred to the assembly just by the base name,
                // omitting the version, culture, publickeytoken information.
                for (int i = 0; i < _assemblyNames.Length; i++)
                {
                    AssemblyNameExtension candidateAssemblyName = _assemblyNames[i];

                    if (candidateAssemblyName != null)
                    {
                        if (String.Equals(requestedAssemblyName.Name, candidateAssemblyName.Name, StringComparison.CurrentCultureIgnoreCase))
                        {
                            return Assembly.UnsafeLoadFrom(_assemblyFiles[i].ItemSpec);
                        }
                    }
                }
            }

            return null;
        }
#endif

        private void PopulateAssemblyNames()
        {
            // Populate the list of assembly names for all passed-in references if it hasn't
            // been populated already.
            if (_assemblyNames == null)
            {
                _assemblyNames = new AssemblyNameExtension[_assemblyFiles.Length];
                for (int i = 0; i < _assemblyFiles.Length; i++)
                {
                    ITaskItem assemblyFile = _assemblyFiles[i];
                    _assemblyNames[i] = null;

                    if (assemblyFile.ItemSpec != null && FileSystems.Default.FileExists(assemblyFile.ItemSpec))
                    {
                        string fusionName = assemblyFile.GetMetadata(ItemMetadataNames.fusionName);
                        if (!String.IsNullOrEmpty(fusionName))
                        {
                            _assemblyNames[i] = new AssemblyNameExtension(fusionName);
                        }
                        else
                        {
                            // whoever passed us this reference wasn't polite enough to also
                            // give us a metadata with the fusion name.  Trying to load up every
                            // assembly here would take a lot of time, so just stick the assembly
                            // file name (which we assume generally maps to the simple name) into
                            // the list instead. If there's a fusion name that matches, we'll get
                            // that first; otherwise there's a good chance that if the simple name
                            // matches the file name, it's a good match.
                            _assemblyNames[i] = new AssemblyNameExtension(Path.GetFileNameWithoutExtension(assemblyFile.ItemSpec));
                        }
                    }
                }
            }
        }

        #region Code from ResGen.EXE

        /// <summary>
        /// Read all resources from a file and write to a new file in the chosen format
        /// </summary>
        /// <remarks>Uses the input and output file extensions to determine their format</remarks>
        /// <param name="inFile">Input resources file</param>
        /// <param name="outFileOrDir">Output resources file or directory</param>
        /// <returns>True if conversion was successful, otherwise false</returns>
        private bool ProcessFile(string inFile, string outFileOrDir)
        {
            Format inFileFormat = GetFormat(inFile);
            if (inFileFormat == Format.Error)
            {
                // GetFormat would have logged an error.
                return false;
            }
            if (inFileFormat != Format.Assembly) // outFileOrDir is a directory when the input file is an assembly
            {
                Format outFileFormat = GetFormat(outFileOrDir);
                if (outFileFormat == Format.Assembly)
                {
                    _logger.LogErrorFromResources("GenerateResource.CannotWriteAssembly", outFileOrDir);
                    return false;
                }
                else if (outFileFormat == Format.Error)
                {
                    return false;
                }
            }

            if (!_extractResWFiles)
            {
                _logger.LogMessageFromResources("GenerateResource.ProcessingFile", inFile, outFileOrDir);
            }

            // Reset state
            _readers = new List<ReaderInfo>();

            try
            {
                ReadResources(inFile, _useSourcePath, outFileOrDir);
            }
            catch (InputFormatNotSupportedException)
            {
                _logger.LogErrorWithCodeFromResources(null, FileUtilities.GetFullPathNoThrow(inFile), 0, 0, 0, 0,
                                                      "GenerateResource.CoreSupportsLimitedScenarios");
                return false;
            }
            catch (MSBuildResXException msbuildResXException)
            {
                _logger.LogErrorWithCodeFromResources(null, FileUtilities.GetFullPathNoThrow(inFile), 0, 0, 0, 0,
                    "General.InvalidResxFile", msbuildResXException.InnerException.ToString());
                return false;
            }
            catch (ArgumentException ae)
            {
                if (ae.InnerException is XmlException xe)
                {
                    _logger.LogErrorWithCodeFromResources(null, FileUtilities.GetFullPathNoThrow(inFile), xe.LineNumber,
                        xe.LinePosition, 0, 0, "General.InvalidResxFile", xe.Message);
                }
                else
                {
                    _logger.LogErrorWithCodeFromResources(null, FileUtilities.GetFullPathNoThrow(inFile), 0, 0, 0, 0,
                        "General.InvalidResxFile", ae.Message);
                }
                return false;
            }
            catch (TextFileException tfe)
            {
                // Used to pass back error context from ReadTextResources to here.
                _logger.LogErrorWithCodeFromResources(null, tfe.FileName, tfe.LineNumber, tfe.LinePosition, 1, 1,
                    "GenerateResource.MessageTunnel", tfe.Message);
                return false;
            }
            catch (XmlException xe)
            {
                _logger.LogErrorWithCodeFromResources(null, FileUtilities.GetFullPathNoThrow(inFile), xe.LineNumber,
                    xe.LinePosition, 0, 0, "General.InvalidResxFile", xe.Message);
                return false;
            }
            catch (Exception e) when (
                                      e is SerializationException ||
                                      e is TargetInvocationException)
            {
                // DDB #9819
                // SerializationException and TargetInvocationException can occur when trying to deserialize a type from a resource format (typically with other exceptions inside)
                // This is a bug in the type being serialized, so the best we can do is dump diagnostic information and move on to the next input resource file.
                _logger.LogErrorWithCodeFromResources(null, FileUtilities.GetFullPathNoThrow(inFile), 0, 0, 0, 0,
                    "General.InvalidResxFile", e.Message);

                // Log the stack, so the problem with the type in the .resx is diagnosable by the customer
                _logger.LogErrorFromException(e, /* stack */ true, /* inner exceptions */ true,
                    FileUtilities.GetFullPathNoThrow(inFile));
                return false;
            }
            catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e))
            {
                // Regular IO error
                _logger.LogErrorWithCodeFromResources(null, FileUtilities.GetFullPathNoThrow(inFile), 0, 0, 0, 0,
                    "General.InvalidResxFile", e.Message);
                return false;
            }

            string currentOutputFile = null;
            string currentOutputDirectory = null;
            string currentOutputSourceCodeFile = null;
            bool currentOutputDirectoryAlreadyExisted = true;

            try
            {
                if (GetFormat(inFile) == Format.Assembly)
                {
                    // Prepare cache data
                    ResGenDependencies.PortableLibraryFile library = new ResGenDependencies.PortableLibraryFile(inFile);
                    List<string> resWFilesForThisAssembly = new List<string>();

                    foreach (ReaderInfo reader in _readers)
                    {
                        String currentOutputFileNoPath = reader.outputFileName + ".resw";
                        currentOutputFile = null;
                        currentOutputDirectoryAlreadyExisted = true;
                        string priDirectory = Path.Combine(outFileOrDir ?? String.Empty,
                            reader.assemblySimpleName);
                        currentOutputDirectory = Path.Combine(priDirectory,
                            reader.cultureName ?? String.Empty);

                        if (!FileSystems.Default.DirectoryExists(currentOutputDirectory))
                        {
                            currentOutputDirectoryAlreadyExisted = false;
                            Directory.CreateDirectory(currentOutputDirectory);
                        }
                        currentOutputFile = Path.Combine(currentOutputDirectory, currentOutputFileNoPath);

                        // For very long resource names, this directory structure may be too deep.
                        // If so, assume that the name is so long it will already uniquely distinguish itself.
                        // However for shorter names we'd still prefer to use the assembly simple name
                        // in the path to avoid conflicts.
                        currentOutputFile = EnsurePathIsShortEnough(currentOutputFile, currentOutputFileNoPath,
                            outFileOrDir, reader.cultureName);

                        if (currentOutputFile == null)
                        {
                            // We couldn't generate a file name short enough to handle this.  Fail but continue.
                            continue;
                        }

                        // Always write the output file here - other logic prevents us from processing this
                        // file for incremental builds if everything was up to date.
                        WriteResources(reader, currentOutputFile);

                        string escapedOutputFile = EscapingUtilities.Escape(currentOutputFile);
                        ITaskItem newOutputFile = new TaskItem(escapedOutputFile);
                        resWFilesForThisAssembly.Add(escapedOutputFile);
                        newOutputFile.SetMetadata("ResourceIndexName", reader.assemblySimpleName);
                        library.AssemblySimpleName = reader.assemblySimpleName;
                        if (reader.fromNeutralResources)
                        {
                            newOutputFile.SetMetadata("NeutralResourceLanguage", reader.cultureName);
                            library.NeutralResourceLanguage = reader.cultureName;
                        }
                        ExtractedResWFiles.Add(newOutputFile);
                    }

                    library.OutputFiles = resWFilesForThisAssembly.ToArray();
                    _portableLibraryCacheInfo.Add(library);
                }
                else
                {
                    currentOutputFile = outFileOrDir;
                    ErrorUtilities.VerifyThrow(_readers.Count == 1,
                        "We have no readers, or we have multiple readers & are ignoring subsequent ones.  Num readers: {0}",
                        _readers.Count);
                    WriteResources(_readers[0], outFileOrDir);
                }

                if (_stronglyTypedLanguage != null)
                {
                    try
                    {
                        ErrorUtilities.VerifyThrow(_readers.Count == 1,
                            "We have no readers, or we have multiple readers & are ignoring subsequent ones.  Num readers: {0}",
                            _readers.Count);
                        CreateStronglyTypedResources(_readers[0], outFileOrDir, inFile, out currentOutputSourceCodeFile);
                    }
                    catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e))
                    {
                        // IO Error
                        _logger.LogErrorWithCodeFromResources("GenerateResource.CannotWriteSTRFile",
                            _stronglyTypedFilename, e.Message);

                        if (FileSystems.Default.FileExists(outFileOrDir)
                            && GetFormat(inFile) != Format.Assembly
                            // outFileOrDir is a directory when the input file is an assembly
                            && GetFormat(outFileOrDir) != Format.Assembly)
                        // Never delete an assembly since we don't ever actually write to assemblies.
                        {
                            RemoveCorruptedFile(outFileOrDir);
                        }
                        if (currentOutputSourceCodeFile != null)
                        {
                            RemoveCorruptedFile(currentOutputSourceCodeFile);
                        }
                        return false;
                    }
                }
            }
            catch (IOException io)
            {
                if (currentOutputFile != null)
                {
                    _logger.LogErrorWithCodeFromResources("GenerateResource.CannotWriteOutput",
                        FileUtilities.GetFullPathNoThrow(currentOutputFile), io.Message);
                    if (FileSystems.Default.FileExists(currentOutputFile))
                    {
                        if (GetFormat(currentOutputFile) != Format.Assembly)
                        // Never delete an assembly since we don't ever actually write to assemblies.
                        {
                            RemoveCorruptedFile(currentOutputFile);
                        }
                    }
                }

                if (currentOutputDirectory != null &&
                    !currentOutputDirectoryAlreadyExisted)
                {
                    // Do not annoy the user by removing an empty directory we did not create.
                    try
                    {
                        Directory.Delete(currentOutputDirectory); // Remove output directory if empty
                    }
                    catch (Exception e) when (!ExceptionHandling.IsCriticalException(e))
                    {
                        // Fail silently (we are not even checking if the call to File.Delete succeeded)
                    }
                }
                return false;
            }
            catch (Exception e) when (
                                      e is SerializationException ||
                                      e is TargetInvocationException)
            {
                // DDB #9819
                // SerializationException and TargetInvocationException can occur when trying to serialize a type into a resource format (typically with other exceptions inside)
                // This is a bug in the type being serialized, so the best we can do is dump diagnostic information and move on to the next input resource file.
                _logger.LogErrorWithCodeFromResources("GenerateResource.CannotWriteOutput",
                    FileUtilities.GetFullPathNoThrow(inFile), e.Message); // Input file is more useful to log

                // Log the stack, so the problem with the type in the .resx is diagnosable by the customer
                _logger.LogErrorFromException(e, /* stack */ true, /* inner exceptions */ true,
                    FileUtilities.GetFullPathNoThrow(inFile));
                return false;
            }
            catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e))
            {
                // Regular IO error
                _logger.LogErrorWithCodeFromResources("GenerateResource.CannotWriteOutput",
                    FileUtilities.GetFullPathNoThrow(currentOutputFile), e.Message);
                return false;
            }

            return true;
        }

        /// <summary>
        /// For very long resource names, the directory structure we generate may be too deep.
        /// If so, assume that the name is so long it will already uniquely distinguish itself.
        /// However for shorter names we'd still prefer to use the assembly simple name
        /// in the path to avoid conflicts.
        /// </summary>
        /// <param name="currentOutputFile">The current path name</param>
        /// <param name="currentOutputFileNoPath">The current file name without a path.</param>
        /// <param name="outputDirectory">Output directory path</param>
        /// <param name="cultureName">culture for this resource</param>
        /// <returns>The current path or a shorter one.</returns>
        private string EnsurePathIsShortEnough(string currentOutputFile, string currentOutputFileNoPath, string outputDirectory, string cultureName)
        {
            if (!NativeMethodsShared.HasMaxPath)
            {
                return Path.GetFullPath(currentOutputFile);
            }

            // File names >= 260 characters won't work.  File names of exactly 259 characters are odd though.
            // They seem to work with Notepad and Windows Explorer, but not with MakePri.  They don't work
            // reliably with cmd's dir command either (depending on whether you use absolute or relative paths
            // and whether there are quotes around the name).
            const int EffectiveMaxPath = 258;   // Everything <= EffectiveMaxPath should work well.
            bool success;
            try
            {
                currentOutputFile = Path.GetFullPath(currentOutputFile);
                success = currentOutputFile.Length <= EffectiveMaxPath;
            }
            catch (PathTooLongException)
            {
                success = false;
            }

            if (!success)
            {
                string shorterPath = Path.Combine(outputDirectory ?? String.Empty, cultureName ?? String.Empty);
                if (!FileSystems.Default.DirectoryExists(shorterPath))
                {
                    Directory.CreateDirectory(shorterPath);
                }
                currentOutputFile = Path.Combine(shorterPath, currentOutputFileNoPath);

                // Try again
                try
                {
                    currentOutputFile = Path.GetFullPath(currentOutputFile);
                    success = currentOutputFile.Length <= EffectiveMaxPath;
                }
                catch (PathTooLongException)
                {
                    success = false;
                }

                // Can't do anything more without violating correctness.
                if (!success)
                {
                    _logger.LogErrorWithCodeFromResources("GenerateResource.PathTooLong", currentOutputFile);
                    currentOutputFile = null;
                    // We've logged an error message.  This MSBuild task will fail, but can continue processing other input (to find other errors).
                }
            }
            return currentOutputFile;
        }

        /// <summary>
        /// Remove a corrupted file, with error handling and a warning if we fail.
        /// </summary>
        /// <param name="filename">Full path to file to delete</param>
        private void RemoveCorruptedFile(string filename)
        {
            _logger.LogMessageFromResources("GenerateResource.CorruptOutput", FileUtilities.GetFullPathNoThrow(filename));
            try
            {
                File.Delete(filename);
            }
            catch (Exception deleteException)
            {
                _logger.LogWarningWithCodeFromResources("GenerateResource.DeleteCorruptOutputFailed", FileUtilities.GetFullPathNoThrow(filename), deleteException.Message);

                if (ExceptionHandling.NotExpectedException(deleteException))
                {
                    throw;
                }
            }
        }

        /// <summary>
        /// Figure out the format of an input resources file from the extension
        /// </summary>
        /// <param name="filename">Input resources file</param>
        /// <returns>Resources format</returns>
        private Format GetFormat(string filename)
        {
            string extension;
            try
            {
                extension = Path.GetExtension(filename);
            }
            catch (ArgumentException ex)
            {
                _logger.LogErrorWithCodeFromResources("GenerateResource.InvalidFilename", filename, ex.Message);
                return Format.Error;
            }

            if (String.Equals(extension, ".txt", StringComparison.OrdinalIgnoreCase) ||
                String.Equals(extension, ".restext", StringComparison.OrdinalIgnoreCase))
            {
                return Format.Text;
            }
            else if (String.Equals(extension, ".resx", StringComparison.OrdinalIgnoreCase) ||
                     String.Equals(extension, ".resw", StringComparison.OrdinalIgnoreCase))
            {
                return Format.XML;
            }
            else if (String.Equals(extension, ".resources.dll", StringComparison.OrdinalIgnoreCase) ||
                     String.Equals(extension, ".dll", StringComparison.OrdinalIgnoreCase) ||
                     String.Equals(extension, ".exe", StringComparison.OrdinalIgnoreCase))
            {
                return Format.Assembly;
            }
            else if (String.Equals(extension, ".resources", StringComparison.OrdinalIgnoreCase))
            {
                return Format.Binary;
            }
            else
            {
                _logger.LogErrorWithCodeFromResources("GenerateResource.UnknownFileExtension", Path.GetExtension(filename), filename);
                return Format.Error;
            }
        }

        /// <summary>
        /// Text files are just name/value pairs.  ResText is the same format
        /// with a unique extension to work around some ambiguities with MSBuild
        /// ResX is our existing XML format from V1.
        /// </summary>
        private enum Format
        {
            Text, // .txt or .restext
            XML, // .resx
            Binary, // .resources
            Assembly, // .dll, .exe or .resources.dll
            Error, // anything else
        }

        /// <summary>
        /// Reads the resources out of the specified file and populates the
        /// resources hashtable.
        /// </summary>
        /// <param name="filename">Filename to load</param>
        /// <param name="shouldUseSourcePath">Whether to resolve paths in the
        /// resources file relative to the resources file location</param>
        /// <param name="outFileOrDir"> Output file or directory. </param>
        private void ReadResources(String filename, bool shouldUseSourcePath, String outFileOrDir)
        {
            Format format = GetFormat(filename);

            if (format == Format.Assembly) // Multiple input .resources files within one assembly
            {
#if !FEATURE_ASSEMBLYLOADCONTEXT
                ReadAssemblyResources(filename, outFileOrDir);
#else
                throw new InputFormatNotSupportedException("Reading resources from Assembly not supported on .NET Core MSBuild");
#endif
            }
            else
            {
                ReaderInfo reader = new ReaderInfo();
                _readers.Add(reader);
                switch (format)
                {
                    case Format.Text:
                        ReadTextResources(reader, filename);
                        break;

                    case Format.XML:
                        // On full framework, the default is to use the longstanding
                        // deserialize/reserialize approach. On Core, always use the new
                        // preserialized approach.
#if FEATURE_RESXREADER_LIVEDESERIALIZATION
                        if (!_usePreserializedResources)
                        {
                            ResXResourceReader resXReader = null;
                            if (_typeResolver != null)
                            {
                                resXReader = new ResXResourceReader(filename, _typeResolver);
                            }
                            else
                            {
                                resXReader = new ResXResourceReader(filename);
                            }

                            if (shouldUseSourcePath)
                            {
                                String fullPath = Path.GetFullPath(filename);
                                resXReader.BasePath = Path.GetDirectoryName(fullPath);
                            }
                            // ReadResources closes the reader for us
                            ReadResources(reader, resXReader, filename);
                        }
                        else
#endif
                        {
                            if (Traits.Instance.EscapeHatches.UseMinimalResxParsingInCoreScenarios)
                            {
                                AddResourcesUsingMinimalCoreResxParsing(filename, reader);
                            }
                            else
                            {
                                foreach (IResource resource in MSBuildResXReader.GetResourcesFromFile(filename, shouldUseSourcePath, _logger, _logWarningForBinaryFormatter))
                                {
                                    AddResource(reader, resource, filename, 0, 0);
                                }
                            }
                        }
                        break;

                    case Format.Binary:
#if FEATURE_RESXREADER_LIVEDESERIALIZATION
                        ReadResources(reader, new ResourceReader(filename), filename); // closes reader for us
                        break;
#else
                        throw new InputFormatNotSupportedException("Reading resources from binary .resources not supported on .NET Core MSBuild");
#endif

                    default:
                        // We should never get here, we've already checked the format
                        Debug.Fail($"Unknown format {format}");
                        return;
                }
                _logger.LogMessageFromResources(MessageImportance.Low, "GenerateResource.ReadResourceMessage", reader.resources.Count, filename);
            }
        }

        /// <summary>
        /// Legacy Core implementation of string-only ResX handling
        /// </summary>
        private void AddResourcesUsingMinimalCoreResxParsing(string filename, ReaderInfo reader)
        {
            using (var xmlReader = new XmlTextReader(filename))
            {
                xmlReader.WhitespaceHandling = WhitespaceHandling.None;
                XDocument doc = XDocument.Load(xmlReader, LoadOptions.PreserveWhitespace);
                foreach (XElement dataElem in doc.Element("root").Elements("data"))
                {
                    string name = dataElem.Attribute("name").Value;
                    string value = dataElem.Element("value").Value;
                    AddResource(reader, name, value, filename);
                }
            }
        }

#if !FEATURE_ASSEMBLYLOADCONTEXT
        /// <summary>
        /// Reads resources from an assembly.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="outFileOrDir"> Output file or directory. </param>
        /// <remarks> This should not run for Framework assemblies. </remarks>
        internal void ReadAssemblyResources(String name, String outFileOrDir)
        {
            // If something else in the solution failed to build...
            if (!FileSystems.Default.FileExists(name))
            {
                _logger.LogErrorWithCodeFromResources("GenerateResource.MissingFile", name);
                return;
            }

            Assembly a = null;
            bool mainAssembly = false;
            bool failedLoadingCultureInfo = false;
            NeutralResourcesLanguageAttribute neutralResourcesLanguageAttribute = null;
            AssemblyName assemblyName = null;

            try
            {
                a = Assembly.UnsafeLoadFrom(name);
                assemblyName = a.GetName();

                if (_extractResWFiles)
                {
                    var targetFrameworkAttribute = a.GetCustomAttribute<TargetFrameworkAttribute>();
                    if (
                            targetFrameworkAttribute != null &&
                            (
                                targetFrameworkAttribute.FrameworkName.StartsWith("Silverlight,", StringComparison.OrdinalIgnoreCase) ||
                                targetFrameworkAttribute.FrameworkName.StartsWith("WindowsPhone,", StringComparison.OrdinalIgnoreCase)))
                    {
                        // Skip Silverlight assemblies.
                        _logger.LogMessageFromResources("GenerateResource.SkippingExtractingFromNonSupportedFramework", name, targetFrameworkAttribute.FrameworkName);
                        return;
                    }

                    _logger.LogMessageFromResources("GenerateResource.ExtractingResWFiles", name, outFileOrDir);
                }

                CultureInfo ci = null;
                try
                {
                    ci = assemblyName.CultureInfo;
                }
                catch (ArgumentException e)
                {
                    _logger.LogWarningWithCodeFromResources(null, name, 0, 0, 0, 0, "GenerateResource.CreatingCultureInfoFailed", e.GetType().Name, e.Message, assemblyName.ToString());
                    failedLoadingCultureInfo = true;
                }

                if (!failedLoadingCultureInfo)
                {
                    mainAssembly = ci.Equals(CultureInfo.InvariantCulture);
                    neutralResourcesLanguageAttribute = CheckAssemblyCultureInfo(name, assemblyName, ci, a, mainAssembly);
                }  // if (!failedLoadingCultureInfo)
            }
            catch (BadImageFormatException)
            {
                // If we're extracting ResW files, this task is being run on all DLL's referred to by the project.
                // That may potentially include C++ libraries & immersive (non-portable) class libraries, which don't have resources.
                // We can't easily filter those.  We can simply skip them.
                return;
            }
            catch (ArgumentException e) when (e.InnerException is BadImageFormatException)
            {
                // BadImageFormatExceptions can be wrapped in ArgumentExceptions, so catch those, too. See https://referencesource.microsoft.com/#mscorlib/system/reflection/module.cs,857
                return;
            }
            catch (Exception e) when (!ExceptionHandling.IsCriticalException(e))
            {
                _logger.LogErrorWithCodeFromResources("GenerateResource.CannotLoadAssemblyLoadFromFailed", name, e);
            }

            if (a != null)
            {
                String[] resources = a.GetManifestResourceNames();
                CultureInfo satCulture = null;
                String expectedExt = null;
                if (!failedLoadingCultureInfo)
                {
                    satCulture = assemblyName.CultureInfo;
                    if (!satCulture.Equals(CultureInfo.InvariantCulture))
                    {
                        expectedExt = '.' + satCulture.Name + ".resources";
                    }
                }

                foreach (String resName in resources)
                {
                    if (!resName.EndsWith(".resources", StringComparison.OrdinalIgnoreCase)) // Skip non-.resources assembly blobs
                    {
                        continue;
                    }

                    if (mainAssembly)
                    {
                        if (CultureInfo.InvariantCulture.CompareInfo.IsSuffix(resName, ".en-US.resources", CompareOptions.IgnoreCase))
                        {
                            _logger.LogErrorFromResources("GenerateResource.ImproperlyBuiltMainAssembly", resName, name);
                            continue;
                        }

                        if (neutralResourcesLanguageAttribute == null)
                        {
                            _logger.LogWarningWithCodeFromResources(null, name, 0, 0, 0, 0, "GenerateResource.MainAssemblyMissingNeutralResourcesLanguage", name);
                            break;
                        }
                    }
                    else if (!failedLoadingCultureInfo && !CultureInfo.InvariantCulture.CompareInfo.IsSuffix(resName, expectedExt, CompareOptions.IgnoreCase))
                    {
                        _logger.LogErrorFromResources("GenerateResource.ImproperlyBuiltSatelliteAssembly", resName, expectedExt, name);
                        continue;
                    }

                    try
                    {
                        Stream s = a.GetManifestResourceStream(resName);
                        using (IResourceReader rr = new ResourceReader(s))
                        {
                            ReaderInfo reader = new ReaderInfo();
                            if (mainAssembly)
                            {
                                reader.fromNeutralResources = true;
                                reader.assemblySimpleName = assemblyName.Name;
                            }
                            else
                            {
                                Debug.Assert(assemblyName.Name.EndsWith(".resources", StringComparison.OrdinalIgnoreCase));
                                reader.assemblySimpleName = assemblyName.Name.Remove(assemblyName.Name.Length - 10);  // Remove .resources from satellite assembly name
                            }
                            reader.outputFileName = resName.Remove(resName.Length - 10); // Remove the .resources extension
                            if (satCulture != null && !String.IsNullOrEmpty(satCulture.Name))
                            {
                                reader.cultureName = satCulture.Name;
                            }
                            else if (neutralResourcesLanguageAttribute != null && !String.IsNullOrEmpty(neutralResourcesLanguageAttribute.CultureName))
                            {
                                reader.cultureName = neutralResourcesLanguageAttribute.CultureName;
                            }

                            if (reader.cultureName != null)
                            {
                                // Remove the culture from the filename
                                if (reader.outputFileName.EndsWith("." + reader.cultureName, StringComparison.OrdinalIgnoreCase))
                                {
                                    reader.outputFileName = reader.outputFileName.Remove(reader.outputFileName.Length - (reader.cultureName.Length + 1));
                                }
                            }
                            _readers.Add(reader);

                            foreach (DictionaryEntry pair in rr)
                            {
                                AddResource(reader, (string)pair.Key, pair.Value, resName);
                            }
                        }
                    }
                    catch (FileNotFoundException)
                    {
                        _logger.LogErrorWithCodeFromResources(null, name, 0, 0, 0, 0, "GenerateResource.NoResourcesFileInAssembly", resName);
                    }
                }
            }

            var satelliteAssemblies = _satelliteInFiles.Where(ti => ti.GetMetadata("OriginalItemSpec").Equals(name, StringComparison.OrdinalIgnoreCase));

            foreach (var satelliteAssembly in satelliteAssemblies)
            {
                ReadAssemblyResources(satelliteAssembly.ItemSpec, outFileOrDir);
            }
        }

        /// <summary>
        /// Checks the consistency of the CultureInfo and NeutralResourcesLanguageAttribute settings.
        /// </summary>
        /// <param name="name">Assembly's file name</param>
        /// <param name="assemblyName">AssemblyName of this assembly</param>
        /// <param name="culture">Assembly's CultureInfo</param>
        /// <param name="a">The actual Assembly</param>
        /// <param name="mainAssembly">Whether this is the main assembly</param>
        private NeutralResourcesLanguageAttribute CheckAssemblyCultureInfo(String name, AssemblyName assemblyName, CultureInfo culture, Assembly a, bool mainAssembly)
        {
            NeutralResourcesLanguageAttribute neutralResourcesLanguageAttribute = null;
            if (mainAssembly)
            {
                Object[] attrs = a.GetCustomAttributes(typeof(NeutralResourcesLanguageAttribute), false);
                if (attrs.Length != 0)
                {
                    neutralResourcesLanguageAttribute = (NeutralResourcesLanguageAttribute)attrs[0];
                    bool fallbackToSatellite = neutralResourcesLanguageAttribute.Location == UltimateResourceFallbackLocation.Satellite;
                    if (!fallbackToSatellite && neutralResourcesLanguageAttribute.Location != UltimateResourceFallbackLocation.MainAssembly)
                    {
                        _logger.LogWarningWithCodeFromResources(null, name, 0, 0, 0, 0, "GenerateResource.UnrecognizedUltimateResourceFallbackLocation", neutralResourcesLanguageAttribute.Location, name);
                    }
                    // This MSBuild task needs to not report an error for main assemblies that don't have managed resources.
                }
            }
            else
            {  // Satellite assembly, or a mal-formed main assembly
                // Additional error checking from ResView.
                if (!assemblyName.Name.EndsWith(".resources", StringComparison.OrdinalIgnoreCase))
                {
                    _logger.LogWarningWithCodeFromResources(null, name, 0, 0, 0, 0, "GenerateResource.SatelliteOrMalformedAssembly", name, culture.Name, assemblyName.Name);
                    return null;
                }
                Type[] types = a.GetTypes();
                if (types.Length > 0)
                {
                    _logger.LogWarningWithCodeFromResources("GenerateResource.SatelliteAssemblyContainsCode", name);
                }

                if (!ContainsProperlyNamedResourcesFiles(a, false))
                {
                    _logger.LogWarningWithCodeFromResources("GenerateResource.SatelliteAssemblyContainsNoResourcesFile", assemblyName.CultureInfo.Name);
                }
            }
            return neutralResourcesLanguageAttribute;
        }

        private static bool ContainsProperlyNamedResourcesFiles(Assembly a, bool mainAssembly)
        {
            String postfix = mainAssembly ? ".resources" : a.GetName().CultureInfo.Name + ".resources";
            foreach (String manifestResourceName in a.GetManifestResourceNames())
            {
                if (manifestResourceName.EndsWith(postfix, StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }
            }

            return false;
        }
#endif

        /// <summary>
        /// Write resources from the resources ArrayList to the specified output file
        /// </summary>
        /// <param name="reader">Reader information</param>
        /// <param name="filename">Output resources file</param>
        private void WriteResources(ReaderInfo reader, String filename)
        {
            Format format = GetFormat(filename);
            switch (format)
            {
                case Format.Text:
                    WriteTextResources(reader, filename);
                    break;

                case Format.XML:
#if FEATURE_RESXREADER_LIVEDESERIALIZATION
                    WriteResources(reader, new ResXResourceWriter(filename)); // closes writer for us
#else
                    _logger.LogError($"{format} not supported on .NET Core MSBuild");
#endif
                    break;

                case Format.Assembly:
                    _logger.LogErrorFromResources("GenerateResource.CannotWriteAssembly", filename);
                    break;

                case Format.Binary:
                    WriteBinaryResources(reader, filename);
                    break;

                default:
                    // We should never get here, we've already checked the format
                    Debug.Fail($"Unknown format {format}");
                    break;
            }
        }

        private void WriteBinaryResources(ReaderInfo reader, string filename)
        {
            if (_usePreserializedResources && HaveSystemResourcesExtensionsReference)
            {
                WriteResources(reader, new PreserializedResourceWriter(new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None))); // closes writer for us
                return;
            }

            try
            {
                WriteResources(reader, new ResourceWriter(new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None))); // closes writer for us
            }
            catch (PreserializedResourceWriterRequiredException)
            {
                if (!_usePreserializedResources)
                {
                    _logger.LogErrorWithCodeFromResources("GenerateResource.PreserializedResourcesRequiresProperty");
                }

                if (!HaveSystemResourcesExtensionsReference)
                {
                    _logger.LogErrorWithCodeFromResources("GenerateResource.PreserializedResourcesRequiresExtensions");
                }

                // one of the above should have been logged as we would have used preserialized writer otherwise.
                Debug.Assert(_logger.HasLoggedErrors);

                // We may have partially written some string resources to a file, then bailed out
                // because we encountered a non-string resource but don't meet the prereqs.
                RemoveCorruptedFile(filename);
            }
        }

        private bool? _haveSystemResourcesExtensionsReference;

        private bool HaveSystemResourcesExtensionsReference
        {
            get
            {
                if (_haveSystemResourcesExtensionsReference.HasValue)
                {
                    return _haveSystemResourcesExtensionsReference.Value;
                }

                if (_assemblyFiles == null)
                {
                    _haveSystemResourcesExtensionsReference = false;
                    return false;
                }

                PopulateAssemblyNames();

                foreach (AssemblyNameExtension assemblyName in _assemblyNames)
                {
                    if (assemblyName == null)
                    {
                        continue;
                    }

                    if (string.Equals(assemblyName.Name, "System.Resources.Extensions", StringComparison.OrdinalIgnoreCase))
                    {
                        _haveSystemResourcesExtensionsReference = true;
                        return true;
                    }
                }

                _haveSystemResourcesExtensionsReference = false;
                return false;
            }
        }

        /// <summary>
        /// Create a strongly typed resource class
        /// </summary>
        /// <param name="reader">Reader information</param>
        /// <param name="outFile">Output resource filename, for defaulting the class filename</param>
        /// <param name="inputFileName">Input resource filename, for error messages</param>
        /// <param name="sourceFile">The generated strongly typed filename</param>
        private void CreateStronglyTypedResources(ReaderInfo reader, String outFile, String inputFileName, out String sourceFile)
        {
            CodeDomProvider provider = null;
            try
            {
                if (!TryCreateCodeDomProvider(_logger, _stronglyTypedLanguage, out provider))
                {
                    sourceFile = null;
                    return;
                }


                // Default the class name if we need to
                if (_stronglyTypedClassName == null)
                {
                    _stronglyTypedClassName = Path.GetFileNameWithoutExtension(outFile);
                }

                // Default the filename if we need to
                if (_stronglyTypedFilename == null)
                {
                    _stronglyTypedFilename = GenerateDefaultStronglyTypedFilename(provider, outFile);
                }
                sourceFile = this.StronglyTypedFilename;

                _logger.LogMessageFromResources("GenerateResource.CreatingSTR", _stronglyTypedFilename);

                // Generate the STR class
                String[] errors;
                bool generateInternalClass = !_stronglyTypedClassIsPublic;
                // StronglyTypedResourcesNamespace can be null and this is ok.
                // If it is null then the default namespace (=stronglyTypedNamespace) is used.
                CodeCompileUnit ccu = StronglyTypedResourceBuilder.Create(
                        reader.resourcesHashTable,
                        _stronglyTypedClassName,
                        _stronglyTypedNamespace,
                        _stronglyTypedResourcesNamespace,
                        provider,
                        generateInternalClass,
                        out errors);

                CodeGeneratorOptions codeGenOptions = new CodeGeneratorOptions();
                using (TextWriter output = new StreamWriter(_stronglyTypedFilename))
                {
                    provider.GenerateCodeFromCompileUnit(ccu, output, codeGenOptions);
                }

                if (errors.Length > 0)
                {
                    _logger.LogErrorWithCodeFromResources("GenerateResource.ErrorFromCodeDom", inputFileName);
                    foreach (String error in errors)
                    {
                        _logger.LogErrorWithCodeFromResources("GenerateResource.CodeDomError", error);
                    }
                }
                else
                {
                    // No errors, and no exceptions - we presumably did create the STR class file
                    // and it should get added to FilesWritten. So set a flag to indicate this.
                    _stronglyTypedResourceSuccessfullyCreated = true;
                }
            }
            finally
            {
                provider?.Dispose();
            }
        }

        /// <summary>
        /// If no strongly typed resource class filename was specified, we come up with a default based on the
        /// input file name and the default language extension.
        /// </summary>
        /// <comments>
        /// Broken out here so it can be called from GenerateResource class.
        /// </comments>
        /// <param name="provider">A CodeDomProvider for the language</param>
        /// <param name="outputResourcesFile">Name of the output resources file</param>
        /// <returns>Filename for strongly typed resource class</returns>
        public static string GenerateDefaultStronglyTypedFilename(CodeDomProvider provider, string outputResourcesFile)
        {
            return Path.ChangeExtension(outputResourcesFile, provider.FileExtension);
        }

        /// <summary>
        /// Tries to create a CodeDom provider for the specified strongly typed language.  If successful, returns true,
        /// otherwise returns false.
        /// </summary>
        /// <comments>
        /// Broken out here so it can be called from GenerateResource class.
        /// Not a true "TryXXX" method, as it still throws if it encounters an exception it doesn't expect.
        /// </comments>
        /// <param name="logger">Logger helper.</param>
        /// <param name="stronglyTypedLanguage">The language to create a provider for.</param>
        /// <param name="provider">The provider in question, if one is successfully created.</param>
        /// <returns>True if the provider was successfully created, false otherwise.</returns>
        public static bool TryCreateCodeDomProvider(TaskLoggingHelper logger, string stronglyTypedLanguage, out CodeDomProvider provider)
        {
            provider = null;

            try
            {
                provider = CodeDomProvider.CreateProvider(stronglyTypedLanguage);
            }
            catch (Exception e) when
#if FEATURE_SYSTEM_CONFIGURATION
             (e is ConfigurationException || e is SecurityException)
#else
             (e is SystemException && e.GetType().Name == "ConfigurationErrorsException") // TODO: catch specific exception type once it is public https://github.com/dotnet/corefx/issues/40456
#endif
            {
                logger.LogErrorWithCodeFromResources("GenerateResource.STRCodeDomProviderFailed", stronglyTypedLanguage, e.Message);
                return false;
            }

            return provider != null;
        }

#if FEATURE_RESXREADER_LIVEDESERIALIZATION
        /// <summary>
        /// Read resources from an XML or binary format file
        /// </summary>
        /// <param name="readerInfo">Reader info</param>
        /// <param name="reader">Appropriate IResourceReader</param>
        /// <param name="fileName">Filename, for error messages</param>
        private void ReadResources(ReaderInfo readerInfo, IResourceReader reader, String fileName)
        {
            using (reader)
            {
                IDictionaryEnumerator resEnum = reader.GetEnumerator();
                while (resEnum.MoveNext())
                {
                    string name = (string)resEnum.Key;
                    object value = resEnum.Value;
                    AddResource(readerInfo, name, value, fileName);
                }
            }
        }
#endif // FEATURE_RESXREADER_LIVEDESERIALIZATION

        /// <summary>
        /// Read resources from a text format file.
        /// </summary>
        /// <param name="reader">Reader info.</param>
        /// <param name="fileName">Input resources filename.</param>
        private void ReadTextResources(ReaderInfo reader, String fileName)
        {
            // Check for byte order marks in the beginning of the input file, but
            // default to UTF-8.
            using var fs = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
            using (LineNumberStreamReader sr = new LineNumberStreamReader(fs, new UTF8Encoding(true), true))
            {
                StringBuilder name = new StringBuilder(255);
                StringBuilder value = new StringBuilder(2048);

                int ch = sr.Read();
                while (ch != -1)
                {
                    if (ch == '\n' || ch == '\r')
                    {
                        ch = sr.Read();
                        continue;
                    }

                    // Skip over commented lines or ones starting with whitespace.
                    // Support LocStudio INF format's comment char, ';'
                    if (ch == '#' || ch == '\t' || ch == ' ' || ch == ';')
                    {
                        // comment char (or blank line) - skip line.
                        sr.ReadLine();
                        ch = sr.Read();
                        continue;
                    }
                    // Note that in Beta of version 1 we recommended users should put a [strings]
                    // section in their file.  Now it's completely unnecessary and can
                    // only cause bugs.  We will not parse anything using '[' stuff now
                    // and we should give a warning about seeing [strings] stuff.
                    // In V1.1 or V2, we can rip this out completely, I hope.
                    if (ch == '[')
                    {
                        String skip = sr.ReadLine();
                        if (skip.Equals("strings]"))
                        {
                            _logger.LogWarningWithCodeFromResources(null, fileName, sr.LineNumber - 1, 1, 0, 0, "GenerateResource.ObsoleteStringsTag");
                        }
                        else
                        {
                            throw new TextFileException(_logger.FormatResourceString("GenerateResource.UnexpectedInfBracket", "[" + skip), fileName, sr.LineNumber - 1, 1);
                        }
                        ch = sr.Read();
                        continue;
                    }

                    // Read in name
                    name.Length = 0;
                    while (ch != '=')
                    {
                        if (ch == '\r' || ch == '\n')
                        {
                            throw new TextFileException(_logger.FormatResourceString("GenerateResource.NoEqualsInLine", name), fileName, sr.LineNumber, sr.LinePosition);
                        }

                        name.Append((char)ch);
                        ch = sr.Read();
                        if (ch == -1)
                        {
                            break;
                        }
                    }
                    if (name.Length == 0)
                    {
                        throw new TextFileException(_logger.FormatResourceString("GenerateResource.NoNameInLine"), fileName, sr.LineNumber, sr.LinePosition);
                    }

                    // For the INF file, we must allow a space on both sides of the equals
                    // sign.  Deal with it.
                    if (name[name.Length - 1] == ' ')
                    {
                        name.Length--;
                    }
                    ch = sr.Read(); // move past =

                    // If it exists, move past the first space after the equals sign.
                    if (ch == ' ')
                    {
                        ch = sr.Read();
                    }

                    // Read in value
                    value.Length = 0;

                    while (ch != -1)
                    {
                        // Did we read @"\r" or @"\n"?
                        bool quotedNewLine = false;
                        if (ch == '\\')
                        {
                            ch = sr.Read();
                            switch (ch)
                            {
                                case '\\':
                                    // nothing needed
                                    break;
                                case 'n':
                                    ch = '\n';
                                    quotedNewLine = true;
                                    break;
                                case 'r':
                                    ch = '\r';
                                    quotedNewLine = true;
                                    break;
                                case 't':
                                    ch = '\t';
                                    break;
                                case '"':
                                    ch = '\"';
                                    break;
                                case 'u':
                                    char[] hex = new char[4];
                                    int numChars = 4;
                                    int index = 0;
                                    while (numChars > 0)
                                    {
                                        int n = sr.Read(hex, index, numChars);
                                        if (n == 0)
                                        {
                                            throw new TextFileException(_logger.FormatResourceString("GenerateResource.InvalidEscape", name.ToString(), (char)ch), fileName, sr.LineNumber, sr.LinePosition);
                                        }

                                        index += n;
                                        numChars -= n;
                                    }
                                    try
                                    {
                                        ch = (char)UInt16.Parse(
#if NET
                                            hex,
#else
                                            new String(hex),
#endif
                                            NumberStyles.HexNumber, CultureInfo.CurrentCulture);
                                    }
                                    catch (FormatException)
                                    {
                                        // We know about this one...
                                        throw new TextFileException(_logger.FormatResourceString("GenerateResource.InvalidHexEscapeValue", name.ToString(), new String(hex)), fileName, sr.LineNumber, sr.LinePosition);
                                    }
                                    catch (OverflowException)
                                    {
                                        // We know about this one, too...
                                        throw new TextFileException(_logger.FormatResourceString("GenerateResource.InvalidHexEscapeValue", name.ToString(), new String(hex)), fileName, sr.LineNumber, sr.LinePosition);
                                    }
                                    quotedNewLine = (ch == '\n' || ch == '\r');
                                    break;

                                default:
                                    throw new TextFileException(_logger.FormatResourceString("GenerateResource.InvalidEscape", name.ToString(), (char)ch), fileName, sr.LineNumber, sr.LinePosition);
                            }
                        }

                        // Consume endline...
                        //   Endline can be \r\n or \n.  But do not treat a
                        //   quoted newline (ie, @"\r" or @"\n" in text) as a
                        //   real new line.  They aren't the end of a line.
                        if (!quotedNewLine)
                        {
                            if (ch == '\r')
                            {
                                ch = sr.Read();
                                if (ch == -1)
                                {
                                    break;
                                }
                                else if (ch == '\n')
                                {
                                    ch = sr.Read();
                                    break;
                                }
                            }
                            else if (ch == '\n')
                            {
                                ch = sr.Read();
                                break;
                            }
                        }

                        value.Append((char)ch);
                        ch = sr.Read();
                    }

                    // Note that value can be an empty string
                    AddResource(reader, name.ToString(), value.ToString(), fileName, sr.LineNumber, sr.LinePosition);
                }
            }
        }

        /// <summary>
        /// Write resources to an XML or binary format resources file.
        /// </summary>
        /// <remarks>Closes writer automatically</remarks>
        /// <param name="reader">Reader information</param>
        /// <param name="writer">Appropriate IResourceWriter</param>
        private void WriteResources(ReaderInfo reader,
            IResourceWriter writer)
        {
            Exception capturedException = null;
            try
            {
                foreach (IResource entry in reader.resources)
                {
                    entry.AddTo(writer);
                }
            }
            catch (Exception e)
            {
                capturedException = e; // Rethrow this after catching exceptions thrown by Close().
            }
            finally
            {
                if (capturedException == null)
                {
                    writer.Dispose(); // If this throws, exceptions will be caught upstream.
                }
                else
                {
                    // It doesn't hurt to call Close() twice. In the event of a full disk, we *need* to call Close() twice.
                    // In that case, the first time we catch an exception indicating that the XML written to disk is malformed,
                    // specifically an InvalidOperationException: "Token EndElement in state Error would result in an invalid XML document."
                    try { writer.Dispose(); }
                    catch (Exception) { } // We aggressively catch all exception types since we already have one we will throw.

                    // The second time we catch the out of disk space exception.
                    try { writer.Dispose(); }
                    catch (Exception) { } // We aggressively catch all exception types since we already have one we will throw.
                    throw capturedException; // In the event of a full disk, this is an out of disk space IOException.
                }
            }
        }

        /// <summary>
        /// Write resources to a text format resources file
        /// </summary>
        /// <param name="reader">Reader information</param>
        /// <param name="fileName">Output resources file</param>
        private void WriteTextResources(ReaderInfo reader, String fileName)
        {
            using (StreamWriter writer = FileUtilities.OpenWrite(fileName, false, Encoding.UTF8))
            {
                foreach (IResource resource in reader.resources)
                {
                    LiveObjectResource entry = resource as LiveObjectResource;

                    String key = entry?.Name;
                    Object v = entry?.Value;
                    String value = v as String;
                    if (value == null)
                    {
                        _logger.LogErrorWithCodeFromResources(null, fileName, 0, 0, 0, 0, "GenerateResource.OnlyStringsSupported", key, v?.GetType().FullName);
                    }
                    else
                    {
                        // Escape any special characters in the String.
                        value = value.Replace("\\", "\\\\");
                        value = value.Replace("\n", "\\n");
                        value = value.Replace("\r", "\\r");
                        value = value.Replace("\t", "\\t");

                        writer.WriteLine("{0}={1}", key, value);
                    }
                }
            }
        }

        /// <summary>
        /// Add a resource from a text file to the internal data structures
        /// </summary>
        /// <param name="reader">Reader information</param>
        /// <param name="name">Resource name</param>
        /// <param name="value">Resource value</param>
        /// <param name="inputFileName">Input file for messages</param>
        /// <param name="lineNumber">Line number for messages</param>
        /// <param name="linePosition">Column number for messages</param>
        private void AddResource(ReaderInfo reader, string name, object value, String inputFileName, int lineNumber, int linePosition)
        {
            LiveObjectResource entry = new LiveObjectResource(name, value);

            AddResource(reader, entry, inputFileName, lineNumber, linePosition);
        }

        private void AddResource(ReaderInfo reader, IResource entry, String inputFileName, int lineNumber, int linePosition)
        {
            if (reader.resourcesHashTable.ContainsKey(entry.Name))
            {
                _logger.LogWarningWithCodeFromResources(null, inputFileName, lineNumber, linePosition, 0, 0, "GenerateResource.DuplicateResourceName", entry.Name);
                return;
            }

            reader.resources.Add(entry);
            reader.resourcesHashTable.Add(entry.Name, entry);
        }

        /// <summary>
        /// Add a resource from an XML or binary format file to the internal data structures
        /// </summary>
        /// <param name="reader">Reader information.</param>
        /// <param name="name">Resource name.</param>
        /// <param name="value">Resource value.</param>
        /// <param name="inputFileName">Input file for messages.</param>
        private void AddResource(ReaderInfo reader, string name, object value, String inputFileName)
        {
            AddResource(reader, name, value, inputFileName, 0, 0);
        }

        internal sealed class ReaderInfo
        {
            public String outputFileName;
            public String cultureName;
            // We use a list to preserve the resource ordering (primarily for easier testing),
            // but also use a hash table to check for duplicate names.
            public List<IResource> resources;
            public Dictionary<string, IResource> resourcesHashTable;
            public String assemblySimpleName;  // The main assembly's simple name (ie, no .resources)
            public bool fromNeutralResources;  // Was this from the main assembly (or if the NRLA specified fallback to satellite, that satellite?)

            public ReaderInfo()
            {
                resources = new List<IResource>();
                resourcesHashTable = new Dictionary<string, IResource>(StringComparer.OrdinalIgnoreCase);
            }
        }

        /// <summary>
        /// Custom StreamReader that provides detailed position information,
        /// used when reading text format resources
        /// </summary>
        internal sealed class LineNumberStreamReader : StreamReader
        {
            // Line numbers start from 1, as well as line position.
            // For better error reporting, set line number to 1 and col to 0.
            private int _lineNumber;
            private int _col;

            internal LineNumberStreamReader(Stream fileStream, Encoding encoding, bool detectEncoding)
                : base(fileStream, encoding, detectEncoding)
            {
                _lineNumber = 1;
                _col = 0;
            }

            internal LineNumberStreamReader(Stream stream)
                : base(stream)
            {
                _lineNumber = 1;
                _col = 0;
            }

            public override int Read()
            {
                int ch = base.Read();
                if (ch != -1)
                {
                    _col++;
                    if (ch == '\n')
                    {
                        _lineNumber++;
                        _col = 0;
                    }
                }
                return ch;
            }

            public override int Read([In, Out] char[] chars, int index, int count)
            {
                int r = base.Read(chars, index, count);
                for (int i = 0; i < r; i++)
                {
                    if (chars[i + index] == '\n')
                    {
                        _lineNumber++;
                        _col = 0;
                    }
                    else
                    {
                        _col++;
                    }
                }
                return r;
            }

            public override String ReadLine()
            {
                String s = base.ReadLine();
                if (s != null)
                {
                    _lineNumber++;
                    _col = 0;
                }
                return s;
            }

            public override String ReadToEnd()
            {
                throw new NotImplementedException("NYI");
            }

            internal int LineNumber
            {
                get { return _lineNumber; }
            }

            internal int LinePosition
            {
                get { return _col; }
            }
        }

        /// <summary>
        /// For flow of control and passing sufficient error context back
        /// from ReadTextResources
        /// </summary>
        [Serializable]
        internal sealed class TextFileException : Exception
        {
            private String fileName;
            private int lineNumber;
            private int column;

#if NET8_0_OR_GREATER
            [Obsolete(DiagnosticId = "SYSLIB0051")]
#endif
            private TextFileException(SerializationInfo info, StreamingContext context)
                : base(info, context)
            {
            }

            internal TextFileException(String message, String fileName, int lineNumber, int linePosition)
                : base(message)
            {
                this.fileName = fileName;
                this.lineNumber = lineNumber;
                column = linePosition;
            }

            internal String FileName
            {
                get { return fileName; }
            }

            internal int LineNumber
            {
                get { return lineNumber; }
            }

            internal int LinePosition
            {
                get { return column; }
            }
        }
        #endregion // Code from ResGen.EXE
    }

#if !FEATURE_ASSEMBLYLOADCONTEXT
    /// <summary>
    /// This implemention of ITypeResolutionService is passed into the ResxResourceReader
    /// class, which calls back into the methods on this class in order to resolve types
    /// and assemblies that are referenced inside of the .RESX.
    /// </summary>
    internal class AssemblyNamesTypeResolutionService : ITypeResolutionService
    {
        private Hashtable _cachedAssemblies;
        private ITaskItem[] _referencePaths;
        private Hashtable _cachedTypes = new Hashtable();

        /// <summary>
        /// Constructor, initialized with the set of resolved reference paths passed
        /// into the GenerateResource task.
        /// </summary>
        /// <param name="referencePaths"></param>
        internal AssemblyNamesTypeResolutionService(ITaskItem[] referencePaths)
        {
            _referencePaths = referencePaths;
        }

        /// <summary>
        /// Not implemented.  Not called by the ResxResourceReader.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public Assembly GetAssembly(AssemblyName name)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Not implemented.  Not called by the ResxResourceReader.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="throwOnError"></param>
        /// <returns></returns>
        public Assembly GetAssembly(AssemblyName name, bool throwOnError)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Given a path to an assembly, load the assembly if it's not already loaded.
        /// </summary>
        /// <param name="pathToAssembly"></param>
        /// <param name="throwOnError"></param>
        /// <returns></returns>
        private Assembly GetAssemblyByPath(string pathToAssembly, bool throwOnError)
        {
            if (_cachedAssemblies == null)
            {
                _cachedAssemblies = new Hashtable();
            }

            if (!_cachedAssemblies.Contains(pathToAssembly))
            {
                try
                {
                    _cachedAssemblies[pathToAssembly] = Assembly.UnsafeLoadFrom(pathToAssembly);
                }
                catch when (!throwOnError)
                {
                }
            }

            return (Assembly)_cachedAssemblies[pathToAssembly];
        }

        /// <summary>
        /// Not implemented.  Not called by the ResxResourceReader.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public string GetPathOfAssembly(AssemblyName name)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Returns the type with the specified name.  Searches for the type in all
        /// of the assemblies passed into the References parameter of the GenerateResource
        /// task.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public Type GetType(string name)
        {
            return GetType(name, true);
        }

        /// <summary>
        /// Returns the type with the specified name.  Searches for the type in all
        /// of the assemblies passed into the References parameter of the GenerateResource
        /// task.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="throwOnError"></param>
        /// <returns></returns>
        public Type GetType(string name, bool throwOnError)
        {
            return GetType(name, throwOnError, false);
        }

        /// <summary>
        /// Returns the type with the specified name.  Searches for the type in all
        /// of the assemblies passed into the References parameter of the GenerateResource
        /// task.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="throwOnError"></param>
        /// <param name="ignoreCase"></param>
        /// <returns></returns>
        public Type GetType(string name, bool throwOnError, bool ignoreCase)
        {
            Type resultFromCache = (Type)_cachedTypes[name];

            if (!_cachedTypes.Contains(name))
            {
                // first try to resolve in the GAC
                Type result = Type.GetType(name, false, ignoreCase);

                // did not find it in the GAC, check each assembly
                if ((result == null) && (_referencePaths != null))
                {
                    foreach (ITaskItem referencePath in _referencePaths)
                    {
                        Assembly a = this.GetAssemblyByPath(referencePath.ItemSpec, throwOnError);
                        if (a != null)
                        {
                            result = a.GetType(name, false, ignoreCase);
                            if (result == null)
                            {
                                int indexOfComma = name.IndexOf(',');
                                if (indexOfComma != -1)
                                {
                                    string shortName = name.Substring(0, indexOfComma);
                                    result = a.GetType(shortName, false, ignoreCase);
                                }
                            }

                            if (result != null)
                            {
                                break;
                            }
                        }
                    }
                }

                if (result == null && throwOnError)
                {
                    ErrorUtilities.ThrowArgument("GenerateResource.CouldNotLoadType", name);
                }

                _cachedTypes[name] = result;
                resultFromCache = result;
            }

            return resultFromCache;
        }

        /// <summary>
        /// Not implemented.  Not called by the ResxResourceReader.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public void ReferenceAssembly(AssemblyName name)
        {
            throw new NotSupportedException();
        }
    }
#endif
}
