|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Xsl;
using Microsoft.Build.Shared;
using Microsoft.Build.Shared.FileSystem;
#nullable disable
namespace Microsoft.Build.Tasks.Deployment.Bootstrapper
{
/// <summary>
/// This class is the top-level object for the bootstrapper system.
/// </summary>
[ComVisible(true)]
[Guid("1D9FE38A-0226-4b95-9C6B-6DFFA2236270")]
[ClassInterface(ClassInterfaceType.None)]
[SupportedOSPlatform("windows")]
public class BootstrapperBuilder : IBootstrapperBuilder
{
private static readonly bool s_logging = !String.IsNullOrEmpty(Environment.GetEnvironmentVariable("VSPLOG"));
private static readonly string s_logPath = GetLogPath();
private string _path;
private XmlDocument _document;
private XmlNamespaceManager _xmlNamespaceManager;
private readonly ProductCollection _products = new ProductCollection();
private readonly Dictionary<string, XmlNode> _cultures = new Dictionary<string, XmlNode>(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, ProductValidationResults> _validationResults = new Dictionary<string, ProductValidationResults>(StringComparer.Ordinal);
private BuildResults _results;
private BuildResults _loopDependenciesWarnings;
private bool _fInitialized;
private const string SETUP_EXE = "setup.exe";
private const string SETUP_BIN = "setup.bin";
private const string SETUP_RESOURCES_FILE = "setup.xml";
private const string ENGINE_PATH = "Engine"; // relative to bootstrapper path
private const string SCHEMA_PATH = "Schemas"; // relative to bootstrapper path
private const string PACKAGE_PATH = "Packages"; // relative to bootstrapper path
private const string RESOURCES_PATH = "";
private const string BOOTSTRAPPER_NAMESPACE = "http://schemas.microsoft.com/developer/2004/01/bootstrapper";
private const string BOOTSTRAPPER_PREFIX = "bootstrapper";
private const string ROOT_MANIFEST_FILE = "product.xml";
private const string CHILD_MANIFEST_FILE = "package.xml";
private const string MANIFEST_FILE_SCHEMA = "package.xsd";
private const string CONFIG_TRANSFORM = "xmltoconfig.xsl";
private const string EULA_ATTRIBUTE = "LicenseAgreement";
private const string HOMESITE_ATTRIBUTE = "HomeSite";
private const string PUBLICKEY_ATTRIBUTE = "PublicKey";
private const string URLNAME_ATTRIBUTE = "UrlName";
private const string HASH_ATTRIBUTE = "Hash";
private const int MESSAGE_TABLE = 43;
private const int RESOURCE_TABLE = 45;
/// <summary>
/// Creates a new BootstrapperBuilder.
/// </summary>
public BootstrapperBuilder()
{
_path = Util.DefaultPath;
}
/// <summary>
/// Creates a new BootstrapperBuilder.
/// </summary>
/// <param name="visualStudioVersion">The version of Visual Studio that is used to build this bootstrapper.</param>
public BootstrapperBuilder(string visualStudioVersion)
{
_path = Util.GetDefaultPath(visualStudioVersion);
}
#region IBootstrapperBuilder Members
/// <summary>
/// Specifies the location of the required bootstrapper files.
/// </summary>
/// <value>Path to bootstrapper files.</value>
public string Path
{
get => _path;
set
{
if (!_fInitialized || !string.Equals(_path, value, StringComparison.OrdinalIgnoreCase))
{
_path = value;
Refresh();
}
}
}
/// <summary>
/// Returns all products available at the current bootstrapper Path
/// </summary>
public ProductCollection Products
{
get
{
if (!_fInitialized)
{
Refresh();
}
return _products;
}
}
/// <summary>
/// Generates a bootstrapper based on the specified settings.
/// </summary>
/// <param name="settings">The properties used to build this bootstrapper.</param>
/// <returns>The results of the bootstrapper generation</returns>
public BuildResults Build(BuildSettings settings)
{
_results = new BuildResults();
try
{
if (settings.ApplicationFile == null && (settings.ProductBuilders == null || settings.ProductBuilders.Count == 0))
{
_results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.InvalidInput"));
return _results;
}
if (String.IsNullOrEmpty(settings.OutputPath))
{
_results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.NoOutputPath"));
return _results;
}
if (!_fInitialized)
{
Refresh();
}
if (String.IsNullOrEmpty(settings.Culture))
{
settings.Culture = MapLCIDToCultureName(settings.LCID);
}
if (String.IsNullOrEmpty(settings.FallbackCulture))
{
settings.FallbackCulture = MapLCIDToCultureName(settings.FallbackLCID);
}
if (String.IsNullOrEmpty(settings.Culture) || settings.Culture == "*")
{
settings.Culture = settings.FallbackCulture;
}
AddBuiltProducts(settings);
var componentFilesCopied = new List<string>();
// Copy setup.bin to the output directory
string strOutputExe = System.IO.Path.Combine(settings.OutputPath, SETUP_EXE);
if (!CopySetupToOutputDirectory(settings, strOutputExe))
{
// Appropriate messages should have been stuffed into the results already
return _results;
}
var resourceUpdater = new ResourceUpdater();
// Build up the String table for setup.exe
if (!BuildResources(settings, resourceUpdater))
{
// Appropriate messages should have been stuffed into the results already
return _results;
}
AddStringResourceForUrl(resourceUpdater, "BASEURL", settings.ApplicationUrl, "ApplicationUrl");
AddStringResourceForUrl(resourceUpdater, "COMPONENTSURL", settings.ComponentsUrl, "ComponentsUrl");
AddStringResourceForUrl(resourceUpdater, "SUPPORTURL", settings.SupportUrl, "SupportUrl");
if (settings.ComponentsLocation == ComponentsLocation.HomeSite)
{
resourceUpdater.AddStringResource(40, "HOMESITE", true.ToString());
}
XmlElement configElement = _document.CreateElement("Configuration");
XmlElement applicationElement = CreateApplicationElement(configElement, settings);
if (applicationElement != null)
{
configElement.AppendChild(applicationElement);
}
// Key: File hash, Value: A DictionaryEntry whose Key is "EULAx" and value is a
// fully qualified path to a eula. It can be any eula that matches the hash.
var eulas = new Dictionary<string, KeyValuePair<string, string>>(StringComparer.Ordinal);
// Copy package files, add each Package config info to the config file
if (!BuildPackages(settings, configElement, resourceUpdater, componentFilesCopied, eulas))
{
return _results;
}
// Transform the configuration xml into something the bootstrapper will understand
DumpXmlToFile(configElement, "bootstrapper.cfg.xml");
string config = XmlToConfigurationFile(configElement);
resourceUpdater.AddStringResource(41, "SETUPCFG", config);
DumpStringToFile(config, "bootstrapper.cfg", false);
// Put eulas in the resource stream
foreach (KeyValuePair<string, string> de in eulas.Values)
{
string data;
var fi = new FileInfo(de.Value);
using (FileStream fs = fi.OpenRead())
{
using var sr = new StreamReader(fs);
data = sr.ReadToEnd();
}
resourceUpdater.AddStringResource(44, de.Key, data);
}
resourceUpdater.AddStringResource(44, "COUNT", eulas.Count.ToString(CultureInfo.InvariantCulture));
if (!resourceUpdater.UpdateResources(strOutputExe, _results))
{
return _results;
}
_results.SetKeyFile(strOutputExe);
string[] componentFiles = new string[componentFilesCopied.Count];
componentFilesCopied.CopyTo(componentFiles);
_results.AddComponentFiles(componentFiles);
_results.BuildSucceeded();
}
catch (Exception ex)
{
_results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.General", ex.Message));
}
return _results;
}
private static void Merge(Dictionary<string, Product> output, Dictionary<string, Product> input)
{
foreach (Product product in input.Values)
{
AddProduct(output, product);
}
}
private static void AddProduct(Dictionary<string, Product> output, Product product)
{
if (!output.ContainsKey(product.ProductCode.ToLowerInvariant()))
{
output.Add(product.ProductCode.ToLowerInvariant(), product);
}
}
private void AddBuiltProducts(BuildSettings settings)
{
var builtProducts = new Dictionary<string, ProductBuilder>();
var productsAndIncludes = new Dictionary<string, Product>();
if (_loopDependenciesWarnings?.Messages != null)
{
foreach (BuildMessage message in _loopDependenciesWarnings.Messages)
{
_results.AddMessage(message);
}
}
foreach (ProductBuilder builder in settings.ProductBuilders)
{
builtProducts.Add(builder.Product.ProductCode.ToLowerInvariant(), builder);
Merge(productsAndIncludes, GetIncludedProducts(builder.Product));
AddProduct(productsAndIncludes, builder.Product);
}
foreach (ProductBuilder builder in settings.ProductBuilders)
{
Dictionary<string, Product> includes = GetIncludedProducts(builder.Product);
foreach (Product p in includes.Values)
{
if (builtProducts.ContainsKey(p.ProductCode.ToLowerInvariant()))
{
_results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.IncludedProductIncluded", builder.Name, p.Name));
}
}
foreach (List<Product> productDependency in builder.Product.Dependencies)
{
bool foundDependency = false;
foreach (Product p in productDependency)
{
if (productsAndIncludes.ContainsKey(p.ProductCode.ToLowerInvariant()))
{
foundDependency = true;
break;
}
}
if (!foundDependency)
{
if (productDependency.Count == 1)
{
_results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.MissingDependency", productDependency[0].Name, builder.Name));
}
else
{
StringBuilder missingProductCodes = new StringBuilder();
foreach (Product product in productDependency)
{
missingProductCodes.Append(product.Name);
missingProductCodes.Append(", ");
}
string productCodes = missingProductCodes.ToString();
productCodes = productCodes.Substring(0, productCodes.Length - 2);
_results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.MissingDependencyMultiple", productCodes, builder.Name));
}
}
}
foreach (List<string> missingDependecies in builder.Product.MissingDependencies)
{
if (missingDependecies.Count == 1)
{
_results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.DependencyNotFound", builder.Name, missingDependecies[0]));
}
else
{
var missingProductCodes = new StringBuilder();
foreach (string productCode in missingDependecies)
{
missingProductCodes.Append(productCode);
missingProductCodes.Append(", ");
}
string productCodes = missingProductCodes.ToString();
productCodes = productCodes.Substring(0, productCodes.Length - 2);
_results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.MultipleDependeciesNotFound", builder.Name, productCodes));
}
}
}
}
private bool CopySetupToOutputDirectory(BuildSettings settings, string strOutputExe)
{
string bootstrapperPath = BootstrapperPath;
string setupSourceFile = System.IO.Path.Combine(bootstrapperPath, SETUP_BIN);
if (!FileSystems.Default.FileExists(setupSourceFile))
{
_results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.MissingSetupBin", SETUP_BIN, bootstrapperPath));
return false;
}
try
{
EnsureFolderExists(settings.OutputPath);
File.Copy(setupSourceFile, strOutputExe, true);
ClearReadOnlyAttribute(strOutputExe);
}
catch (IOException ex)
{
_results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.CopyError", setupSourceFile, strOutputExe, ex.Message));
return false;
}
catch (UnauthorizedAccessException ex)
{
_results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.CopyError", setupSourceFile, strOutputExe, ex.Message));
return false;
}
catch (ArgumentException ex)
{
_results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.CopyError", setupSourceFile, strOutputExe, ex.Message));
return false;
}
catch (NotSupportedException ex)
{
_results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.CopyError", setupSourceFile, strOutputExe, ex.Message));
return false;
}
return true;
}
private void AddStringResourceForUrl(ResourceUpdater resourceUpdater, string name, string url, string nameToUseInLog)
{
if (!String.IsNullOrEmpty(url))
{
resourceUpdater.AddStringResource(40, name, url);
if (!Util.IsWebUrl(url) && !Util.IsUncPath(url))
{
_results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.InvalidUrl", nameToUseInLog, url));
}
}
}
#endregion
/// <summary>
/// Returns the directories bootstrapper component files would be copied to when built given the specified settings
/// </summary>
/// <param name="productCodes">The productCodes of the selected components</param>
/// <param name="culture">The culture used to build the bootstrapper</param>
/// <param name="fallbackCulture">The fallback culture used to build the bootstrapper</param>
/// <param name="componentsLocation">How the bootstrapper would package the selected components</param>
public string[] GetOutputFolders(string[] productCodes, string culture, string fallbackCulture, ComponentsLocation componentsLocation)
{
if (!_fInitialized)
{
Refresh();
}
var folders = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var settings = new BuildSettings();
string invariantPath = PackagePath.ToLowerInvariant();
invariantPath = Util.AddTrailingChar(invariantPath, System.IO.Path.DirectorySeparatorChar);
settings.CopyComponents = false;
settings.Culture = culture;
settings.FallbackCulture = fallbackCulture;
settings.ComponentsLocation = componentsLocation;
if (String.IsNullOrEmpty(settings.Culture) || settings.Culture == "*")
{
settings.Culture = settings.FallbackCulture;
}
foreach (string productCode in productCodes)
{
Product product = Products.Product(productCode);
if (product != null)
{
settings.ProductBuilders.Add(product.ProductBuilder);
}
}
var files = new List<string>();
BuildPackages(settings, null, null, files, null);
List<string> packagePaths = new List<string>() { invariantPath };
packagePaths.AddRange(Util.AdditionalPackagePaths.Select(p => Util.AddTrailingChar(p.ToLowerInvariant(), System.IO.Path.DirectorySeparatorChar)));
foreach (string file in files)
{
string folder = System.IO.Path.GetDirectoryName(file);
foreach (string packagePath in packagePaths)
{
if (folder.Length >= packagePath.Length && folder.Substring(0, packagePath.Length).ToLowerInvariant().CompareTo(packagePath) == 0)
{
string relPath = folder.Substring(packagePath.Length);
if (!folders.Contains(relPath))
{
folders.Add(relPath);
}
break;
}
}
}
return folders.ToArray();
}
internal bool ContainsCulture(string culture)
{
if (!_fInitialized)
{
Refresh();
}
return _cultures.ContainsKey(culture);
}
internal string[] Cultures
{
get
{
if (!_fInitialized)
{
Refresh();
}
List<string> list = _cultures.Values.Select(v => v.ToString()).ToList();
list.Sort();
return list.ToArray();
}
}
internal bool Validate { get; set; } = true;
private string BootstrapperPath => System.IO.Path.Combine(Path, ENGINE_PATH);
private string PackagePath => System.IO.Path.Combine(Path, PACKAGE_PATH);
private string SchemaPath => System.IO.Path.Combine(Path, SCHEMA_PATH);
private void Refresh()
{
RefreshResources();
RefreshProducts();
_fInitialized = true;
if (s_logging)
{
StringBuilder productsOrder = new StringBuilder();
foreach (Product p in Products)
{
productsOrder.Append(p.ProductCode).Append(Environment.NewLine);
}
DumpStringToFile(productsOrder.ToString(), "BootstrapperInstallOrder.txt", false);
}
}
private void RefreshResources()
{
string startDirectory = System.IO.Path.Combine(BootstrapperPath, RESOURCES_PATH);
_cultures.Clear();
if (FileSystems.Default.DirectoryExists(startDirectory))
{
foreach (string subDirectory in Directory.GetDirectories(startDirectory))
{
string resourceDirectory = System.IO.Path.Combine(startDirectory, subDirectory);
string resourceFilePath = System.IO.Path.Combine(resourceDirectory, SETUP_RESOURCES_FILE);
if (FileSystems.Default.FileExists(resourceFilePath))
{
var resourceDoc = new XmlDocument();
try
{
var xrs = new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore, CloseInput = true };
FileStream fs = File.OpenRead(resourceFilePath);
using (var xr = XmlReader.Create(fs, xrs))
{
resourceDoc.Load(xr);
}
}
catch (XmlException ex)
{
// UNDONE: Log exception due to bad resource file
Debug.Fail(ex.Message);
continue;
}
XmlNode rootNode = resourceDoc.SelectSingleNode("Resources");
XmlAttribute cultureAttribute = (XmlAttribute)rootNode?.Attributes.GetNamedItem("Culture");
if (cultureAttribute != null)
{
XmlNode stringsNode = rootNode.SelectSingleNode("Strings");
XmlNode stringNode = stringsNode?.SelectSingleNode(string.Format(CultureInfo.InvariantCulture, "String[@Name='{0}']", cultureAttribute.Value));
if (stringNode != null)
{
string culture = stringNode.InnerText;
XmlNode resourcesNode = rootNode.OwnerDocument.ImportNode(rootNode, true);
resourcesNode.Attributes.RemoveNamedItem("Culture");
var newAttribute = (XmlAttribute)rootNode.OwnerDocument.ImportNode(cultureAttribute, false);
newAttribute.Value = stringNode.InnerText;
resourcesNode.Attributes.Append(newAttribute);
if (!_cultures.ContainsKey(culture))
{
_cultures.Add(culture, resourcesNode);
}
else
{
Debug.Fail("Already found resources for culture " + stringNode.InnerText);
}
}
}
}
}
}
}
private void RefreshProducts()
{
_products.Clear();
_validationResults.Clear();
_document = new XmlDocument();
_xmlNamespaceManager = new XmlNamespaceManager(_document.NameTable);
_xmlNamespaceManager.AddNamespace(BOOTSTRAPPER_PREFIX, BOOTSTRAPPER_NAMESPACE);
XmlElement rootElement = _document.CreateElement("Products", BOOTSTRAPPER_NAMESPACE);
List<string> packagePaths = new List<string>() { PackagePath };
packagePaths.AddRange(Util.AdditionalPackagePaths);
foreach (string packagePath in packagePaths)
{
if (FileSystems.Default.DirectoryExists(packagePath))
{
foreach (string strSubDirectory in Directory.GetDirectories(packagePath))
{
int nStartIndex = packagePath.Length;
if ((strSubDirectory.ToCharArray())[nStartIndex] == System.IO.Path.DirectorySeparatorChar)
{
nStartIndex++;
}
ExploreDirectory(strSubDirectory.Substring(nStartIndex), rootElement, packagePath);
}
}
}
_document.AppendChild(rootElement);
var availableProducts = new Dictionary<string, Product>(StringComparer.Ordinal);
// A second copy of all the project which will get destroyed during the generation of the build order
var buildQueue = new Dictionary<string, Product>(StringComparer.Ordinal);
XmlNodeList productsFound = rootElement.SelectNodes(BOOTSTRAPPER_PREFIX + ":Product", _xmlNamespaceManager);
foreach (XmlNode productNode in productsFound)
{
Product p = CreateProduct(productNode);
if (p != null)
{
availableProducts.Add(p.ProductCode, p);
buildQueue.Add(p.ProductCode, CreateProduct(productNode));
}
}
// Set the product and included products for each product
foreach (Product p in availableProducts.Values)
{
AddDependencies(p, availableProducts);
AddIncludes(p, availableProducts);
}
// We need only the dependencies to generate the bulid order
foreach (Product p in buildQueue.Values)
{
AddDependencies(p, buildQueue);
}
// Scan the products and their dependencies to calculate install order
OrderProducts(availableProducts, buildQueue);
}
private void AddDependencies(Product p, Dictionary<string, Product> availableProducts)
{
foreach (string relatedProductCode in SelectRelatedProducts(p, "DependsOnProduct"))
{
if (availableProducts.TryGetValue(relatedProductCode, out Product product))
{
p.AddDependentProduct(product);
}
else
{
p.AddMissingDependency(new List<string> { relatedProductCode });
}
}
foreach (XmlNode eitherProductNode in SelectEitherProducts(p))
{
var foundDependencies = new List<Product>();
var allDependencies = new List<string>();
foreach (XmlNode relatedProductNode in eitherProductNode.SelectNodes(String.Format(CultureInfo.InvariantCulture, "{0}:DependsOnProduct", BOOTSTRAPPER_PREFIX), _xmlNamespaceManager))
{
var relatedProductAttribute = (XmlAttribute)(relatedProductNode.Attributes.GetNamedItem("Code"));
if (relatedProductAttribute != null)
{
string dependency = relatedProductAttribute.Value;
if (availableProducts.TryGetValue(dependency, out Product product))
{
foundDependencies.Add(product);
}
allDependencies.Add(dependency);
}
}
if (foundDependencies.Count > 0)
{
if (!p.ContainsDependencies(foundDependencies))
{
p.Dependencies.Add(foundDependencies);
}
}
else if (allDependencies.Count > 0)
{
p.AddMissingDependency(allDependencies);
}
}
}
private void AddIncludes(Product p, Dictionary<string, Product> availableProducts)
{
foreach (string relatedProductCode in SelectRelatedProducts(p, "IncludesProduct"))
{
if (availableProducts.TryGetValue(relatedProductCode, out Product product))
{
p.Includes.Add(product);
}
}
}
private string[] SelectRelatedProducts(Product p, string nodeName)
{
var list = new List<string>();
XmlNodeList relatedProducts = p.Node.SelectNodes(string.Format(CultureInfo.InvariantCulture, "{0}:Package/{1}:RelatedProducts/{2}:{3}", BOOTSTRAPPER_PREFIX, BOOTSTRAPPER_PREFIX, BOOTSTRAPPER_PREFIX, nodeName), _xmlNamespaceManager);
if (relatedProducts != null)
{
foreach (XmlNode relatedProduct in relatedProducts)
{
XmlAttribute relatedProductAttribute = (XmlAttribute)(relatedProduct.Attributes.GetNamedItem("Code"));
if (relatedProductAttribute != null)
{
list.Add(relatedProductAttribute.Value);
}
}
}
return list.ToArray();
}
private XmlNodeList SelectEitherProducts(Product p)
{
XmlNodeList eitherProducts = p.Node.SelectNodes(string.Format(CultureInfo.InvariantCulture, "{0}:Package/{1}:RelatedProducts/{2}:EitherProducts", BOOTSTRAPPER_PREFIX, BOOTSTRAPPER_PREFIX, BOOTSTRAPPER_PREFIX), _xmlNamespaceManager);
return eitherProducts;
}
private void OrderProducts(Dictionary<string, Product> availableProducts, Dictionary<string, Product> buildQueue)
{
bool loopDetected = false;
_loopDependenciesWarnings = new BuildResults();
var productsInLoop = new StringBuilder();
var productsToRemove = new List<string>();
while (buildQueue.Count > 0)
{
productsToRemove.Clear();
foreach (Product p in buildQueue.Values)
{
if (p.Dependencies.Count == 0)
{
_products.Add(availableProducts[p.ProductCode]);
RemoveDependency(buildQueue, p);
productsToRemove.Add(p.ProductCode);
}
}
foreach (string productCode in productsToRemove)
{
buildQueue.Remove(productCode);
if (loopDetected)
{
productsInLoop.Append(productCode);
productsInLoop.Append(", ");
}
}
// If we could not remove any products and there are still products in the queue
// there must be a loop in it. We'll break the loop by removing the dependencies
// of the first project in the queue;
if (buildQueue.Count > 0 && productsToRemove.Count == 0)
{
Product p = buildQueue.Values.First();
p.Dependencies.RemoveAll(m => true);
loopDetected = true;
}
// If we've been in a loop and there are no more products left
// or no more products can be installed, we have completely walked that loop
// and now is a good time to show the warning message for the loop
if (productsInLoop.Length > 0 && (buildQueue.Count == 0 || productsToRemove.Count == 0))
{
productsInLoop.Remove(productsInLoop.Length - 2, 2);
_loopDependenciesWarnings.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.CircularDependency", productsInLoop.ToString()));
productsInLoop.Remove(0, productsInLoop.Length);
}
}
}
private static void RemoveDependency(Dictionary<string, Product> availableProducts, Product product)
{
foreach (Product p in availableProducts.Values)
{
foreach (List<Product> dependency in p.Dependencies)
{
dependency.RemoveAll(m => m == product);
}
p.Dependencies.RemoveAll(m => m.Count == 0);
}
}
private XmlDocument LoadAndValidateXmlDocument(string filePath, bool validateFilePresent, string schemaPath, string schemaNamespace, XmlValidationResults results)
{
XmlDocument xmlDocument = null;
Debug.Assert(filePath != null, "null filePath?");
Debug.Assert(schemaPath != null, "null schemaPath?");
Debug.Assert(schemaNamespace != null, "null schemaNamespace?");
if ((filePath != null) && (schemaPath != null) && (schemaNamespace != null))
{
// set up our validation logic by detecting the trace-switch enabled and whether or
// not our files exist.
bool validate = true;
bool fileExists = FileSystems.Default.FileExists(filePath);
bool schemaExists = FileSystems.Default.FileExists(schemaPath);
// if we're being asked to validate but we can't find the schema file, then
// output something useful to tell user that we can't find the schema.
if (!schemaExists)
{
Debug.Fail("Could not locate schema '" + schemaPath + "', so no validation of '" + filePath + "' is possible.");
validate = false;
}
// if we're being asked to validate but we can't find the data file, then
// output something useful to tell user that we can't find the file and that we
// can't do anything useful.
if (validate && (!fileExists) && validateFilePresent)
{
Debug.Fail("Could not locate data file '" + filePath + "'.");
validate = false;
}
if (fileExists)
{
XmlReaderSettings xmlReaderSettings = new XmlReaderSettings();
xmlReaderSettings.DtdProcessing = DtdProcessing.Ignore;
if (validate)
{
xmlReaderSettings.ValidationType = ValidationType.Schema;
xmlReaderSettings.XmlResolver = null;
xmlReaderSettings.ValidationEventHandler += results.SchemaValidationEventHandler; ;
xmlReaderSettings.Schemas.Add(null, schemaPath);
}
using (StreamReader streamReader = new StreamReader(filePath))
{
using (XmlReader xmlReader = XmlReader.Create(streamReader, xmlReaderSettings, filePath))
{
try
{
Debug.Assert(_document != null, "our document should have been created by now!");
xmlDocument = new XmlDocument(_document.NameTable);
xmlDocument.Load(xmlReader);
}
catch (XmlException ex)
{
Debug.Fail("Failed to load document '" + filePath + "' due to the following exception:\r\n" + ex.Message);
return null;
}
catch (System.Xml.Schema.XmlSchemaException ex)
{
Debug.Fail("Failed to load document '" + filePath + "' due to the following exception:\r\n" + ex.Message);
return null;
}
}
}
// Note that the xml document's default namespace must match the schema namespace
// or none of our SelectNodes/SelectSingleNode calls will succeed
Debug.Assert(xmlDocument.DocumentElement != null &&
string.Equals(xmlDocument.DocumentElement.NamespaceURI, schemaNamespace, StringComparison.Ordinal),
"'" + xmlDocument.DocumentElement.NamespaceURI + "' is not '" + schemaNamespace + "'...");
}
}
return xmlDocument;
}
private void ExploreDirectory(string strSubDirectory, XmlElement rootElement, string packagePath)
{
try
{
string strSubDirectoryFullPath = System.IO.Path.Combine(packagePath, strSubDirectory);
// figure out our product file paths based on the directory full path
string strBaseManifestFilename = System.IO.Path.Combine(strSubDirectoryFullPath, ROOT_MANIFEST_FILE);
string strBaseManifestSchemaFileName = System.IO.Path.Combine(SchemaPath, MANIFEST_FILE_SCHEMA);
var productValidationResults = new ProductValidationResults(strBaseManifestFilename);
// open the XmlDocument for this product.xml
XmlDocument productDoc = LoadAndValidateXmlDocument(strBaseManifestFilename, false, strBaseManifestSchemaFileName, BOOTSTRAPPER_NAMESPACE, productValidationResults);
if (productDoc != null)
{
bool packageAdded = false;
XmlNode baseNode = productDoc.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":Product", _xmlNamespaceManager);
if (baseNode != null)
{
// Get the ProductCode attribute for this product
var productCodeAttribute = (XmlAttribute)(baseNode.Attributes.GetNamedItem("ProductCode"));
if (productCodeAttribute != null)
{
// now add it to our full document if it's not already present
XmlNode productNode = rootElement.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":Product[@ProductCode='" + productCodeAttribute.Value + "']", _xmlNamespaceManager);
if (productNode == null)
{
productNode = CreateProductNode(baseNode);
}
else
{
_validationResults.TryGetValue(
productCodeAttribute.Value,
out productValidationResults);
}
// Fix-up the <PackageFiles> of the base node to include the SourcePath and TargetPath
XmlNode packageFilesNode = baseNode.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":PackageFiles", _xmlNamespaceManager);
XmlNode checksNode = baseNode.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":InstallChecks", _xmlNamespaceManager);
XmlNode commandsNode = baseNode.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":Commands", _xmlNamespaceManager);
// if there was a packageFiles node, then add it in to our full document with the rest
if (packageFilesNode != null)
{
UpdatePackageFileNodes(packageFilesNode, System.IO.Path.Combine(packagePath, strSubDirectory), strSubDirectory);
ReplacePackageFileAttributes(checksNode, "PackageFile", packageFilesNode, "PackageFile", "OldName", "Name");
ReplacePackageFileAttributes(commandsNode, "PackageFile", packageFilesNode, "PackageFile", "OldName", "Name");
ReplacePackageFileAttributes(baseNode, EULA_ATTRIBUTE, packageFilesNode, "PackageFile", "OldName", "SourcePath");
}
foreach (string strLanguageDirectory in Directory.GetDirectories(strSubDirectoryFullPath))
{
// The base node would get destroyed as we build-up this new node.
// Thus, we want to use a copy of the baseNode
var baseElement = (XmlElement)(_document.ImportNode(baseNode, true));
string strLangManifestFilename = System.IO.Path.Combine(strLanguageDirectory, CHILD_MANIFEST_FILE);
string strLangManifestSchemaFileName = System.IO.Path.Combine(SchemaPath, MANIFEST_FILE_SCHEMA);
if (FileSystems.Default.FileExists(strLangManifestFilename))
{
// Load Package.xml
XmlValidationResults packageValidationResults = new XmlValidationResults(strLangManifestFilename);
XmlDocument langDoc = LoadAndValidateXmlDocument(strLangManifestFilename, false, strLangManifestSchemaFileName, BOOTSTRAPPER_NAMESPACE, packageValidationResults);
Debug.Assert(langDoc != null, "we couldn't load package.xml in '" + strLangManifestFilename + "'...?");
if (langDoc == null)
{
continue;
}
XmlNode langNode = langDoc.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":Package", _xmlNamespaceManager);
Debug.Assert(langNode != null, string.Format(CultureInfo.CurrentCulture, "Unable to find a package node in {0}", strLangManifestFilename));
if (langNode != null)
{
XmlElement langElement = (XmlElement)(_document.ImportNode(langNode, true));
XmlElement mergeElement = _document.CreateElement("Package", BOOTSTRAPPER_NAMESPACE);
// Update the "PackageFiles" section to reflect this language subdirectory
XmlNode packageFilesNodePackage = langElement.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":PackageFiles", _xmlNamespaceManager);
checksNode = langElement.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":InstallChecks", _xmlNamespaceManager);
commandsNode = langElement.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":Commands", _xmlNamespaceManager);
if (packageFilesNodePackage != null)
{
int nStartIndex = packagePath.Length;
if ((strLanguageDirectory.ToCharArray())[nStartIndex] ==
System.IO.Path.DirectorySeparatorChar)
{
nStartIndex++;
}
UpdatePackageFileNodes(packageFilesNodePackage, strLanguageDirectory, strSubDirectory);
ReplacePackageFileAttributes(checksNode, "PackageFile", packageFilesNodePackage, "PackageFile", "OldName", "Name");
ReplacePackageFileAttributes(commandsNode, "PackageFile", packageFilesNodePackage, "PackageFile", "OldName", "Name");
ReplacePackageFileAttributes(langElement, EULA_ATTRIBUTE, packageFilesNodePackage, "PackageFile", "OldName", "SourcePath");
}
if (packageFilesNode != null)
{
ReplacePackageFileAttributes(checksNode, "PackageFile", packageFilesNode, "PackageFile", "OldName", "Name");
ReplacePackageFileAttributes(commandsNode, "PackageFile", packageFilesNode, "PackageFile", "OldName", "Name");
ReplacePackageFileAttributes(langElement, EULA_ATTRIBUTE, packageFilesNode, "PackageFile", "OldName", "SourcePath");
}
// in general, we prefer the attributes of the language document over the
// attributes of the base document. Copy attributes from the lang to the merged,
// and then merge all unique elements into merge
foreach (XmlAttribute attribute in langElement.Attributes)
{
mergeElement.Attributes.Append((XmlAttribute)(mergeElement.OwnerDocument.ImportNode(attribute, false)));
}
foreach (XmlAttribute attribute in baseElement.Attributes)
{
var convertedAttribute = (XmlAttribute)(mergeElement.OwnerDocument.ImportNode(attribute, false));
MergeAttribute(mergeElement, convertedAttribute);
}
// And append all of the nodes
// There is a well-known set of nodes which may have inherit children
// When merging these nodes, there may be subnodes taken from both the lang element and the base element.
// There will never be multiple nodes with the same name in the same manifest
// The function which performs this action is CombineElements(...)
CombineElements(langElement, baseElement, "Commands", "PackageFile", mergeElement);
CombineElements(langElement, baseElement, "InstallChecks", "Property", mergeElement);
CombineElements(langElement, baseElement, "PackageFiles", "Name", mergeElement);
CombineElements(langElement, baseElement, "Schedules", "Name", mergeElement);
CombineElements(langElement, baseElement, "Strings", "Name", mergeElement);
ReplaceStrings(mergeElement);
CorrectPackageFiles(mergeElement);
AppendNode(baseElement, "RelatedProducts", mergeElement);
// Create a unique identifier for this package
var cultureAttribute = (XmlAttribute)mergeElement.Attributes.GetNamedItem("Culture");
if (!String.IsNullOrEmpty(cultureAttribute?.Value))
{
string packageCode = productCodeAttribute.Value + "." + cultureAttribute.Value;
AddAttribute(mergeElement, "PackageCode", packageCode);
if (productValidationResults != null && packageValidationResults != null)
{
productValidationResults.AddPackageResults(cultureAttribute.Value, packageValidationResults);
}
// Only add this package if there is a culture apecified.
productNode.AppendChild(mergeElement);
packageAdded = true;
}
}
}
}
if (packageAdded)
{
rootElement.AppendChild(productNode);
if (!_validationResults.ContainsKey(productCodeAttribute.Value))
{
_validationResults.Add(productCodeAttribute.Value, productValidationResults);
}
else
{
Debug.WriteLine(String.Format(CultureInfo.CurrentCulture, "Validation results already added for Product Code '{0}'", productCodeAttribute));
}
}
}
}
}
}
catch (XmlException ex)
{
Debug.Fail(ex.Message);
}
catch (IOException ex)
{
Debug.Fail(ex.Message);
}
catch (ArgumentException ex)
{
Debug.Fail(ex.Message);
}
}
private Product CreateProduct(XmlNode node)
{
bool fPackageAdded = false;
string productCode = ReadAttribute(node, "ProductCode");
Product product = null;
if (!String.IsNullOrEmpty(productCode))
{
_validationResults.TryGetValue(productCode, out ProductValidationResults results);
XmlNode packageFilesNode = node.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":Package/" + BOOTSTRAPPER_PREFIX + ":PackageFiles", _xmlNamespaceManager);
string copyAllPackageFiles = String.Empty;
if (packageFilesNode != null)
{
copyAllPackageFiles = ReadAttribute(packageFilesNode, "CopyAllPackageFiles");
}
product = new Product(node, productCode, results, copyAllPackageFiles);
XmlNodeList packageNodeList = node.SelectNodes(BOOTSTRAPPER_PREFIX + ":Package", _xmlNamespaceManager);
foreach (XmlNode packageNode in packageNodeList)
{
Package package = CreatePackage(packageNode, product);
if (package != null)
{
product.AddPackage(package);
fPackageAdded = true;
}
}
}
if (fPackageAdded)
{
return product;
}
return null;
}
private static Package CreatePackage(XmlNode node, Product product)
{
string culture = ReadAttribute(node, "Culture");
XmlValidationResults results;
if (culture != null)
{
results = product.GetPackageValidationResults(culture);
}
else
{
return null;
}
return new Package(product, node, results, ReadAttribute(node, "Name"), ReadAttribute(node, "Culture"));
}
private void ReplaceAttributes(XmlNode targetNode, string attributeName, string oldValue, string newValue)
{
if (targetNode != null)
{
// select all nodes where the attributeName equals the oldValue
XmlNodeList nodeList = targetNode.SelectNodes(BOOTSTRAPPER_PREFIX + string.Format(CultureInfo.InvariantCulture, ":*[@{0}='{1}']", attributeName, oldValue), _xmlNamespaceManager);
foreach (XmlNode node in nodeList)
{
ReplaceAttribute(node, attributeName, newValue);
}
// replace attributes on the node itself
XmlAttribute attrib = targetNode.Attributes[attributeName];
if (attrib != null && attrib.Value == oldValue)
{
attrib.Value = newValue;
}
}
}
private static void ReplaceAttribute(XmlNode targetNode, string attributeName, string attributeValue)
{
XmlAttribute attribute = targetNode.OwnerDocument.CreateAttribute(attributeName);
attribute.Value = attributeValue;
targetNode.Attributes.SetNamedItem(attribute);
}
private static void MergeAttribute(XmlNode targetNode, XmlAttribute attribute)
{
var targetAttribute = (XmlAttribute)(targetNode.Attributes.GetNamedItem(attribute.Name));
if (targetAttribute == null)
{
// This node does not already contain the attribute. Add the parameter
targetNode.Attributes.Append(attribute);
}
}
private void UpdatePackageFileNodes(XmlNode packageFilesNode, string strSourcePath, string strTargetPath)
{
XmlNodeList packageFileNodeList = packageFilesNode.SelectNodes(BOOTSTRAPPER_PREFIX + ":PackageFile", _xmlNamespaceManager);
foreach (XmlNode packageFileNode in packageFileNodeList)
{
var nameAttribute = (XmlAttribute)(packageFileNode.Attributes.GetNamedItem("Name"));
// the name attribute is required -- we can't do anything if it's not present
if (nameAttribute != null)
{
string relativePath = nameAttribute.Value;
XmlAttribute sourcePathAttribute = packageFilesNode.OwnerDocument.CreateAttribute("SourcePath");
string strSourceFile = System.IO.Path.Combine(strSourcePath, relativePath);
sourcePathAttribute.Value = strSourceFile;
XmlAttribute targetPathAttribute = packageFilesNode.OwnerDocument.CreateAttribute("TargetPath");
targetPathAttribute.Value = System.IO.Path.Combine(strTargetPath, relativePath);
string oldNameValue = nameAttribute.Value;
string newNameValue = System.IO.Path.Combine(strTargetPath, relativePath);
XmlAttribute oldNameAttribute = packageFilesNode.OwnerDocument.CreateAttribute("OldName");
oldNameAttribute.Value = oldNameValue;
ReplaceAttribute(packageFileNode, "Name", newNameValue);
MergeAttribute(packageFileNode, sourcePathAttribute);
MergeAttribute(packageFileNode, targetPathAttribute);
MergeAttribute(packageFileNode, oldNameAttribute);
}
}
}
private void AppendNode(XmlElement element, string nodeName, XmlElement mergeElement)
{
XmlNode node = element.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":" + nodeName, _xmlNamespaceManager);
if (node != null)
{
mergeElement.AppendChild(node);
}
}
private void CombineElements(XmlElement langElement, XmlElement baseElement, string strNodeName, string strSubNodeKey, XmlElement mergeElement)
{
XmlNode langNode = langElement.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":" + strNodeName, _xmlNamespaceManager);
XmlNode baseNode = baseElement.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":" + strNodeName, _xmlNamespaceManager);
// There are 4 basic cases to be dealt with:
// Case # 1 2 3 4
// base null null present present
// lang null present null present
// Result null lang base combine
//
// Cases 1 - 3 are pretty trivial.
if (baseNode == null)
{
if (langNode != null)
{
// Case 2
mergeElement.AppendChild(langNode);
}
// Case 1 is to do nothing
}
else
{
if (langNode == null)
{
// Case 3
mergeElement.AppendChild(baseNode);
}
else
{
XmlNode mergeSubNode = _document.CreateElement(strNodeName, BOOTSTRAPPER_NAMESPACE);
XmlNode nextNode = baseNode.FirstChild;
// Begin case 4
// Go through every element in the base node
while (nextNode != null)
{
if (nextNode.NodeType == XmlNodeType.Element)
{
XmlAttribute keyAttribute = (XmlAttribute)(nextNode.Attributes.GetNamedItem(strSubNodeKey));
if (keyAttribute != null)
{
XmlNode queryResultNode = QueryForSubNode(langNode, strSubNodeKey, keyAttribute.Value);
// if there is no match in the lang node, use the current base node
// Otherwise use that node and remove it later
if (queryResultNode == null)
{
mergeSubNode.AppendChild(mergeSubNode.OwnerDocument.ImportNode(nextNode, true));
}
else
{
mergeSubNode.AppendChild(mergeSubNode.OwnerDocument.ImportNode(queryResultNode, true));
langNode.RemoveChild(queryResultNode);
}
}
else
{
Debug.Assert(false, "Specified key does not exist for node " + nextNode.InnerXml);
}
}
nextNode = nextNode.NextSibling;
}
// Append all remaining lang nodes
nextNode = langNode.FirstChild;
while (nextNode != null)
{
mergeSubNode.AppendChild(mergeSubNode.OwnerDocument.ImportNode(nextNode, true));
nextNode = nextNode.NextSibling;
}
// Copy all attributes. The langnode again has priority
foreach (XmlAttribute attribute in langNode.Attributes)
{
AddAttribute(mergeSubNode, attribute.Name, attribute.Value);
}
foreach (XmlAttribute attribute in baseNode.Attributes)
{
if (mergeSubNode.Attributes.GetNamedItem(attribute.Name) == null)
{
AddAttribute(mergeSubNode, attribute.Name, attribute.Value);
}
}
mergeElement.AppendChild(mergeSubNode);
}
}
}
private XmlNode QueryForSubNode(XmlNode subNode, string strSubNodeKey, string strTargetValue)
{
string strQuery = string.Format(CultureInfo.InvariantCulture, "{0}:*[@{1}='{2}']", BOOTSTRAPPER_PREFIX, strSubNodeKey, strTargetValue);
return subNode.SelectSingleNode(strQuery, _xmlNamespaceManager);
}
private void CorrectPackageFiles(XmlNode node)
{
XmlNode packageFilesNode = node.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":PackageFiles", _xmlNamespaceManager);
if (packageFilesNode != null)
{
// Map all StringKey attributes to corresponding String values
XmlNodeList packageFileNodeList = node.SelectNodes("//" + BOOTSTRAPPER_PREFIX + ":*[@PackageFile]", _xmlNamespaceManager);
foreach (XmlNode currentNode in packageFileNodeList)
{
var attribute = (XmlAttribute)(currentNode.Attributes.GetNamedItem("PackageFile"));
string strQuery = BOOTSTRAPPER_PREFIX + ":PackageFile[@Name='" + attribute.Value + "']";
XmlNode packageFileNode = packageFilesNode.SelectSingleNode(strQuery, _xmlNamespaceManager);
if (packageFileNode != null)
{
var targetPathAttribute = (XmlAttribute)(packageFileNode.Attributes.GetNamedItem("TargetPath"));
attribute.Value = targetPathAttribute.Value;
}
}
}
}
private void ReplaceStrings(XmlNode node)
{
XmlNode stringsNode = node.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":Strings", _xmlNamespaceManager);
if (stringsNode != null)
{
string stringNodeLookupTemplate = BOOTSTRAPPER_PREFIX + ":String[@Name='{0}']";
// The name attribute at the package level is an entry into the String table
ReplaceAttributeString(node, "Name", stringsNode);
ReplaceAttributeString(node, "Culture", stringsNode);
// Homesite information is also carried in the String table
XmlNode packageFilesNode = node.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":PackageFiles", _xmlNamespaceManager);
if (packageFilesNode != null)
{
XmlNodeList packageFileNodeList = packageFilesNode.SelectNodes(BOOTSTRAPPER_PREFIX + ":PackageFile", _xmlNamespaceManager);
foreach (XmlNode packageFileNode in packageFileNodeList)
{
ReplaceAttributeString(packageFileNode, HOMESITE_ATTRIBUTE, stringsNode);
}
}
// Map all String attributes to corresponding String values
// It is currently expected that these come from either an ExitCode or FailIf
XmlNodeList stringKeyNodeList = node.SelectNodes("//" + BOOTSTRAPPER_PREFIX + ":*[@String]", _xmlNamespaceManager);
foreach (XmlNode currentNode in stringKeyNodeList)
{
var attribute = (XmlAttribute)(currentNode.Attributes.GetNamedItem("String"));
XmlNode stringNode = stringsNode.SelectSingleNode(string.Format(CultureInfo.InvariantCulture, stringNodeLookupTemplate, attribute.Value), _xmlNamespaceManager);
if (stringNode != null)
{
AddAttribute(currentNode, "Text", stringNode.InnerText);
}
currentNode.Attributes.Remove(attribute);
}
// The Strings node is no longer necessary. Remove it.
node.RemoveChild(stringsNode);
}
}
private bool BuildPackages(BuildSettings settings, XmlElement configElement, ResourceUpdater resourceUpdater, List<string> filesCopied, Dictionary<string, KeyValuePair<string, string>> eulas)
{
bool fSucceeded = true;
foreach (ProductBuilder builder in settings.ProductBuilders)
{
if (Validate && !builder.Product.ValidationPassed)
{
if (_results != null)
{
_results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.ProductValidation", builder.Name, builder.Product.ValidationResults.FilePath));
foreach (string validationMessage in builder.Product.ValidationResults.ValidationErrors)
{
_results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.ValidationError", builder.Product.ValidationResults.FilePath, validationMessage));
}
foreach (string validationMessage in builder.Product.ValidationResults.ValidationWarnings)
{
_results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.ValidationWarning", builder.Product.ValidationResults.FilePath, validationMessage));
}
}
}
Package package = GetPackageForSettings(settings, builder, _results);
if (package == null)
{
// GetPackage should have already added the correct message info
continue;
}
if (Validate && !package.ValidationPassed)
{
if (_results != null)
{
_results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.PackageValidation", builder.Name, package.ValidationResults.FilePath));
foreach (string validationMessage in package.ValidationResults.ValidationErrors)
{
_results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.ValidationError", package.ValidationResults.FilePath, validationMessage));
}
foreach (string validationMessage in package.ValidationResults.ValidationWarnings)
{
_results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.ValidationWarning", package.ValidationResults.FilePath, validationMessage));
}
}
}
XmlNode node = package.Node;
// Copy the files for this package to the output directory
XmlAttribute eulaAttribute = node.Attributes[EULA_ATTRIBUTE];
XmlNodeList packageFileNodes = node.SelectNodes(BOOTSTRAPPER_PREFIX + ":PackageFiles/" + BOOTSTRAPPER_PREFIX + ":PackageFile", _xmlNamespaceManager);
XmlNode installChecksNode = node.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":InstallChecks", _xmlNamespaceManager);
foreach (XmlNode packageFileNode in packageFileNodes)
{
var packageFileSource = (XmlAttribute)(packageFileNode.Attributes.GetNamedItem("SourcePath"));
var packageFileDestination = (XmlAttribute)(packageFileNode.Attributes.GetNamedItem("TargetPath"));
var packageFileName = (XmlAttribute)(packageFileNode.Attributes.GetNamedItem("Name"));
var packageFileCopy = (XmlAttribute)(packageFileNode.Attributes.GetNamedItem("CopyOnBuild"));
if (packageFileSource != null && !String.IsNullOrEmpty(eulaAttribute?.Value) && packageFileSource.Value == eulaAttribute.Value)
{
// need to remove EULA from the package file list
XmlNode packageFilesNode = node.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":PackageFiles", _xmlNamespaceManager);
packageFilesNode.RemoveChild(packageFileNode);
continue;
}
if ((packageFileSource != null) && (packageFileDestination != null) &&
(packageFileName != null))
{
// Calculate the hash of this file and add it to the PackageFileNode
if (!AddVerificationInformation(
packageFileNode,
packageFileSource.Value,
packageFileName.Value,
builder,
settings,
_results))
{
fSucceeded = false;
}
}
if ((packageFileSource != null) && (packageFileDestination != null) &&
((packageFileCopy == null) || (!String.Equals(packageFileCopy.Value, "False", StringComparison.InvariantCulture))))
{
// if this is the key for an external check, we will add it to the Resource Updater instead of copying the file
XmlNode subNode = null;
if ((installChecksNode != null) && (packageFileName != null))
{
subNode = QueryForSubNode(installChecksNode, "PackageFile", packageFileName.Value);
}
if (subNode != null)
{
if (resourceUpdater != null)
{
if (!FileSystems.Default.FileExists(packageFileSource.Value))
{
_results?.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.PackageResourceFileNotFound", packageFileSource.Value, builder.Name));
fSucceeded = false;
continue;
}
resourceUpdater.AddFileResource(packageFileSource.Value, packageFileDestination.Value);
}
}
else
{
if (settings.ComponentsLocation == ComponentsLocation.Relative || !VerifyHomeSiteInformation(packageFileNode, builder, settings, _results))
{
if (settings.CopyComponents)
{
string strDestinationFileName = System.IO.Path.Combine(settings.OutputPath, packageFileDestination.Value);
try
{
if (!FileSystems.Default.FileExists(packageFileSource.Value))
{
_results?.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.PackageFileNotFound", packageFileDestination.Value, builder.Name));
fSucceeded = false;
continue;
}
EnsureFolderExists(System.IO.Path.GetDirectoryName(strDestinationFileName));
File.Copy(packageFileSource.Value, strDestinationFileName, true);
ClearReadOnlyAttribute(strDestinationFileName);
}
catch (UnauthorizedAccessException ex)
{
_results?.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.CopyPackageError", packageFileSource.Value, builder.Name, ex.Message));
fSucceeded = false;
continue;
}
catch (IOException ex)
{
_results?.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.CopyPackageError", packageFileSource.Value, builder.Name, ex.Message));
fSucceeded = false;
continue;
}
catch (ArgumentException ex)
{
_results?.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.CopyPackageError", packageFileSource.Value, builder.Name, ex.Message));
fSucceeded = false;
continue;
}
catch (NotSupportedException ex)
{
_results?.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.CopyPackageError", packageFileSource.Value, builder.Name, ex.Message));
fSucceeded = false;
continue;
}
filesCopied.Add(strDestinationFileName);
}
else
{
filesCopied.Add(packageFileSource.Value);
}
// Add the file size to the PackageFileNode
XmlAttribute sizeAttribute = packageFileNode.OwnerDocument.CreateAttribute("Size");
var fi = new FileInfo(packageFileSource.Value);
sizeAttribute.Value = "" + (fi.Length.ToString(CultureInfo.InvariantCulture));
MergeAttribute(packageFileNode, sizeAttribute);
}
}
}
}
// Add the Eula attribute correctly
if (eulas != null && !String.IsNullOrEmpty(eulaAttribute?.Value))
{
if (FileSystems.Default.FileExists(eulaAttribute.Value))
{
string key = GetFileHash(eulaAttribute.Value);
if (eulas.TryGetValue(key, out KeyValuePair<string, string> eulaInfo))
{
eulaAttribute.Value = eulaInfo.Key;
}
else
{
string configFileKey = string.Format(CultureInfo.InvariantCulture, "EULA{0}", eulas.Count);
var de = new KeyValuePair<string, string>(configFileKey, eulaAttribute.Value);
eulas[key] = de;
eulaAttribute.Value = configFileKey;
}
}
else
{
_results?.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.PackageResourceFileNotFound", eulaAttribute.Value, builder.Name));
fSucceeded = false;
continue;
}
}
// Write the package node
if (configElement != null)
{
configElement.AppendChild(configElement.OwnerDocument.ImportNode(node, true));
DumpXmlToFile(node, string.Format(CultureInfo.CurrentCulture, "{0}.{1}.xml", package.Product.ProductCode, package.Culture));
}
}
return fSucceeded;
}
private XmlNode CreateProductNode(XmlNode node)
{
// create a new Product node for the passed-in product
XmlNode productNode = _document.CreateElement("Product", BOOTSTRAPPER_NAMESPACE);
// find the ProductCode attribute
var sourceAttribute = (XmlAttribute)(node.Attributes.GetNamedItem("ProductCode"));
Debug.Assert(sourceAttribute != null, "we should not be here if there is no ProductCode attribute");
AddAttribute(productNode, "ProductCode", sourceAttribute.Value);
node.Attributes.Remove(sourceAttribute);
return productNode;
}
private static string ReadAttribute(XmlNode node, string strAttributeName)
{
var attribute = (XmlAttribute)(node.Attributes.GetNamedItem(strAttributeName));
return attribute?.Value;
}
private static void EnsureFolderExists(string strFolderPath)
{
if (!FileSystems.Default.DirectoryExists(strFolderPath))
{
Directory.CreateDirectory(strFolderPath);
}
}
private static void ClearReadOnlyAttribute(string strFileName)
{
FileAttributes attribs = File.GetAttributes(strFileName);
if ((attribs & FileAttributes.ReadOnly) != 0)
{
attribs &= (~FileAttributes.ReadOnly);
File.SetAttributes(strFileName, attribs);
}
}
private static string ByteArrayToString(byte[] byteArray)
{
if (byteArray == null)
{
return null;
}
var output = new StringBuilder(byteArray.Length);
foreach (byte byteValue in byteArray)
{
output.Append(byteValue.ToString("X02", CultureInfo.InvariantCulture));
}
return output.ToString();
}
private static string GetFileHash(string filePath)
{
var fi = new FileInfo(filePath);
String retVal;
// Bootstrapper is always signed with the SHA-256 algorithm, no matter which version of
// the .NET Framework we are targeting. In ideal situations, bootstrapper files will be
// pre-signed anwyay; this is a fallback in case we ever encounter a bootstrapper that is
// not signed.
#pragma warning disable SA1111, SA1009 // Closing parenthesis should be on line of last parameter
using System.Security.Cryptography.SHA256 sha = System.Security.Cryptography.SHA256.Create(
#if FEATURE_CRYPTOGRAPHIC_FACTORY_ALGORITHM_NAMES
"System.Security.Cryptography.SHA256CryptoServiceProvider"
#endif
);
#pragma warning restore SA1111, SA1009 // Closing parenthesis should be on line of last parameter
using (Stream s = fi.OpenRead())
{
retVal = ByteArrayToString(sha.ComputeHash(s));
}
return retVal;
}
private void ReplaceAttributeString(XmlNode node, string attributeName, XmlNode stringsNode)
{
string stringNodeLookupTemplate = BOOTSTRAPPER_PREFIX + ":String[@Name='{0}']";
var attribute = (XmlAttribute)(node.Attributes.GetNamedItem(attributeName));
if (attribute != null)
{
XmlNode stringNode = stringsNode.SelectSingleNode(string.Format(CultureInfo.InvariantCulture, stringNodeLookupTemplate, attribute.Value), _xmlNamespaceManager);
if (stringNode != null)
{
attribute.Value = stringNode.InnerText;
}
}
}
private Package GetPackageForSettings(BuildSettings settings, ProductBuilder builder, BuildResults results)
{
CultureInfo ci = Util.GetCultureInfoFromString(settings.Culture);
CultureInfo fallbackCI = Util.GetCultureInfoFromString(settings.FallbackCulture);
Package package;
if (builder.Product.Packages.Count == 0)
{
results?.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.ProductCultureNotFound", builder.Name));
return null;
}
if (ci != null)
{
package = builder.Product.Packages.Package(ci.Name);
if (package != null)
{
return package;
}
// Target culture not found? Go through the progression of parent cultures (up until but excluding the invariant culture) -> fallback culture -> parent fallback culture -> default culture -> parent default culture -> any culture available
// Note: there is no warning if the parent culture of the requested culture is found
CultureInfo parentCulture = ci.Parent;
// Keep going up the chain of parents, stopping at the invariant culture
while (parentCulture != CultureInfo.InvariantCulture)
{
package = GetPackageForSettings_Helper(ci, parentCulture, builder, results, false);
if (package != null)
{
return package;
}
parentCulture = parentCulture.Parent;
}
}
if (fallbackCI != null)
{
package = GetPackageForSettings_Helper(ci, fallbackCI, builder, results, true);
if (package != null)
{
return package;
}
if (!fallbackCI.IsNeutralCulture)
{
package = GetPackageForSettings_Helper(ci, fallbackCI.Parent, builder, results, true);
if (package != null)
{
return package;
}
}
}
package = GetPackageForSettings_Helper(ci, Util.DefaultCultureInfo, builder, results, true);
if (package != null)
{
return package;
}
if (!Util.DefaultCultureInfo.IsNeutralCulture)
{
package = GetPackageForSettings_Helper(ci, Util.DefaultCultureInfo.Parent, builder, results, true);
if (package != null)
{
return package;
}
}
if (results != null && ci != null)
{
results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.UsingProductCulture", ci.Name, builder.Name, builder.Product.Packages.Item(0).Culture));
}
return builder.Product.Packages.Item(0);
}
private static Package GetPackageForSettings_Helper(CultureInfo culture, CultureInfo altCulture, ProductBuilder builder, BuildResults results, bool fShowWarning)
{
if (altCulture == null)
{
return null;
}
Package package = builder.Product.Packages.Package(altCulture.Name);
if (package != null)
{
if (fShowWarning && culture != null)
{
results?.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.UsingProductCulture", culture.Name, builder.Name, altCulture.Name));
}
return package;
}
return null;
}
private bool BuildResources(BuildSettings settings, ResourceUpdater resourceUpdater)
{
if (_cultures.Count == 0)
{
_results?.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.NoResources"));
return false;
}
int codePage = -1;
XmlNode resourcesNode = GetResourcesNodeForSettings(settings, _results, ref codePage);
XmlNode stringsNode = resourcesNode.SelectSingleNode("Strings");
XmlNode fontsNode = resourcesNode.SelectSingleNode("Fonts");
if (stringsNode == null)
{
_results?.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.NoStringsForCulture", resourcesNode.Attributes.GetNamedItem("Culture").Value));
return false;
}
XmlNodeList stringNodes = stringsNode.SelectNodes("String");
foreach (XmlNode stringNode in stringNodes)
{
XmlAttribute resourceIdAttribute = (XmlAttribute)stringNode.Attributes.GetNamedItem("Name");
if (resourceIdAttribute != null)
{
resourceUpdater.AddStringResource(MESSAGE_TABLE, resourceIdAttribute.Value.ToUpper(CultureInfo.InvariantCulture), stringNode.InnerText);
}
}
if (fontsNode != null)
{
foreach (XmlNode fontNode in fontsNode.SelectNodes("Font"))
{
ConvertChildsNodeToAttributes(fontNode);
}
string fontsConfig = XmlToConfigurationFile(fontsNode);
resourceUpdater.AddStringResource(RESOURCE_TABLE, "SETUPRES", fontsConfig);
DumpXmlToFile(fontsNode, "fonts.cfg.xml");
DumpStringToFile(fontsConfig, "fonts.cfg", false);
if (codePage != -1)
{
resourceUpdater.AddStringResource(RESOURCE_TABLE, "CODEPAGE", codePage.ToString(CultureInfo.InvariantCulture));
}
}
return true;
}
private XmlNode GetResourcesNodeForSettings(BuildSettings settings, BuildResults results, ref int codepage)
{
CultureInfo ci = Util.GetCultureInfoFromString(settings.Culture);
CultureInfo fallbackCI = Util.GetCultureInfoFromString(settings.FallbackCulture);
XmlNode cultureNode;
if (ci != null)
{
// Work through the progression of parent cultures (up until but excluding the invariant culture) -> fallback culture -> parent fallback culture -> default culture -> parent default culture -> any available culture
cultureNode = GetResourcesNodeForSettings_Helper(ci, ci, results, ref codepage, false);
if (cultureNode != null)
{
return cultureNode;
}
CultureInfo parentCulture = ci.Parent;
// Keep going up the chain of parents, stopping at the invariant culture
while (parentCulture != CultureInfo.InvariantCulture)
{
cultureNode = GetResourcesNodeForSettings_Helper(ci, parentCulture, results, ref codepage, false);
if (cultureNode != null)
{
return cultureNode;
}
parentCulture = parentCulture.Parent;
}
}
if (fallbackCI != null)
{
cultureNode = GetResourcesNodeForSettings_Helper(ci, fallbackCI, results, ref codepage, true);
if (cultureNode != null)
{
return cultureNode;
}
if (!fallbackCI.IsNeutralCulture)
{
cultureNode = GetResourcesNodeForSettings_Helper(ci, fallbackCI.Parent, results, ref codepage, true);
if (cultureNode != null)
{
return cultureNode;
}
}
}
cultureNode = GetResourcesNodeForSettings_Helper(ci, Util.DefaultCultureInfo, results, ref codepage, true);
if (cultureNode != null)
{
return cultureNode;
}
if (!Util.DefaultCultureInfo.IsNeutralCulture)
{
cultureNode = GetResourcesNodeForSettings_Helper(ci, Util.DefaultCultureInfo.Parent, results, ref codepage, true);
if (cultureNode != null)
{
return cultureNode;
}
}
KeyValuePair<string, XmlNode> altCulturePair = _cultures.FirstOrDefault();
if (ci != null)
{
results?.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.UsingResourcesCulture", ci.Name, altCulturePair.Key));
}
GetCodePage(altCulturePair.Key, ref codepage);
return altCulturePair.Value;
}
private XmlNode GetResourcesNodeForSettings_Helper(CultureInfo culture, CultureInfo altCulture, BuildResults results, ref int codepage, bool fShowWarning)
{
if (altCulture != null && _cultures.TryGetValue(altCulture.Name, out XmlNode cultureNode))
{
if (fShowWarning && culture != null)
{
results?.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.UsingResourcesCulture", culture.Name, altCulture.Name));
}
codepage = altCulture.TextInfo.ANSICodePage;
return cultureNode;
}
return null;
}
private static void GetCodePage(string culture, ref int codePage)
{
try
{
var info = new CultureInfo(culture);
codePage = info.TextInfo.ANSICodePage;
}
catch (ArgumentException ex)
{
Debug.Fail(ex.Message);
}
}
private void ReplacePackageFileAttributes(XmlNode targetNodes, string targetAttribute, XmlNode sourceNodes, string sourceSubNodeName, string sourceOldName, string sourceNewName)
{
XmlNodeList sourceNodeList = sourceNodes.SelectNodes(BOOTSTRAPPER_PREFIX + ":" + sourceSubNodeName, _xmlNamespaceManager);
foreach (XmlNode sourceNode in sourceNodeList)
{
XmlAttribute oldNameAttribute = (XmlAttribute)(sourceNode.Attributes.GetNamedItem(sourceOldName));
XmlAttribute newNameAttribute = (XmlAttribute)(sourceNode.Attributes.GetNamedItem(sourceNewName));
if (oldNameAttribute != null && newNameAttribute != null)
{
ReplaceAttributes(targetNodes, targetAttribute, oldNameAttribute.Value, newNameAttribute.Value);
}
}
}
private static XmlElement CreateApplicationElement(XmlElement configElement, BuildSettings settings)
{
XmlElement applicationElement = null;
if (!String.IsNullOrEmpty(settings.ApplicationName) || !String.IsNullOrEmpty(settings.ApplicationFile))
{
applicationElement = configElement.OwnerDocument.CreateElement("Application");
if (!String.IsNullOrEmpty(settings.ApplicationName))
{
AddAttribute(applicationElement, "Name", settings.ApplicationName);
}
AddAttribute(applicationElement, "RequiresElevation", settings.ApplicationRequiresElevation ? "true" : "false");
if (!String.IsNullOrEmpty(settings.ApplicationFile))
{
XmlElement filesNode = applicationElement.OwnerDocument.CreateElement("Files");
XmlElement fileNode = filesNode.OwnerDocument.CreateElement("File");
AddAttribute(fileNode, "Name", settings.ApplicationFile);
AddAttribute(fileNode, URLNAME_ATTRIBUTE, Uri.EscapeDataString(settings.ApplicationFile));
filesNode.AppendChild(fileNode);
applicationElement.AppendChild(filesNode);
}
}
return applicationElement;
}
private static void AddAttribute(XmlNode node, string attributeName, string attributeValue)
{
XmlAttribute attrib = node.OwnerDocument.CreateAttribute(attributeName);
attrib.Value = attributeValue;
node.Attributes.Append(attrib);
}
[SuppressMessage("Microsoft.Security.Xml", "CA3073: ReviewTrustedXsltUse.", Justification = "Input style sheet comes from our own assemblies. Hence it is a trusted source.")]
[SuppressMessage("Microsoft.Security.Xml", "CA3059: UseXmlReaderForXPathDocument.", Justification = "Input style sheet comes from our own assemblies. Hence it is a trusted source.")]
[SuppressMessage("Microsoft.Security.Xml", "CA3052: UseXmlResolver.", Justification = "Input style sheet comes from our own assemblies. Hence it is a trusted source.")]
public static string XmlToConfigurationFile(XmlNode input)
{
using (var reader = new XmlNodeReader(input))
{
Stream s = GetEmbeddedResourceStream(CONFIG_TRANSFORM);
var d = new XPathDocument(s);
var xslc = new XslCompiledTransform();
// Using the Trusted Xslt is fine as the style sheet comes from our own assembly.
xslc.Load(d, XsltSettings.TrustedXslt, new XmlUrlResolver());
var xml = new XPathDocument(reader);
using (var m = new MemoryStream())
{
using (var w = new StreamWriter(m))
{
xslc.Transform(xml, null, w);
w.Flush();
m.Position = 0;
using (StreamReader r = new StreamReader(m))
{
// HACKHACK
string str = r.ReadToEnd();
return str.Replace("%NEWLINE%", Environment.NewLine);
}
}
}
}
}
private static Stream GetEmbeddedResourceStream(string name)
{
Assembly a = Assembly.GetExecutingAssembly();
Stream s = a.GetManifestResourceStream(String.Format(CultureInfo.InvariantCulture, "{0}.{1}", typeof(BootstrapperBuilder).Namespace, name));
Debug.Assert(s != null, String.Format(CultureInfo.CurrentCulture, "EmbeddedResource '{0}' not found", name));
return s;
}
private static void DumpXmlToFile(XmlNode node, string fileName)
{
if (s_logging)
{
try
{
using (var xmlwriter = new XmlTextWriter(System.IO.Path.Combine(s_logPath, fileName), Encoding.UTF8))
{
xmlwriter.Formatting = Formatting.Indented;
xmlwriter.Indentation = 4;
using var xmlReader = new XmlNodeReader(node);
xmlwriter.WriteNode(xmlReader, true);
}
}
catch (IOException)
{
// can't write info to a log file? This is a trouble-shooting helper only, and
// this exception can be ignored
}
catch (UnauthorizedAccessException)
{
// can't write info to a log file? This is a trouble-shooting helper only, and
// this exception can be ignored
}
catch (ArgumentException)
{
// can't write info to a log file? This is a trouble-shooting helper only, and
// this exception can be ignored
}
catch (NotSupportedException)
{
// can't write info to a log file? This is a trouble-shooting helper only, and
// this exception can be ignored
}
catch (XmlException)
{
// can't write info to a log file? This is a trouble-shooting helper only, and
// this exception can be ignored
}
}
}
private static void DumpStringToFile(string text, string fileName, bool append)
{
if (s_logging)
{
try
{
using (var fileWriter = new StreamWriter(System.IO.Path.Combine(s_logPath, fileName), append))
{
fileWriter.Write(text);
}
}
catch (IOException)
{
// can't write info to a log file? This is a trouble-shooting helper only, and
// this exception can be ignored
}
catch (UnauthorizedAccessException)
{
// can't write info to a log file? This is a trouble-shooting helper only, and
// this exception can be ignored
}
catch (ArgumentException)
{
// can't write info to a log file? This is a trouble-shooting helper only, and
// this exception can be ignored
}
catch (NotSupportedException)
{
// can't write info to a log file? This is a trouble-shooting helper only, and
// this exception can be ignored
}
}
}
private static bool VerifyHomeSiteInformation(XmlNode packageFileNode, ProductBuilder builder, BuildSettings settings, BuildResults results)
{
if (settings.ComponentsLocation != ComponentsLocation.HomeSite)
{
return true;
}
XmlAttribute homesiteAttribute = packageFileNode.Attributes[HOMESITE_ATTRIBUTE];
if (homesiteAttribute == null && builder.Product.CopyAllPackageFiles != CopyAllFilesType.CopyAllFilesIfNotHomeSite)
{
results?.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.PackageHomeSiteMissing", builder.Name));
return false;
}
return true;
}
private bool AddVerificationInformation(XmlNode packageFileNode, string fileSource, string fileName, ProductBuilder builder, BuildSettings settings, BuildResults results)
{
XmlAttribute hashAttribute = packageFileNode.Attributes[HASH_ATTRIBUTE];
XmlAttribute publicKeyAttribute = packageFileNode.Attributes[PUBLICKEY_ATTRIBUTE];
if (FileSystems.Default.FileExists(fileSource))
{
string publicKey = GetPublicKeyOfFile(fileSource);
if (hashAttribute == null && publicKeyAttribute == null)
{
// If neither the Hash nor PublicKey attributes were specified in the manifest, add it
if (publicKey != null)
{
AddAttribute(packageFileNode, PUBLICKEY_ATTRIBUTE, publicKey);
}
else
{
AddAttribute(packageFileNode, HASH_ATTRIBUTE, GetFileHash(fileSource));
}
}
if (publicKeyAttribute != null)
{
// Always use the PublicKey of the file on disk
if (publicKey != null)
{
ReplaceAttribute(packageFileNode, PUBLICKEY_ATTRIBUTE, publicKey);
}
else
{
// File on disk is not signed. Remove the public key info, and make sure the hash is written instead
packageFileNode.Attributes.RemoveNamedItem(PUBLICKEY_ATTRIBUTE);
if (hashAttribute == null)
{
AddAttribute(packageFileNode, HASH_ATTRIBUTE, GetFileHash(fileSource));
}
}
// If the public key in the file doesn't match the public key on disk, issue a build warning
// Skip this check if the public key attribute is "0", as this means we're expecting the public key
// comparison to be skipped at install time because the file is signed by an MS trusted cert.
if (!publicKeyAttribute.Value.Equals("0", StringComparison.OrdinalIgnoreCase) &&
publicKey?.Equals(publicKeyAttribute.Value, StringComparison.OrdinalIgnoreCase) == false)
{
results?.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.DifferingPublicKeys", PUBLICKEY_ATTRIBUTE, builder.Name, fileSource));
}
}
if (hashAttribute != null)
{
string fileHash = GetFileHash(fileSource);
// Always use the Hash of the file on disk
ReplaceAttribute(packageFileNode, HASH_ATTRIBUTE, fileHash);
// If the public key in the file doesn't match the public key on disk, issue a build warning
if (!fileHash.Equals(hashAttribute.Value, StringComparison.OrdinalIgnoreCase))
{
results?.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.DifferingPublicKeys", "Hash", builder.Name, fileSource));
}
}
}
else if (settings.ComponentsLocation == ComponentsLocation.HomeSite)
{
if (hashAttribute == null && publicKeyAttribute == null)
{
results?.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.MissingVerificationInformation", fileName, builder.Name));
return false;
}
}
return true;
}
private static string GetPublicKeyOfFile(string fileSource)
{
if (FileSystems.Default.FileExists(fileSource))
{
try
{
using var cert = new X509Certificate(fileSource);
string publicKey = cert.GetPublicKeyString();
return publicKey;
}
catch (System.Security.Cryptography.CryptographicException)
{
// This just means the file is not signed.
}
}
return null;
}
private static void ConvertChildsNodeToAttributes(XmlNode node)
{
XmlNode childNode = node.FirstChild;
while (childNode != null)
{
// Need to get the next child node now because when the current node is removed, the NextSibling
// will be null
XmlNode currentNode = childNode;
childNode = currentNode.NextSibling;
if (currentNode.Attributes.Count == 0 && currentNode.InnerText.Length > 0)
{
AddAttribute(node, currentNode.Name, currentNode.InnerText);
node.RemoveChild(currentNode);
}
}
}
private static string GetLogPath()
{
if (!s_logging)
{
return null;
}
string logPath = System.IO.Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
@"Microsoft\VisualStudio\" + VisualStudioConstants.CurrentVisualStudioVersion + @"\VSPLOG");
Directory.CreateDirectory(logPath);
return logPath;
}
private static Dictionary<string, Product> GetIncludedProducts(Product product)
{
var includedProducts = new Dictionary<string, Product>(StringComparer.OrdinalIgnoreCase)
{
// Add in this product in case there is a circular includes:
// we won't continue to explore this product. It will be removed later.
{ product.ProductCode, product }
};
// Recursively add included products
foreach (Product p in product.Includes)
{
AddIncludedProducts(p, includedProducts);
}
includedProducts.Remove(product.ProductCode);
return includedProducts;
}
private static void AddIncludedProducts(Product product, Dictionary<string, Product> includedProducts)
{
if (!includedProducts.ContainsKey(product.ProductCode))
{
includedProducts.Add(product.ProductCode, product);
foreach (Product p in product.Includes)
{
AddIncludedProducts(p, includedProducts);
}
}
}
private static string MapLCIDToCultureName(int lcid)
{
if (lcid == 0)
{
return Util.DefaultCultureInfo.Name;
}
try
{
var ci = new CultureInfo(lcid);
return ci.Name;
}
catch (ArgumentException)
{
// Can't convert this lcid to a CultureInfo? Just return the default CultureInfo instead...
return Util.DefaultCultureInfo.Name;
}
}
}
}
|