/// <summary>
        /// Find all the build products created by compiling the given project file
        /// </summary>
        /// <param name="ProjectFile">Initial project file to read. All referenced projects will also be read.</param>
        /// <param name="InitialProperties">Mapping of property name to value</param>
        /// <param name="OutBuildProducts">Receives a set of build products on success</param>
        /// <returns>True if the build products were found, false otherwise.</returns>
        static bool FindBuildProducts(HashSet <FileReference> ProjectFiles, Dictionary <string, string> InitialProperties, out HashSet <FileReference> OutBuildProducts)
        {
            // Read all the project information into a dictionary
            Dictionary <FileReference, MsBuildProjectInfo> FileToProjectInfo = new Dictionary <FileReference, MsBuildProjectInfo>();

            foreach (FileReference ProjectFile in ProjectFiles)
            {
                if (!ReadProjectsRecursively(ProjectFile, InitialProperties, FileToProjectInfo))
                {
                    OutBuildProducts = null;
                    return(false);
                }
            }

            // Find all the build products
            HashSet <FileReference> BuildProducts = new HashSet <FileReference>();

            foreach (KeyValuePair <FileReference, MsBuildProjectInfo> Pair in FileToProjectInfo)
            {
                MsBuildProjectInfo ProjectInfo = Pair.Value;

                // Add the standard build products
                DirectoryReference OutputDir = ProjectInfo.GetOutputDir(Pair.Key.Directory);
                ProjectInfo.AddBuildProducts(OutputDir, BuildProducts);

                // Add the referenced assemblies
                foreach (FileReference OtherAssembly in ProjectInfo.References.Where(x => x.Value).Select(x => x.Key))
                {
                    FileReference OutputFile = FileReference.Combine(OutputDir, OtherAssembly.GetFileName());
                    BuildProducts.Add(OutputFile);

                    FileReference SymbolFile = OtherAssembly.ChangeExtension(".pdb");
                    if (SymbolFile.Exists())
                    {
                        BuildProducts.Add(OutputFile.ChangeExtension(".pdb"));
                    }
                }

                // Add build products from all the referenced projects. MSBuild only copy the directly referenced build products, not recursive references or other assemblies.
                foreach (MsBuildProjectInfo OtherProjectInfo in ProjectInfo.ProjectReferences.Where(x => x.Value).Select(x => FileToProjectInfo[x.Key]))
                {
                    OtherProjectInfo.AddBuildProducts(OutputDir, BuildProducts);
                }
            }

            // Update the output set
            OutBuildProducts = BuildProducts;
            return(true);
        }
        /// <summary>
        /// Attempts to read project information for the given file.
        /// </summary>
        /// <param name="File">The project file to read</param>
        /// <param name="Properties">Initial set of property values</param>
        /// <param name="OutProjectInfo">If successful, the parsed project info</param>
        /// <returns>True if the project was read successfully, false otherwise</returns>
        public static bool TryRead(FileReference File, Dictionary <string, string> Properties, out MsBuildProjectInfo OutProjectInfo)
        {
            // Read the project file
            XmlDocument Document = new XmlDocument();

            Document.Load(File.FullName);

            // Check the root element is the right type
//			HashSet<FileReference> ProjectBuildProducts = new HashSet<FileReference>();
            if (Document.DocumentElement.Name != "Project")
            {
                OutProjectInfo = null;
                return(false);
            }

            // Parse the basic structure of the document, updating properties and recursing into other referenced projects as we go
            MsBuildProjectInfo ProjectInfo = new MsBuildProjectInfo(Properties);

            foreach (XmlElement Element in Document.DocumentElement.ChildNodes.OfType <XmlElement>())
            {
                switch (Element.Name)
                {
                case "PropertyGroup":
                    if (EvaluateCondition(Element, ProjectInfo.Properties))
                    {
                        ParsePropertyGroup(Element, ProjectInfo.Properties);
                    }
                    break;

                case "ItemGroup":
                    if (EvaluateCondition(Element, ProjectInfo.Properties))
                    {
                        ParseItemGroup(File.Directory, Element, ProjectInfo);
                    }
                    break;
                }
            }

            // Return the complete project
            OutProjectInfo = ProjectInfo;
            return(true);
        }
        /// <summary>
        /// Read a project file, plus all the project files it references.
        /// </summary>
        /// <param name="File">Project file to read</param>
        /// <param name="InitialProperties">Mapping of property name to value for the initial project</param>
        /// <param name="FileToProjectInfo"></param>
        /// <returns>True if the projects were read correctly, false (and prints an error to the log) if not</returns>
        static bool ReadProjectsRecursively(FileReference File, Dictionary <string, string> InitialProperties, Dictionary <FileReference, MsBuildProjectInfo> FileToProjectInfo)
        {
            // Early out if we've already read this project, return succes
            if (FileToProjectInfo.ContainsKey(File))
            {
                return(true);
            }

            // Try to read this project
            MsBuildProjectInfo ProjectInfo;

            if (!MsBuildProjectInfo.TryRead(File, InitialProperties, out ProjectInfo))
            {
                CommandUtils.LogError("Couldn't read project '{0}'", File.FullName);
                return(false);
            }

            // Add it to the project lookup, and try to read all the projects it references
            FileToProjectInfo.Add(File, ProjectInfo);
            return(ProjectInfo.ProjectReferences.Keys.All(x => ReadProjectsRecursively(x, new Dictionary <string, string>(), FileToProjectInfo)));
        }
        /// <summary>
        /// Parses an 'ItemGroup' element.
        /// </summary>
        /// <param name="BaseDirectory">Base directory to resolve relative paths against</param>
        /// <param name="ParentElement">The parent 'ItemGroup' element</param>
        /// <param name="ProjectInfo">Project info object to be updated</param>
        static void ParseItemGroup(DirectoryReference BaseDirectory, XmlElement ParentElement, MsBuildProjectInfo ProjectInfo)
        {
            // Parse any external assembly references
            foreach (XmlElement ItemElement in ParentElement.ChildNodes.OfType <XmlElement>())
            {
                switch (ItemElement.Name)
                {
                case "Reference":
                    // Reference to an external assembly
                    if (EvaluateCondition(ItemElement, ProjectInfo.Properties))
                    {
                        ParseReference(BaseDirectory, ItemElement, ProjectInfo.References);
                    }
                    break;

                case "ProjectReference":
                    // Reference to another project
                    if (EvaluateCondition(ItemElement, ProjectInfo.Properties))
                    {
                        ParseProjectReference(BaseDirectory, ItemElement, ProjectInfo.ProjectReferences);
                    }
                    break;
                }
            }
        }