1 // Copyright (c) Microsoft. All rights reserved. 2 // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 4 using Microsoft.Build.Shared; 5 using System; 6 using System.Collections; 7 using System.Collections.Generic; 8 using System.Diagnostics; 9 using System.Diagnostics.CodeAnalysis; 10 using System.Globalization; 11 using System.IO; 12 using System.Reflection; 13 using System.Runtime.InteropServices; 14 using System.Security.Cryptography.X509Certificates; 15 using System.Text; 16 using System.Xml; 17 using System.Xml.XPath; 18 using System.Xml.Xsl; 19 20 namespace Microsoft.Build.Tasks.Deployment.Bootstrapper 21 { 22 /// <summary> 23 /// This class is the top-level object for the bootstrapper system. 24 /// </summary> 25 [ComVisibleAttribute(true), GuidAttribute("1D9FE38A-0226-4b95-9C6B-6DFFA2236270"), ClassInterface(ClassInterfaceType.None)] 26 public class BootstrapperBuilder : IBootstrapperBuilder 27 { 28 private static readonly bool s_logging = !String.IsNullOrEmpty(Environment.GetEnvironmentVariable("VSPLOG")); 29 private static readonly string s_logPath = GetLogPath(); 30 31 private string _path; 32 private XmlDocument _document; 33 34 private XmlNamespaceManager _xmlNamespaceManager; 35 private ProductCollection _products = new ProductCollection(); 36 private Hashtable _cultures = new Hashtable(); 37 private Hashtable _validationResults = new Hashtable(); 38 private BuildResults _results; 39 private BuildResults _loopDependenciesWarnings; 40 private bool _fValidate = true; 41 private bool _fInitialized = false; 42 43 private const string SETUP_EXE = "setup.exe"; 44 private const string SETUP_BIN = "setup.bin"; 45 private const string SETUP_RESOURCES_FILE = "setup.xml"; 46 47 private const string ENGINE_PATH = "Engine"; // relative to bootstrapper path 48 private const string SCHEMA_PATH = "Schemas"; // relative to bootstrapper path 49 private const string PACKAGE_PATH = "Packages"; // relative to bootstrapper path 50 private const string RESOURCES_PATH = ""; 51 52 private const string BOOTSTRAPPER_NAMESPACE = "http://schemas.microsoft.com/developer/2004/01/bootstrapper"; 53 54 private const string BOOTSTRAPPER_PREFIX = "bootstrapper"; 55 56 private const string ROOT_MANIFEST_FILE = "product.xml"; 57 private const string CHILD_MANIFEST_FILE = "package.xml"; 58 private const string MANIFEST_FILE_SCHEMA = "package.xsd"; 59 private const string CONFIG_TRANSFORM = "xmltoconfig.xsl"; 60 61 private const string EULA_ATTRIBUTE = "LicenseAgreement"; 62 private const string HOMESITE_ATTRIBUTE = "HomeSite"; 63 private const string PUBLICKEY_ATTRIBUTE = "PublicKey"; 64 private const string URLNAME_ATTRIBUTE = "UrlName"; 65 private const string HASH_ATTRIBUTE = "Hash"; 66 67 private const int MESSAGE_TABLE = 43; 68 private const int RESOURCE_TABLE = 45; 69 70 /// <summary> 71 /// Creates a new BootstrapperBuilder. 72 /// </summary> BootstrapperBuilder()73 public BootstrapperBuilder() 74 { 75 _path = Util.DefaultPath; 76 } 77 78 /// <summary> 79 /// Creates a new BootstrapperBuilder. 80 /// </summary> 81 /// <param name="visualStudioVersion">The version of Visual Studio that is used to build this bootstrapper.</param> BootstrapperBuilder(string visualStudioVersion)82 public BootstrapperBuilder(string visualStudioVersion) 83 { 84 _path = Util.GetDefaultPath(visualStudioVersion); 85 } 86 87 #region IBootstrapperBuilder Members 88 89 /// <summary> 90 /// Specifies the location of the required bootstrapper files. 91 /// </summary> 92 /// <value>Path to bootstrapper files.</value> 93 public string Path 94 { 95 get { return _path; } 96 set 97 { 98 if (!_fInitialized || string.Compare(_path, value, StringComparison.OrdinalIgnoreCase) != 0) 99 { 100 _path = value; 101 Refresh(); 102 } 103 } 104 } 105 106 /// <summary> 107 /// Returns all products available at the current bootstrapper Path 108 /// </summary> 109 public ProductCollection Products 110 { 111 get 112 { 113 if (!_fInitialized) 114 Refresh(); 115 116 return _products; 117 } 118 } 119 120 /// <summary> 121 /// Generates a bootstrapper based on the specified settings. 122 /// </summary> 123 /// <param name="settings">The properties used to build this bootstrapper.</param> 124 /// <returns>The results of the bootstrapper generation</returns> Build(BuildSettings settings)125 public BuildResults Build(BuildSettings settings) 126 { 127 _results = new BuildResults(); 128 try 129 { 130 if (settings.ApplicationFile == null && (settings.ProductBuilders == null || settings.ProductBuilders.Count == 0)) 131 { 132 _results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.InvalidInput")); 133 return _results; 134 } 135 136 if (String.IsNullOrEmpty(settings.OutputPath)) 137 { 138 _results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.NoOutputPath")); 139 return _results; 140 } 141 142 if (!_fInitialized) 143 Refresh(); 144 145 if (String.IsNullOrEmpty(settings.Culture)) 146 settings.Culture = MapLCIDToCultureName(settings.LCID); 147 if (String.IsNullOrEmpty(settings.FallbackCulture)) 148 settings.FallbackCulture = MapLCIDToCultureName(settings.FallbackLCID); 149 150 if (String.IsNullOrEmpty(settings.Culture) || settings.Culture == "*") 151 { 152 settings.Culture = settings.FallbackCulture; 153 } 154 155 AddBuiltProducts(settings); 156 157 ArrayList componentFilesCopied = new ArrayList(); 158 159 // Copy setup.bin to the output directory 160 string strOutputExe = System.IO.Path.Combine(settings.OutputPath, SETUP_EXE); 161 if (!CopySetupToOutputDirectory(settings, strOutputExe)) 162 { 163 // Appropriate messages should have been stuffed into the results already 164 return _results; 165 } 166 167 ResourceUpdater resourceUpdater = new ResourceUpdater(); 168 169 // Build up the String table for setup.exe 170 if (!BuildResources(settings, resourceUpdater)) 171 { 172 // Appropriate messages should have been stuffed into the results already 173 return _results; 174 } 175 176 AddStringResourceForUrl(resourceUpdater, "BASEURL", settings.ApplicationUrl, "ApplicationUrl"); 177 AddStringResourceForUrl(resourceUpdater, "COMPONENTSURL", settings.ComponentsUrl, "ComponentsUrl"); 178 AddStringResourceForUrl(resourceUpdater, "SUPPORTURL", settings.SupportUrl, "SupportUrl"); 179 if (settings.ComponentsLocation == ComponentsLocation.HomeSite) 180 { 181 resourceUpdater.AddStringResource(40, "HOMESITE", true.ToString()); 182 } 183 184 XmlElement configElement = _document.CreateElement("Configuration"); 185 XmlElement applicationElement = CreateApplicationElement(configElement, settings); 186 if (applicationElement != null) 187 { 188 configElement.AppendChild(applicationElement); 189 } 190 191 // Key: File hash, Value: A DictionaryEntry whose Key is "EULAx" and value is a 192 // fully qualified path to a eula. It can be any eula that matches the hash. 193 Hashtable eulas = new Hashtable(); 194 195 // Copy package files, add each Package config info to the config file 196 if (!BuildPackages(settings, configElement, resourceUpdater, componentFilesCopied, eulas)) 197 return _results; 198 199 // Transform the configuration xml into something the bootstrapper will understand 200 DumpXmlToFile(configElement, "bootstrapper.cfg.xml"); 201 string config = XmlToConfigurationFile(configElement); 202 resourceUpdater.AddStringResource(41, "SETUPCFG", config); 203 DumpStringToFile(config, "bootstrapper.cfg", false); 204 205 // Put eulas in the resource stream 206 foreach (object obj in eulas.Values) 207 { 208 DictionaryEntry de = (DictionaryEntry)obj; 209 string data; 210 FileInfo fi = new System.IO.FileInfo(de.Value.ToString()); 211 using (FileStream fs = fi.OpenRead()) 212 { 213 data = new StreamReader(fs).ReadToEnd(); 214 } 215 216 resourceUpdater.AddStringResource(44, de.Key.ToString(), data); 217 } 218 219 resourceUpdater.AddStringResource(44, "COUNT", eulas.Count.ToString(CultureInfo.InvariantCulture)); 220 if (!resourceUpdater.UpdateResources(strOutputExe, _results)) 221 { 222 return _results; 223 } 224 225 _results.SetKeyFile(strOutputExe); 226 string[] componentFiles = new string[componentFilesCopied.Count]; 227 componentFilesCopied.CopyTo(componentFiles); 228 _results.AddComponentFiles(componentFiles); 229 _results.BuildSucceeded(); 230 } 231 catch (Exception ex) 232 { 233 _results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.General", ex.Message)); 234 } 235 return _results; 236 } 237 Merge(Dictionary<string, Product> output, Dictionary<string, Product> input)238 private void Merge(Dictionary<string, Product> output, Dictionary<string, Product> input) 239 { 240 foreach (Product product in input.Values) 241 { 242 AddProduct(output, product); 243 } 244 } 245 AddProduct(Dictionary<string, Product> output, Product product)246 private void AddProduct(Dictionary<string, Product> output, Product product) 247 { 248 if (!output.ContainsKey(product.ProductCode.ToLowerInvariant())) 249 output.Add(product.ProductCode.ToLowerInvariant(), product); 250 } 251 AddBuiltProducts(BuildSettings settings)252 private void AddBuiltProducts(BuildSettings settings) 253 { 254 Dictionary<string, ProductBuilder> builtProducts = new Dictionary<string, ProductBuilder>(); 255 Dictionary<string, Product> productsAndIncludes = new Dictionary<string, Product>(); 256 257 if (_loopDependenciesWarnings != null && _loopDependenciesWarnings.Messages != null) 258 { 259 foreach (BuildMessage message in _loopDependenciesWarnings.Messages) 260 { 261 _results.AddMessage(message); 262 } 263 } 264 265 foreach (ProductBuilder builder in settings.ProductBuilders) 266 { 267 builtProducts.Add(builder.Product.ProductCode.ToLowerInvariant(), builder); 268 Merge(productsAndIncludes, GetIncludedProducts(builder.Product)); 269 AddProduct(productsAndIncludes, builder.Product); 270 } 271 272 foreach (ProductBuilder builder in settings.ProductBuilders) 273 { 274 Dictionary<string, Product> includes = GetIncludedProducts(builder.Product); 275 foreach (Product p in includes.Values) 276 { 277 if (builtProducts.ContainsKey(p.ProductCode.ToLowerInvariant())) 278 { 279 _results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.IncludedProductIncluded", builder.Name, p.Name)); 280 } 281 } 282 283 foreach (List<Product> productDependency in builder.Product.Dependencies) 284 { 285 bool foundDependency = false; 286 foreach (Product p in productDependency) 287 { 288 if (productsAndIncludes.ContainsKey(p.ProductCode.ToLowerInvariant())) 289 { 290 foundDependency = true; 291 break; 292 } 293 } 294 295 if (!foundDependency) 296 { 297 if (productDependency.Count == 1) 298 { 299 _results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.MissingDependency", productDependency[0].Name, builder.Name)); 300 } 301 else 302 { 303 StringBuilder missingProductCodes = new StringBuilder(); 304 foreach (Product product in productDependency) 305 { 306 missingProductCodes.Append(product.Name); 307 missingProductCodes.Append(", "); 308 } 309 310 string productCodes = missingProductCodes.ToString(); 311 productCodes = productCodes.Substring(0, productCodes.Length - 2); 312 _results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.MissingDependencyMultiple", productCodes, builder.Name)); 313 } 314 } 315 } 316 317 foreach (ArrayList missingDependecies in builder.Product.MissingDependencies) 318 { 319 if (missingDependecies.Count == 1) 320 { 321 _results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.DependencyNotFound", builder.Name, missingDependecies[0])); 322 } 323 else 324 { 325 StringBuilder missingProductCodes = new StringBuilder(); 326 foreach (string productCode in missingDependecies) 327 { 328 missingProductCodes.Append(productCode); 329 missingProductCodes.Append(", "); 330 } 331 332 string productCodes = missingProductCodes.ToString(); 333 productCodes = productCodes.Substring(0, productCodes.Length - 2); 334 _results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.MultipleDependeciesNotFound", builder.Name, productCodes)); 335 } 336 } 337 } 338 } 339 CopySetupToOutputDirectory(BuildSettings settings, string strOutputExe)340 private bool CopySetupToOutputDirectory(BuildSettings settings, string strOutputExe) 341 { 342 string bootstrapperPath = BootstrapperPath; 343 string setupSourceFile = System.IO.Path.Combine(bootstrapperPath, SETUP_BIN); 344 345 if (!File.Exists(setupSourceFile)) 346 { 347 _results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.MissingSetupBin", SETUP_BIN, bootstrapperPath)); 348 return false; 349 } 350 351 try 352 { 353 EnsureFolderExists(settings.OutputPath); 354 File.Copy(setupSourceFile, strOutputExe, true); 355 ClearReadOnlyAttribute(strOutputExe); 356 } 357 catch (IOException ex) 358 { 359 _results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.CopyError", setupSourceFile, strOutputExe, ex.Message)); 360 return false; 361 } 362 catch (UnauthorizedAccessException ex) 363 { 364 _results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.CopyError", setupSourceFile, strOutputExe, ex.Message)); 365 return false; 366 } 367 catch (ArgumentException ex) 368 { 369 _results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.CopyError", setupSourceFile, strOutputExe, ex.Message)); 370 return false; 371 } 372 catch (NotSupportedException ex) 373 { 374 _results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.CopyError", setupSourceFile, strOutputExe, ex.Message)); 375 return false; 376 } 377 378 return true; 379 } 380 AddStringResourceForUrl(ResourceUpdater resourceUpdater, string name, string url, string nameToUseInLog)381 private void AddStringResourceForUrl(ResourceUpdater resourceUpdater, string name, string url, string nameToUseInLog) 382 { 383 if (!String.IsNullOrEmpty(url)) 384 { 385 resourceUpdater.AddStringResource(40, name, url); 386 if (!Util.IsWebUrl(url) && !Util.IsUncPath(url)) 387 { 388 _results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.InvalidUrl", nameToUseInLog, url)); 389 } 390 } 391 } 392 393 #endregion 394 395 /// <summary> 396 /// Returns the directories bootstrapper component files would be copied to when built given the specified settings 397 /// </summary> 398 /// <param name="productCodes">The productCodes of the selected components</param> 399 /// <param name="culture">The culture used to build the bootstrapper</param> 400 /// <param name="fallbackCulture">The fallback culture used to build the bootstrapper</param> 401 /// <param name="componentsLocation">How the bootstrapper would package the selected components</param> 402 /// <returns></returns> GetOutputFolders(string[] productCodes, string culture, string fallbackCulture, ComponentsLocation componentsLocation)403 public string[] GetOutputFolders(string[] productCodes, string culture, string fallbackCulture, ComponentsLocation componentsLocation) 404 { 405 if (!_fInitialized) 406 { 407 Refresh(); 408 } 409 410 Hashtable folders = new Hashtable(); 411 BuildSettings settings = new BuildSettings(); 412 string invariantPath = PackagePath.ToLowerInvariant(); 413 invariantPath = Util.AddTrailingChar(invariantPath, System.IO.Path.DirectorySeparatorChar); 414 settings.CopyComponents = false; 415 settings.Culture = culture; 416 settings.FallbackCulture = fallbackCulture; 417 settings.ComponentsLocation = componentsLocation; 418 if (String.IsNullOrEmpty(settings.Culture) || settings.Culture == "*") 419 { 420 settings.Culture = settings.FallbackCulture; 421 } 422 423 foreach (string productCode in productCodes) 424 { 425 Product product = Products.Product(productCode); 426 if (product == null) 427 continue; 428 settings.ProductBuilders.Add(product.ProductBuilder); 429 } 430 ArrayList files = new ArrayList(); 431 432 BuildPackages(settings, null, null, files, null); 433 434 foreach (string file in files) 435 { 436 string folder = System.IO.Path.GetDirectoryName(file); 437 if (folder.Substring(0, invariantPath.Length).ToLowerInvariant().CompareTo(invariantPath) == 0) 438 { 439 string relPath = folder.Substring(invariantPath.Length).ToLowerInvariant(); 440 if (!folders.Contains(relPath)) 441 folders.Add(relPath, relPath); 442 } 443 } 444 445 ArrayList list = new ArrayList(folders.Values); 446 string[] a = new string[list.Count]; 447 list.CopyTo(a, 0); 448 return a; 449 } 450 ContainsCulture(string culture)451 internal bool ContainsCulture(string culture) 452 { 453 if (!_fInitialized) 454 Refresh(); 455 return _cultures.Contains(culture); 456 } 457 458 internal string[] Cultures 459 { 460 get 461 { 462 if (!_fInitialized) 463 Refresh(); 464 465 ArrayList list = new ArrayList(_cultures.Values); 466 list.Sort(); 467 string[] a = new string[list.Count]; 468 list.CopyTo(a, 0); 469 return a; 470 } 471 } 472 473 internal bool Validate 474 { 475 get { return _fValidate; } 476 set { _fValidate = value; } 477 } 478 479 private string BootstrapperPath 480 { 481 get { return System.IO.Path.Combine(Path, ENGINE_PATH); } 482 } 483 484 private string PackagePath 485 { 486 get { return System.IO.Path.Combine(Path, PACKAGE_PATH); } 487 } 488 489 private string SchemaPath 490 { 491 get { return System.IO.Path.Combine(Path, SCHEMA_PATH); } 492 } 493 Refresh()494 private void Refresh() 495 { 496 RefreshResources(); 497 RefreshProducts(); 498 _fInitialized = true; 499 500 if (s_logging) 501 { 502 StringBuilder productsOrder = new StringBuilder(); 503 foreach (Product p in Products) 504 { 505 productsOrder.Append(p.ProductCode + Environment.NewLine); 506 } 507 DumpStringToFile(productsOrder.ToString(), "BootstrapperInstallOrder.txt", false); 508 } 509 } 510 RefreshResources()511 private void RefreshResources() 512 { 513 string startDirectory = System.IO.Path.Combine(BootstrapperPath, RESOURCES_PATH); 514 _cultures.Clear(); 515 516 if (Directory.Exists(startDirectory)) 517 { 518 foreach (string subDirectory in Directory.GetDirectories(startDirectory)) 519 { 520 string resourceDirectory = System.IO.Path.Combine(startDirectory, subDirectory); 521 string resourceFile = System.IO.Path.Combine(resourceDirectory, SETUP_RESOURCES_FILE); 522 if (File.Exists(resourceFile)) 523 { 524 XmlDocument resourceDoc = new XmlDocument(); 525 try 526 { 527 XmlReaderSettings xrs = new XmlReaderSettings(); 528 xrs.DtdProcessing = DtdProcessing.Ignore; 529 using (XmlReader xr = XmlReader.Create(resourceFile, xrs)) 530 { 531 resourceDoc.Load(xr); 532 } 533 } 534 catch (XmlException ex) 535 { 536 // UNDONE: Log exception due to bad resource file 537 Debug.Fail(ex.Message); 538 continue; 539 } 540 541 XmlNode rootNode = resourceDoc.SelectSingleNode("Resources"); 542 if (rootNode != null) 543 { 544 XmlAttribute cultureAttribute = (XmlAttribute)rootNode.Attributes.GetNamedItem("Culture"); 545 if (cultureAttribute != null) 546 { 547 XmlNode stringsNode = rootNode.SelectSingleNode("Strings"); 548 if (stringsNode != null) 549 { 550 XmlNode stringNode = stringsNode.SelectSingleNode(string.Format(CultureInfo.InvariantCulture, "String[@Name='{0}']", cultureAttribute.Value)); 551 if (stringNode != null) 552 { 553 string culture = stringNode.InnerText; 554 555 XmlNode resourcesNode = rootNode.OwnerDocument.ImportNode(rootNode, true); 556 resourcesNode.Attributes.RemoveNamedItem("Culture"); 557 XmlAttribute newAttribute = (XmlAttribute)rootNode.OwnerDocument.ImportNode(cultureAttribute, false); 558 newAttribute.Value = stringNode.InnerText; 559 resourcesNode.Attributes.Append(newAttribute); 560 if (!_cultures.Contains(culture.ToLowerInvariant())) 561 _cultures.Add(culture.ToLowerInvariant(), resourcesNode); 562 else 563 Debug.Fail("Already found resources for culture " + stringNode.InnerText); 564 } 565 } 566 } 567 } 568 } 569 } 570 } 571 } 572 RefreshProducts()573 private void RefreshProducts() 574 { 575 _products.Clear(); 576 _validationResults.Clear(); 577 _document = new XmlDocument(); 578 _xmlNamespaceManager = new XmlNamespaceManager(_document.NameTable); 579 580 _xmlNamespaceManager.AddNamespace(BOOTSTRAPPER_PREFIX, BOOTSTRAPPER_NAMESPACE); 581 582 XmlElement rootElement = _document.CreateElement("Products", BOOTSTRAPPER_NAMESPACE); 583 string packagePath = PackagePath; 584 585 if (Directory.Exists(packagePath)) 586 { 587 foreach (string strSubDirectory in Directory.GetDirectories(packagePath)) 588 { 589 int nStartIndex = packagePath.Length; 590 if ((strSubDirectory.ToCharArray())[nStartIndex] == System.IO.Path.DirectorySeparatorChar) 591 { 592 nStartIndex = nStartIndex + 1; 593 } 594 ExploreDirectory(strSubDirectory.Substring(nStartIndex), rootElement); 595 } 596 } 597 598 _document.AppendChild(rootElement); 599 600 Hashtable availableProducts = new Hashtable(); 601 // A second copy of all the project which will get destroyed during the generation of the build order 602 Hashtable buildQueue = new Hashtable(); 603 604 XmlNodeList productsFound = rootElement.SelectNodes(BOOTSTRAPPER_PREFIX + ":Product", _xmlNamespaceManager); 605 foreach (XmlNode productNode in productsFound) 606 { 607 Product p = CreateProduct(productNode); 608 if (p != null) 609 { 610 availableProducts.Add(p.ProductCode, p); 611 buildQueue.Add(p.ProductCode, CreateProduct(productNode)); 612 } 613 } 614 615 // Set the product and included products for each product 616 foreach (Product p in availableProducts.Values) 617 { 618 AddDependencies(p, availableProducts); 619 AddIncludes(p, availableProducts); 620 } 621 622 // We need only the dependencies to generate the bulid order 623 foreach (Product p in buildQueue.Values) 624 { 625 AddDependencies(p, buildQueue); 626 } 627 628 // Scan the products and their dependencies to calculate install order 629 OrderProducts(availableProducts, buildQueue); 630 } 631 AddDependencies(Product p, Hashtable availableProducts)632 private void AddDependencies(Product p, Hashtable availableProducts) 633 { 634 foreach (string relatedProductCode in SelectRelatedProducts(p, "DependsOnProduct")) 635 { 636 if (availableProducts.Contains(relatedProductCode)) 637 { 638 p.AddDependentProduct((Product)availableProducts[relatedProductCode]); 639 } 640 else 641 { 642 ArrayList missingDependencies = new ArrayList(); 643 missingDependencies.Add(relatedProductCode); 644 p.AddMissingDependency(missingDependencies); 645 } 646 } 647 648 foreach (XmlNode eitherProductNode in SelectEitherProducts(p)) 649 { 650 List<Product> foundDependencies = new List<Product>(); 651 ArrayList allDependencies = new ArrayList(); 652 653 foreach (XmlNode relatedProductNode in eitherProductNode.SelectNodes(String.Format(CultureInfo.InvariantCulture, "{0}:DependsOnProduct", BOOTSTRAPPER_PREFIX), _xmlNamespaceManager)) 654 { 655 XmlAttribute relatedProductAttribute = (XmlAttribute)(relatedProductNode.Attributes.GetNamedItem("Code")); 656 if (relatedProductAttribute != null) 657 { 658 string dependency = relatedProductAttribute.Value; 659 if (availableProducts.Contains(dependency)) 660 { 661 foundDependencies.Add((Product)availableProducts[dependency]); 662 } 663 allDependencies.Add(dependency); 664 } 665 } 666 667 if (foundDependencies.Count > 0) 668 { 669 if (!p.ContainsDependencies(foundDependencies)) 670 { 671 p.Dependencies.Add(foundDependencies); 672 } 673 } 674 else if (allDependencies.Count > 0) 675 { 676 p.AddMissingDependency(allDependencies); 677 } 678 } 679 } 680 AddIncludes(Product p, Hashtable availableProducts)681 private void AddIncludes(Product p, Hashtable availableProducts) 682 { 683 foreach (string relatedProductCode in SelectRelatedProducts(p, "IncludesProduct")) 684 { 685 if (availableProducts.Contains(relatedProductCode)) 686 { 687 p.Includes.Add((Product)availableProducts[relatedProductCode]); 688 } 689 } 690 } 691 SelectRelatedProducts(Product p, string nodeName)692 private string[] SelectRelatedProducts(Product p, string nodeName) 693 { 694 ArrayList list = new ArrayList(); 695 696 XmlNodeList relatedProducts = p.Node.SelectNodes(string.Format(CultureInfo.InvariantCulture, "{0}:Package/{1}:RelatedProducts/{2}:{3}", BOOTSTRAPPER_PREFIX, BOOTSTRAPPER_PREFIX, BOOTSTRAPPER_PREFIX, nodeName), _xmlNamespaceManager); 697 if (relatedProducts != null) 698 { 699 foreach (XmlNode relatedProduct in relatedProducts) 700 { 701 XmlAttribute relatedProductAttribute = (XmlAttribute)(relatedProduct.Attributes.GetNamedItem("Code")); 702 if (relatedProductAttribute != null) 703 { 704 list.Add(relatedProductAttribute.Value); 705 } 706 } 707 } 708 709 string[] a = new string[list.Count]; 710 list.CopyTo(a, 0); 711 return a; 712 } 713 SelectEitherProducts(Product p)714 private XmlNodeList SelectEitherProducts(Product p) 715 { 716 XmlNodeList eitherProducts = p.Node.SelectNodes(string.Format(CultureInfo.InvariantCulture, "{0}:Package/{1}:RelatedProducts/{2}:EitherProducts", BOOTSTRAPPER_PREFIX, BOOTSTRAPPER_PREFIX, BOOTSTRAPPER_PREFIX), _xmlNamespaceManager); 717 return eitherProducts; 718 } 719 OrderProducts(Hashtable availableProducts, Hashtable buildQueue)720 private void OrderProducts(Hashtable availableProducts, Hashtable buildQueue) 721 { 722 bool loopDetected = false; 723 _loopDependenciesWarnings = new BuildResults(); 724 StringBuilder productsInLoop = new StringBuilder(); 725 while (buildQueue.Count > 0) 726 { 727 List<string> productsToRemove = new List<string>(); 728 foreach (Product p in buildQueue.Values) 729 { 730 if (p.Dependencies.Count == 0) 731 { 732 _products.Add((Product)availableProducts[p.ProductCode]); 733 RemoveDependency(buildQueue, p); 734 productsToRemove.Add(p.ProductCode); 735 } 736 } 737 738 foreach (string productCode in productsToRemove) 739 { 740 buildQueue.Remove(productCode); 741 if (loopDetected) 742 { 743 productsInLoop.Append(productCode); 744 productsInLoop.Append(", "); 745 } 746 } 747 748 // If we could not remove any products and there are still products in the queue 749 // there must be a loop in it. We'll break the loop by removing the dependencies 750 // of the first project in the queue; 751 if (buildQueue.Count > 0 && productsToRemove.Count == 0) 752 { 753 IDictionaryEnumerator enumerator = buildQueue.GetEnumerator(); 754 enumerator.MoveNext(); 755 ((Product)enumerator.Value).Dependencies.RemoveAll(m => true); 756 loopDetected = true; 757 } 758 759 // If we've been in a loop and there are no more products left 760 // or no more products can be installed, we have completely walked that loop 761 // and now is a good time to show the warning message for the loop 762 if (productsInLoop.Length > 0 && (buildQueue.Count == 0 || productsToRemove.Count == 0)) 763 { 764 productsInLoop.Remove(productsInLoop.Length - 2, 2); 765 _loopDependenciesWarnings.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.CircularDependency", productsInLoop.ToString())); 766 productsInLoop.Remove(0, productsInLoop.Length); 767 } 768 } 769 } 770 RemoveDependency(Hashtable availableProducts, Product product)771 private void RemoveDependency(Hashtable availableProducts, Product product) 772 { 773 foreach (Product p in availableProducts.Values) 774 { 775 foreach (List<Product> dependency in p.Dependencies) 776 { 777 dependency.RemoveAll(m => m == product); 778 } 779 p.Dependencies.RemoveAll(m => m.Count == 0); 780 } 781 } 782 LoadAndValidateXmlDocument(string filePath, bool validateFilePresent, string schemaPath, string schemaNamespace, XmlValidationResults results)783 private XmlDocument LoadAndValidateXmlDocument(string filePath, bool validateFilePresent, string schemaPath, string schemaNamespace, XmlValidationResults results) 784 { 785 XmlDocument xmlDocument = null; 786 787 Debug.Assert(filePath != null, "null filePath?"); 788 Debug.Assert(schemaPath != null, "null schemaPath?"); 789 Debug.Assert(schemaNamespace != null, "null schemaNamespace?"); 790 791 if ((filePath != null) && (schemaPath != null) && (schemaNamespace != null)) 792 { 793 // set up our validation logic by detecting the trace-switch enabled and whether or 794 // not our files exist. 795 bool validate = true; 796 bool fileExists = File.Exists(filePath); 797 bool schemaExists = File.Exists(schemaPath); 798 799 // if we're being asked to validate but we can't find the schema file, then 800 // output something useful to tell user that we can't find the schema. 801 if (!schemaExists) 802 { 803 Debug.Fail("Could not locate schema '" + schemaPath + "', so no validation of '" + filePath + "' is possible."); 804 validate = false; 805 } 806 807 // if we're being asked to validate but we can't find the data file, then 808 // output something useful to tell user that we can't find the file and that we 809 // can't do anything useful. 810 if (validate && (!fileExists) && validateFilePresent) 811 { 812 Debug.Fail("Could not locate data file '" + filePath + "'."); 813 validate = false; 814 } 815 816 if (fileExists) 817 { 818 XmlTextReader xmlTextReader = new XmlTextReader(filePath); 819 xmlTextReader.DtdProcessing = DtdProcessing.Ignore; 820 821 XmlReader xmlReader = xmlTextReader; 822 823 if (validate) 824 { 825 #pragma warning disable 618 // Using XmlValidatingReader. TODO: We need to switch to using XmlReader.Create() with validation. 826 var validatingReader = new XmlValidatingReader(xmlReader); 827 #pragma warning restore 618 828 XmlReaderSettings xrSettings = new XmlReaderSettings(); 829 xrSettings.DtdProcessing = DtdProcessing.Ignore; 830 using (XmlReader xr = XmlReader.Create(schemaPath, xrSettings)) 831 { 832 try 833 { 834 // first, add our schema to the validating reader's collection of schemas 835 var xmlSchema = validatingReader.Schemas.Add(null, xr); 836 837 // if our schema namespace gets out of sync, 838 // then all of our calls to SelectNodes and SelectSingleNode will fail 839 Debug.Assert((xmlSchema != null) && 840 string.Equals(schemaNamespace, xmlSchema.TargetNamespace, StringComparison.Ordinal), 841 System.IO.Path.GetFileName(schemaPath) + " and BootstrapperBuilder.vb have mismatched namespaces, so the BootstrapperBuilder will fail to work."); 842 843 // if we're supposed to be validating, then hook up our handler 844 validatingReader.ValidationEventHandler += results.SchemaValidationEventHandler; 845 846 // switch readers so the doc does the actual read over the validating 847 // reader so we get validation events as we load the document 848 xmlReader = validatingReader; 849 } 850 catch (XmlException ex) 851 { 852 Debug.Fail("Failed to load schema '" + schemaPath + "' due to the following exception:\r\n" + ex.Message); 853 validate = false; 854 } 855 catch (System.Xml.Schema.XmlSchemaException ex) 856 { 857 Debug.Fail("Failed to load schema '" + schemaPath + "' due to the following exception:\r\n" + ex.Message); 858 validate = false; 859 } 860 } 861 } 862 863 try 864 { 865 Debug.Assert(_document != null, "our document should have been created by now!"); 866 xmlDocument = new XmlDocument(_document.NameTable); 867 xmlDocument.Load(xmlReader); 868 } 869 catch (XmlException ex) 870 { 871 Debug.Fail("Failed to load document '" + filePath + "' due to the following exception:\r\n" + ex.Message); 872 return null; 873 } 874 catch (System.Xml.Schema.XmlSchemaException ex) 875 { 876 Debug.Fail("Failed to load document '" + filePath + "' due to the following exception:\r\n" + ex.Message); 877 return null; 878 } 879 finally 880 { 881 xmlReader.Close(); 882 } 883 884 // Note that the xml document's default namespace must match the schema namespace 885 // or none of our SelectNodes/SelectSingleNode calls will succeed 886 Debug.Assert(xmlDocument.DocumentElement != null && 887 string.Equals(xmlDocument.DocumentElement.NamespaceURI, schemaNamespace, StringComparison.Ordinal), 888 "'" + xmlDocument.DocumentElement.NamespaceURI + "' is not '" + schemaNamespace + "'..."); 889 890 if ((xmlDocument.DocumentElement == null) || 891 (!string.Equals(xmlDocument.DocumentElement.NamespaceURI, schemaNamespace, StringComparison.Ordinal))) 892 { 893 } 894 } 895 } 896 897 return xmlDocument; 898 } 899 ExploreDirectory(string strSubDirectory, XmlElement rootElement)900 private void ExploreDirectory(string strSubDirectory, XmlElement rootElement) 901 { 902 try 903 { 904 string packagePath = PackagePath; 905 string strSubDirectoryFullPath = System.IO.Path.Combine(packagePath, strSubDirectory); 906 907 // figure out our product file paths based on the directory full path 908 string strBaseManifestFilename = System.IO.Path.Combine(strSubDirectoryFullPath, ROOT_MANIFEST_FILE); 909 string strBaseManifestSchemaFileName = System.IO.Path.Combine(SchemaPath, MANIFEST_FILE_SCHEMA); 910 911 ProductValidationResults productValidationResults = new ProductValidationResults(strBaseManifestFilename); 912 913 // open the XmlDocument for this product.xml 914 XmlDocument productDoc = LoadAndValidateXmlDocument(strBaseManifestFilename, false, strBaseManifestSchemaFileName, BOOTSTRAPPER_NAMESPACE, productValidationResults); 915 if (productDoc != null) 916 { 917 bool packageAdded = false; 918 919 XmlNode baseNode = productDoc.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":Product", _xmlNamespaceManager); 920 if (baseNode != null) 921 { 922 // Get the ProductCode attribute for this product 923 XmlAttribute productCodeAttribute = (XmlAttribute)(baseNode.Attributes.GetNamedItem("ProductCode")); 924 if (productCodeAttribute != null) 925 { 926 // now add it to our full document if it's not already present 927 XmlNode productNode = rootElement.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":Product[@ProductCode='" + productCodeAttribute.Value + "']", _xmlNamespaceManager); 928 if (productNode == null) 929 { 930 productNode = CreateProductNode(baseNode); 931 } 932 else 933 { 934 productValidationResults = (ProductValidationResults)_validationResults[productCodeAttribute]; 935 } 936 937 // Fix-up the <PackageFiles> of the base node to include the SourcePath and TargetPath 938 XmlNode packageFilesNode = baseNode.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":PackageFiles", _xmlNamespaceManager); 939 XmlNode checksNode = baseNode.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":InstallChecks", _xmlNamespaceManager); 940 XmlNode commandsNode = baseNode.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":Commands", _xmlNamespaceManager); 941 942 // if there was a packageFiles node, then add it in to our full document with the rest 943 if (packageFilesNode != null) 944 { 945 UpdatePackageFileNodes(packageFilesNode, System.IO.Path.Combine(packagePath, strSubDirectory), strSubDirectory); 946 947 ReplacePackageFileAttributes(checksNode, "PackageFile", packageFilesNode, "PackageFile", "OldName", "Name"); 948 ReplacePackageFileAttributes(commandsNode, "PackageFile", packageFilesNode, "PackageFile", "OldName", "Name"); 949 ReplacePackageFileAttributes(baseNode, EULA_ATTRIBUTE, packageFilesNode, "PackageFile", "OldName", "SourcePath"); 950 } 951 952 foreach (string strLanguageDirectory in Directory.GetDirectories(strSubDirectoryFullPath)) 953 { 954 // The base node would get destroyed as we build-up this new node. 955 // Thus, we want to use a copy of the baseNode 956 XmlElement baseElement = (XmlElement)(_document.ImportNode(baseNode, true)); 957 958 string strLangManifestFilename = System.IO.Path.Combine(strLanguageDirectory, CHILD_MANIFEST_FILE); 959 string strLangManifestSchemaFileName = System.IO.Path.Combine(SchemaPath, MANIFEST_FILE_SCHEMA); 960 961 if (File.Exists(strLangManifestFilename)) 962 { 963 // Load Package.xml 964 XmlValidationResults packageValidationResults = new XmlValidationResults(strLangManifestFilename); 965 XmlDocument langDoc = LoadAndValidateXmlDocument(strLangManifestFilename, false, strLangManifestSchemaFileName, BOOTSTRAPPER_NAMESPACE, packageValidationResults); 966 967 Debug.Assert(langDoc != null, "we couldn't load package.xml in '" + strLangManifestFilename + "'...?"); 968 if (langDoc == null) 969 continue; 970 971 XmlNode langNode = langDoc.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":Package", _xmlNamespaceManager); 972 Debug.Assert(langNode != null, string.Format(CultureInfo.CurrentCulture, "Unable to find a package node in {0}", strLangManifestFilename)); 973 if (langNode != null) 974 { 975 XmlElement langElement = (XmlElement)(_document.ImportNode(langNode, true)); 976 XmlElement mergeElement = _document.CreateElement("Package", BOOTSTRAPPER_NAMESPACE); 977 978 // Update the "PackageFiles" section to reflect this language subdirectory 979 XmlNode packageFilesNodePackage = langElement.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":PackageFiles", _xmlNamespaceManager); 980 checksNode = langElement.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":InstallChecks", _xmlNamespaceManager); 981 commandsNode = langElement.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":Commands", _xmlNamespaceManager); 982 983 if (packageFilesNodePackage != null) 984 { 985 int nStartIndex = packagePath.Length; 986 987 if ((strLanguageDirectory.ToCharArray())[nStartIndex] == System.IO.Path.DirectorySeparatorChar) 988 nStartIndex++; 989 UpdatePackageFileNodes(packageFilesNodePackage, strLanguageDirectory, strSubDirectory); 990 991 ReplacePackageFileAttributes(checksNode, "PackageFile", packageFilesNodePackage, "PackageFile", "OldName", "Name"); 992 ReplacePackageFileAttributes(commandsNode, "PackageFile", packageFilesNodePackage, "PackageFile", "OldName", "Name"); 993 ReplacePackageFileAttributes(langElement, EULA_ATTRIBUTE, packageFilesNodePackage, "PackageFile", "OldName", "SourcePath"); 994 } 995 996 if (packageFilesNode != null) 997 { 998 ReplacePackageFileAttributes(checksNode, "PackageFile", packageFilesNode, "PackageFile", "OldName", "Name"); 999 ReplacePackageFileAttributes(commandsNode, "PackageFile", packageFilesNode, "PackageFile", "OldName", "Name"); 1000 ReplacePackageFileAttributes(langElement, EULA_ATTRIBUTE, packageFilesNode, "PackageFile", "OldName", "SourcePath"); 1001 } 1002 1003 // in general, we prefer the attributes of the language document over the 1004 // attributes of the base document. Copy attributes from the lang to the merged, 1005 // and then merge all unique elements into merge 1006 foreach (XmlAttribute attribute in langElement.Attributes) 1007 { 1008 mergeElement.Attributes.Append((XmlAttribute)(mergeElement.OwnerDocument.ImportNode(attribute, false))); 1009 } 1010 1011 foreach (XmlAttribute attribute in baseElement.Attributes) 1012 { 1013 XmlAttribute convertedAttribute = (XmlAttribute)(mergeElement.OwnerDocument.ImportNode(attribute, false)); 1014 MergeAttribute(mergeElement, convertedAttribute); 1015 } 1016 1017 // And append all of the nodes 1018 // There is a well-known set of nodes which may have inherit children 1019 // When merging these nodes, there may be subnodes taken from both the lang element and the base element. 1020 // There will never be multiple nodes with the same name in the same manifest 1021 // The function which performs this action is CombineElements(...) 1022 CombineElements(langElement, baseElement, "Commands", "PackageFile", mergeElement); 1023 CombineElements(langElement, baseElement, "InstallChecks", "Property", mergeElement); 1024 CombineElements(langElement, baseElement, "PackageFiles", "Name", mergeElement); 1025 CombineElements(langElement, baseElement, "Schedules", "Name", mergeElement); 1026 CombineElements(langElement, baseElement, "Strings", "Name", mergeElement); 1027 1028 ReplaceStrings(mergeElement); 1029 CorrectPackageFiles(mergeElement); 1030 1031 AppendNode(baseElement, "RelatedProducts", mergeElement); 1032 1033 // Create a unique identifier for this package 1034 XmlAttribute cultureAttribute = (XmlAttribute)mergeElement.Attributes.GetNamedItem("Culture"); 1035 if (cultureAttribute != null && !String.IsNullOrEmpty(cultureAttribute.Value)) 1036 { 1037 string packageCode = productCodeAttribute.Value + "." + cultureAttribute.Value; 1038 AddAttribute(mergeElement, "PackageCode", packageCode); 1039 1040 if (productValidationResults != null && packageValidationResults != null) 1041 { 1042 productValidationResults.AddPackageResults(cultureAttribute.Value, packageValidationResults); 1043 } 1044 1045 // Only add this package if there is a culture apecified. 1046 productNode.AppendChild(mergeElement); 1047 packageAdded = true; 1048 } 1049 } 1050 } 1051 } 1052 if (packageAdded) 1053 { 1054 rootElement.AppendChild(productNode); 1055 if (!_validationResults.Contains(productCodeAttribute.Value)) 1056 { 1057 _validationResults.Add(productCodeAttribute.Value, productValidationResults); 1058 } 1059 else 1060 { 1061 Debug.WriteLine(String.Format(CultureInfo.CurrentCulture, "Validation results already added for Product Code '{0}'", productCodeAttribute)); 1062 } 1063 } 1064 } 1065 } 1066 } 1067 } 1068 catch (XmlException ex) 1069 { 1070 Debug.Fail(ex.Message); 1071 } 1072 catch (System.IO.IOException ex) 1073 { 1074 Debug.Fail(ex.Message); 1075 } 1076 catch (ArgumentException ex) 1077 { 1078 Debug.Fail(ex.Message); 1079 } 1080 } 1081 CreateProduct(XmlNode node)1082 private Product CreateProduct(XmlNode node) 1083 { 1084 bool fPackageAdded = false; 1085 string productCode = ReadAttribute(node, "ProductCode"); 1086 Product product = null; 1087 if (!String.IsNullOrEmpty(productCode)) 1088 { 1089 ProductValidationResults results = (ProductValidationResults)_validationResults[productCode]; 1090 1091 XmlNode packageFilesNode = node.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":Package/" + BOOTSTRAPPER_PREFIX + ":PackageFiles", _xmlNamespaceManager); 1092 string copyAllPackageFiles = String.Empty; 1093 1094 if (packageFilesNode != null) copyAllPackageFiles = ReadAttribute(packageFilesNode, "CopyAllPackageFiles"); 1095 1096 product = new Product(node, productCode, results, copyAllPackageFiles); 1097 XmlNodeList packageNodeList = node.SelectNodes(BOOTSTRAPPER_PREFIX + ":Package", _xmlNamespaceManager); 1098 1099 foreach (XmlNode packageNode in packageNodeList) 1100 { 1101 Package package = CreatePackage(packageNode, product); 1102 if (package != null) 1103 { 1104 product.AddPackage(package); 1105 fPackageAdded = true; 1106 } 1107 } 1108 } 1109 1110 if (fPackageAdded) 1111 return product; 1112 return null; 1113 } 1114 CreatePackage(XmlNode node, Product product)1115 private Package CreatePackage(XmlNode node, Product product) 1116 { 1117 string culture = ReadAttribute(node, "Culture"); 1118 1119 XmlValidationResults results = null; 1120 if (culture != null) 1121 { 1122 results = product.GetPackageValidationResults(culture); 1123 } 1124 else 1125 { 1126 return null; 1127 } 1128 1129 return new Package(product, node, results, ReadAttribute(node, "Name"), ReadAttribute(node, "Culture")); 1130 } 1131 ReplaceAttributes(XmlNode targetNode, string attributeName, string oldValue, string newValue)1132 private void ReplaceAttributes(XmlNode targetNode, string attributeName, string oldValue, string newValue) 1133 { 1134 if (targetNode != null) 1135 { 1136 // select all nodes where the attributeName equals the oldValue 1137 XmlNodeList nodeList = targetNode.SelectNodes(BOOTSTRAPPER_PREFIX + string.Format(CultureInfo.InvariantCulture, ":*[@{0}='{1}']", attributeName, oldValue), _xmlNamespaceManager); 1138 1139 foreach (XmlNode node in nodeList) 1140 { 1141 ReplaceAttribute(node, attributeName, newValue); 1142 } 1143 1144 // replace attributes on the node itself 1145 XmlAttribute attrib = targetNode.Attributes[attributeName]; 1146 if (attrib != null && attrib.Value == oldValue) 1147 attrib.Value = newValue; 1148 } 1149 } 1150 ReplaceAttribute(XmlNode targetNode, string attributeName, string attributeValue)1151 private void ReplaceAttribute(XmlNode targetNode, string attributeName, string attributeValue) 1152 { 1153 XmlAttribute attribute = targetNode.OwnerDocument.CreateAttribute(attributeName); 1154 attribute.Value = attributeValue; 1155 targetNode.Attributes.SetNamedItem(attribute); 1156 } 1157 MergeAttribute(XmlNode targetNode, XmlAttribute attribute)1158 private void MergeAttribute(XmlNode targetNode, XmlAttribute attribute) 1159 { 1160 XmlAttribute targetAttribute = (XmlAttribute)(targetNode.Attributes.GetNamedItem(attribute.Name)); 1161 if (targetAttribute == null) 1162 { 1163 // This node does not already contain the attribute. Add the parameter 1164 targetNode.Attributes.Append(attribute); 1165 } 1166 } 1167 UpdatePackageFileNodes(XmlNode packageFilesNode, string strSourcePath, string strTargetPath)1168 private void UpdatePackageFileNodes(XmlNode packageFilesNode, string strSourcePath, string strTargetPath) 1169 { 1170 XmlNodeList packageFileNodeList = packageFilesNode.SelectNodes(BOOTSTRAPPER_PREFIX + ":PackageFile", _xmlNamespaceManager); 1171 1172 foreach (XmlNode packageFileNode in packageFileNodeList) 1173 { 1174 XmlAttribute nameAttribute = (XmlAttribute)(packageFileNode.Attributes.GetNamedItem("Name")); 1175 1176 // the name attribute is required -- we can't do anything if it's not present 1177 if (nameAttribute != null) 1178 { 1179 string relativePath = nameAttribute.Value; 1180 1181 XmlAttribute sourcePathAttribute = packageFilesNode.OwnerDocument.CreateAttribute("SourcePath"); 1182 string strSourceFile = System.IO.Path.Combine(strSourcePath, relativePath); 1183 sourcePathAttribute.Value = strSourceFile; 1184 1185 XmlAttribute targetPathAttribute = packageFilesNode.OwnerDocument.CreateAttribute("TargetPath"); 1186 targetPathAttribute.Value = System.IO.Path.Combine(strTargetPath, relativePath); 1187 1188 string oldNameValue = nameAttribute.Value; 1189 string newNameValue = System.IO.Path.Combine(strTargetPath, relativePath); 1190 1191 XmlAttribute oldNameAttribute = packageFilesNode.OwnerDocument.CreateAttribute("OldName"); 1192 oldNameAttribute.Value = oldNameValue; 1193 1194 ReplaceAttribute(packageFileNode, "Name", newNameValue); 1195 MergeAttribute(packageFileNode, sourcePathAttribute); 1196 MergeAttribute(packageFileNode, targetPathAttribute); 1197 MergeAttribute(packageFileNode, oldNameAttribute); 1198 } 1199 } 1200 } 1201 AppendNode(XmlElement element, string nodeName, XmlElement mergeElement)1202 private void AppendNode(XmlElement element, string nodeName, XmlElement mergeElement) 1203 { 1204 XmlNode node = element.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":" + nodeName, _xmlNamespaceManager); 1205 if (node != null) 1206 { 1207 mergeElement.AppendChild(node); 1208 } 1209 } 1210 CombineElements(XmlElement langElement, XmlElement baseElement, string strNodeName, string strSubNodeKey, XmlElement mergeElement)1211 private void CombineElements(XmlElement langElement, XmlElement baseElement, string strNodeName, string strSubNodeKey, XmlElement mergeElement) 1212 { 1213 XmlNode langNode = langElement.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":" + strNodeName, _xmlNamespaceManager); 1214 XmlNode baseNode = baseElement.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":" + strNodeName, _xmlNamespaceManager); 1215 1216 // There are 4 basic cases to be dealt with: 1217 // Case # 1 2 3 4 1218 // base null null present present 1219 // lang null present null present 1220 // Result null lang base combine 1221 // 1222 // Cases 1 - 3 are pretty trivial. 1223 if (baseNode == null) 1224 { 1225 if (langNode != null) 1226 { 1227 // Case 2 1228 mergeElement.AppendChild(langNode); 1229 } 1230 // Case 1 is to do nothing 1231 } 1232 else 1233 { 1234 if (langNode == null) 1235 { 1236 // Case 3 1237 mergeElement.AppendChild(baseNode); 1238 } 1239 else 1240 { 1241 XmlNode mergeSubNode = _document.CreateElement(strNodeName, BOOTSTRAPPER_NAMESPACE); 1242 XmlNode nextNode = baseNode.FirstChild; 1243 1244 // Begin case 4 1245 // Go through every element in the base node 1246 while (nextNode != null) 1247 { 1248 if (nextNode.NodeType == XmlNodeType.Element) 1249 { 1250 XmlAttribute keyAttribute = (XmlAttribute)(nextNode.Attributes.GetNamedItem(strSubNodeKey)); 1251 if (keyAttribute != null) 1252 { 1253 XmlNode queryResultNode = QueryForSubNode(langNode, strSubNodeKey, keyAttribute.Value); 1254 // if there is no match in the lang node, use the current base node 1255 // Otherwise use that node and remove it later 1256 if (queryResultNode == null) 1257 { 1258 mergeSubNode.AppendChild(mergeSubNode.OwnerDocument.ImportNode(nextNode, true)); 1259 } 1260 else 1261 { 1262 mergeSubNode.AppendChild(mergeSubNode.OwnerDocument.ImportNode(queryResultNode, true)); 1263 langNode.RemoveChild(queryResultNode); 1264 } 1265 } 1266 else 1267 { 1268 Debug.Assert(false, "Specified key does not exist for node " + nextNode.InnerXml); 1269 } 1270 } 1271 nextNode = nextNode.NextSibling; 1272 } 1273 1274 // Append all remaining lang nodes 1275 nextNode = langNode.FirstChild; 1276 1277 while (nextNode != null) 1278 { 1279 mergeSubNode.AppendChild(mergeSubNode.OwnerDocument.ImportNode(nextNode, true)); 1280 nextNode = nextNode.NextSibling; 1281 } 1282 1283 // Copy all attributes. The langnode again has priority 1284 foreach (XmlAttribute attribute in langNode.Attributes) 1285 { 1286 AddAttribute(mergeSubNode, attribute.Name, attribute.Value); 1287 } 1288 foreach (XmlAttribute attribute in baseNode.Attributes) 1289 { 1290 if (mergeSubNode.Attributes.GetNamedItem(attribute.Name) == null) 1291 { 1292 AddAttribute(mergeSubNode, attribute.Name, attribute.Value); 1293 } 1294 } 1295 1296 mergeElement.AppendChild(mergeSubNode); 1297 } 1298 } 1299 } 1300 QueryForSubNode(XmlNode subNode, string strSubNodeKey, string strTargetValue)1301 private XmlNode QueryForSubNode(XmlNode subNode, string strSubNodeKey, string strTargetValue) 1302 { 1303 string strQuery = string.Format(CultureInfo.InvariantCulture, "{0}:*[@{1}='{2}']", BOOTSTRAPPER_PREFIX, strSubNodeKey, strTargetValue); 1304 return subNode.SelectSingleNode(strQuery, _xmlNamespaceManager); 1305 } 1306 CorrectPackageFiles(XmlNode node)1307 private void CorrectPackageFiles(XmlNode node) 1308 { 1309 XmlNode packageFilesNode = node.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":PackageFiles", _xmlNamespaceManager); 1310 1311 if (packageFilesNode != null) 1312 { 1313 // Map all StringKey attributes to corresponding String values 1314 XmlNodeList packageFileNodeList = node.SelectNodes("//" + BOOTSTRAPPER_PREFIX + ":*[@PackageFile]", _xmlNamespaceManager); 1315 foreach (XmlNode currentNode in packageFileNodeList) 1316 { 1317 XmlAttribute attribute = (XmlAttribute)(currentNode.Attributes.GetNamedItem("PackageFile")); 1318 string strQuery = BOOTSTRAPPER_PREFIX + ":PackageFile[@Name='" + attribute.Value + "']"; 1319 XmlNode packageFileNode = packageFilesNode.SelectSingleNode(strQuery, _xmlNamespaceManager); 1320 if (packageFileNode != null) 1321 { 1322 XmlAttribute targetPathAttribute = (XmlAttribute)(packageFileNode.Attributes.GetNamedItem("TargetPath")); 1323 attribute.Value = targetPathAttribute.Value; 1324 } 1325 } 1326 } 1327 } 1328 ReplaceStrings(XmlNode node)1329 private void ReplaceStrings(XmlNode node) 1330 { 1331 XmlNode stringsNode = node.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":Strings", _xmlNamespaceManager); 1332 XmlNode stringNode; 1333 XmlAttribute attribute; 1334 1335 if (stringsNode != null) 1336 { 1337 string stringNodeLookupTemplate = BOOTSTRAPPER_PREFIX + ":String[@Name='{0}']"; 1338 1339 // The name attribute at the package level is an entry into the String table 1340 ReplaceAttributeString(node, "Name", stringsNode); 1341 ReplaceAttributeString(node, "Culture", stringsNode); 1342 1343 // Homesite information is also carried in the String table 1344 XmlNode packageFilesNode = node.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":PackageFiles", _xmlNamespaceManager); 1345 if (packageFilesNode != null) 1346 { 1347 XmlNodeList packageFileNodeList = packageFilesNode.SelectNodes(BOOTSTRAPPER_PREFIX + ":PackageFile", _xmlNamespaceManager); 1348 foreach (XmlNode packageFileNode in packageFileNodeList) 1349 { 1350 ReplaceAttributeString(packageFileNode, HOMESITE_ATTRIBUTE, stringsNode); 1351 } 1352 } 1353 1354 // Map all String attributes to corresponding String values 1355 // It is currently expected that these come from either an ExitCode or FailIf 1356 XmlNodeList stringKeyNodeList = node.SelectNodes("//" + BOOTSTRAPPER_PREFIX + ":*[@String]", _xmlNamespaceManager); 1357 foreach (XmlNode currentNode in stringKeyNodeList) 1358 { 1359 attribute = (XmlAttribute)(currentNode.Attributes.GetNamedItem("String")); 1360 stringNode = stringsNode.SelectSingleNode(string.Format(CultureInfo.InvariantCulture, stringNodeLookupTemplate, attribute.Value), _xmlNamespaceManager); 1361 if (stringNode != null) 1362 { 1363 AddAttribute(currentNode, "Text", stringNode.InnerText); 1364 } 1365 currentNode.Attributes.Remove(attribute); 1366 } 1367 1368 // The Strings node is no longer necessary. Remove it. 1369 node.RemoveChild(stringsNode); 1370 } 1371 } 1372 BuildPackages(BuildSettings settings, XmlElement configElement, ResourceUpdater resourceUpdater, ArrayList filesCopied, Hashtable eulas)1373 private bool BuildPackages(BuildSettings settings, XmlElement configElement, ResourceUpdater resourceUpdater, ArrayList filesCopied, Hashtable eulas) 1374 { 1375 bool fSucceeded = true; 1376 1377 foreach (ProductBuilder builder in settings.ProductBuilders) 1378 { 1379 if (Validate && !builder.Product.ValidationPassed) 1380 { 1381 if (_results != null) 1382 { 1383 _results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.ProductValidation", builder.Name, builder.Product.ValidationResults.FilePath)); 1384 foreach (string validationMessage in builder.Product.ValidationResults.ValidationErrors) 1385 { 1386 _results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.ValidationError", builder.Product.ValidationResults.FilePath, validationMessage)); 1387 } 1388 foreach (string validationMessage in builder.Product.ValidationResults.ValidationWarnings) 1389 { 1390 _results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.ValidationWarning", builder.Product.ValidationResults.FilePath, validationMessage)); 1391 } 1392 } 1393 } 1394 Package package = GetPackageForSettings(settings, builder, _results); 1395 if (package == null) 1396 { 1397 // GetPackage should have already added the correct message info 1398 continue; 1399 } 1400 1401 if (Validate && !package.ValidationPassed) 1402 { 1403 if (_results != null) 1404 { 1405 _results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.PackageValidation", builder.Name, package.ValidationResults.FilePath)); 1406 foreach (string validationMessage in package.ValidationResults.ValidationErrors) 1407 { 1408 _results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.ValidationError", package.ValidationResults.FilePath, validationMessage)); 1409 } 1410 foreach (string validationMessage in package.ValidationResults.ValidationWarnings) 1411 { 1412 _results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.ValidationWarning", package.ValidationResults.FilePath, validationMessage)); 1413 } 1414 } 1415 } 1416 1417 XmlNode node = package.Node; 1418 // Copy the files for this package to the output directory 1419 XmlAttribute eulaAttribute = node.Attributes[EULA_ATTRIBUTE]; 1420 XmlNodeList packageFileNodes = node.SelectNodes(BOOTSTRAPPER_PREFIX + ":PackageFiles/" + BOOTSTRAPPER_PREFIX + ":PackageFile", _xmlNamespaceManager); 1421 XmlNode installChecksNode = node.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":InstallChecks", _xmlNamespaceManager); 1422 foreach (XmlNode packageFileNode in packageFileNodes) 1423 { 1424 XmlAttribute packageFileSource = (XmlAttribute)(packageFileNode.Attributes.GetNamedItem("SourcePath")); 1425 XmlAttribute packageFileDestination = (XmlAttribute)(packageFileNode.Attributes.GetNamedItem("TargetPath")); 1426 XmlAttribute packageFileName = (XmlAttribute)(packageFileNode.Attributes.GetNamedItem("Name")); 1427 XmlAttribute packageFileCopy = (XmlAttribute)(packageFileNode.Attributes.GetNamedItem("CopyOnBuild")); 1428 if (packageFileSource != null && eulaAttribute != null && !String.IsNullOrEmpty(eulaAttribute.Value) && packageFileSource.Value == eulaAttribute.Value) 1429 { 1430 // need to remove EULA from the package file list 1431 XmlNode packageFilesNode = node.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":PackageFiles", _xmlNamespaceManager); 1432 packageFilesNode.RemoveChild(packageFileNode); 1433 continue; 1434 } 1435 1436 if ((packageFileSource != null) && (packageFileDestination != null) && 1437 (packageFileName != null)) 1438 { 1439 // Calculate the hash of this file and add it to the PackageFileNode 1440 if (!AddVerificationInformation(packageFileNode, packageFileSource.Value, packageFileName.Value, builder, settings, _results)) 1441 fSucceeded = false; 1442 } 1443 1444 if ((packageFileSource != null) && (packageFileDestination != null) && 1445 ((packageFileCopy == null) || (String.Compare(packageFileCopy.Value, "False", StringComparison.InvariantCulture) != 0))) 1446 { 1447 // if this is the key for an external check, we will add it to the Resource Updater instead of copying the file 1448 XmlNode subNode = null; 1449 if ((installChecksNode != null) && (packageFileName != null)) 1450 { 1451 subNode = QueryForSubNode(installChecksNode, "PackageFile", packageFileName.Value); 1452 } 1453 if (subNode != null) 1454 { 1455 if (resourceUpdater != null) 1456 { 1457 if (!File.Exists(packageFileSource.Value)) 1458 { 1459 if (_results != null) 1460 _results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.PackageResourceFileNotFound", packageFileSource.Value, builder.Name)); 1461 fSucceeded = false; 1462 continue; 1463 } 1464 resourceUpdater.AddFileResource(packageFileSource.Value, packageFileDestination.Value); 1465 } 1466 } 1467 else 1468 { 1469 if (settings.ComponentsLocation != ComponentsLocation.HomeSite || !VerifyHomeSiteInformation(packageFileNode, builder, settings, _results)) 1470 { 1471 if (settings.CopyComponents) 1472 { 1473 string strDestinationFileName = System.IO.Path.Combine(settings.OutputPath, packageFileDestination.Value); 1474 try 1475 { 1476 if (!File.Exists(packageFileSource.Value)) 1477 { 1478 if (_results != null) 1479 _results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.PackageFileNotFound", packageFileDestination.Value, builder.Name)); 1480 fSucceeded = false; 1481 continue; 1482 } 1483 EnsureFolderExists(System.IO.Path.GetDirectoryName(strDestinationFileName)); 1484 File.Copy(packageFileSource.Value, strDestinationFileName, true); 1485 ClearReadOnlyAttribute(strDestinationFileName); 1486 } 1487 catch (UnauthorizedAccessException ex) 1488 { 1489 if (_results != null) 1490 _results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.CopyPackageError", packageFileSource.Value, builder.Name, ex.Message)); 1491 fSucceeded = false; 1492 continue; 1493 } 1494 catch (IOException ex) 1495 { 1496 if (_results != null) 1497 _results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.CopyPackageError", packageFileSource.Value, builder.Name, ex.Message)); 1498 fSucceeded = false; 1499 continue; 1500 } 1501 catch (ArgumentException ex) 1502 { 1503 if (_results != null) 1504 _results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.CopyPackageError", packageFileSource.Value, builder.Name, ex.Message)); 1505 fSucceeded = false; 1506 continue; 1507 } 1508 catch (NotSupportedException ex) 1509 { 1510 if (_results != null) 1511 _results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.CopyPackageError", packageFileSource.Value, builder.Name, ex.Message)); 1512 fSucceeded = false; 1513 continue; 1514 } 1515 filesCopied.Add(strDestinationFileName); 1516 } 1517 else 1518 { 1519 filesCopied.Add(packageFileSource.Value); 1520 } 1521 1522 // Add the file size to the PackageFileNode 1523 XmlAttribute sizeAttribute = packageFileNode.OwnerDocument.CreateAttribute("Size"); 1524 FileInfo fi = new FileInfo(packageFileSource.Value); 1525 sizeAttribute.Value = "" + (fi.Length.ToString(CultureInfo.InvariantCulture)); 1526 MergeAttribute(packageFileNode, sizeAttribute); 1527 } 1528 } 1529 } 1530 } 1531 // Add the Eula attribute correctly 1532 if (eulas != null && eulaAttribute != null && !String.IsNullOrEmpty(eulaAttribute.Value)) 1533 { 1534 if (File.Exists(eulaAttribute.Value)) 1535 { 1536 // eulas[GetFileHash(eulaAttribute.Value)] = eulaAttribute.Value; 1537 string key = GetFileHash(eulaAttribute.Value); 1538 if (eulas.ContainsKey(key)) 1539 eulaAttribute.Value = ((DictionaryEntry)eulas[key]).Key.ToString(); 1540 else 1541 { 1542 string configFileKey = string.Format(CultureInfo.InvariantCulture, "EULA{0}", eulas.Count); 1543 DictionaryEntry de = new DictionaryEntry(configFileKey, eulaAttribute.Value); 1544 eulas[key] = de; 1545 eulaAttribute.Value = configFileKey; 1546 } 1547 } 1548 else 1549 { 1550 if (_results != null) 1551 _results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.PackageResourceFileNotFound", eulaAttribute.Value, builder.Name)); 1552 fSucceeded = false; 1553 continue; 1554 } 1555 } 1556 // Write the package node 1557 if (configElement != null) 1558 { 1559 configElement.AppendChild(configElement.OwnerDocument.ImportNode(node, true)); 1560 DumpXmlToFile(node, string.Format(CultureInfo.CurrentCulture, "{0}.{1}.xml", package.Product.ProductCode, package.Culture)); 1561 } 1562 } 1563 1564 return fSucceeded; 1565 } 1566 CreateProductNode(XmlNode node)1567 private XmlNode CreateProductNode(XmlNode node) 1568 { 1569 // create a new Product node for the passed-in product 1570 XmlNode productNode = _document.CreateElement("Product", BOOTSTRAPPER_NAMESPACE); 1571 XmlAttribute sourceAttribute; 1572 1573 // find the ProductCode attribute 1574 sourceAttribute = (XmlAttribute)(node.Attributes.GetNamedItem("ProductCode")); 1575 Debug.Assert(sourceAttribute != null, "we should not be here if there is no ProductCode attribute"); 1576 1577 AddAttribute(productNode, "ProductCode", sourceAttribute.Value); 1578 1579 node.Attributes.Remove(sourceAttribute); 1580 1581 return productNode; 1582 } 1583 ReadAttribute(XmlNode node, string strAttributeName)1584 private string ReadAttribute(XmlNode node, string strAttributeName) 1585 { 1586 XmlAttribute attribute = (XmlAttribute)(node.Attributes.GetNamedItem(strAttributeName)); 1587 1588 if (attribute != null) 1589 return attribute.Value; 1590 1591 return null; 1592 } 1593 EnsureFolderExists(string strFolderPath)1594 private void EnsureFolderExists(string strFolderPath) 1595 { 1596 if (!Directory.Exists(strFolderPath)) 1597 { 1598 Directory.CreateDirectory(strFolderPath); 1599 } 1600 } 1601 ClearReadOnlyAttribute(string strFileName)1602 private void ClearReadOnlyAttribute(string strFileName) 1603 { 1604 FileAttributes attribs = File.GetAttributes(strFileName); 1605 if ((attribs & FileAttributes.ReadOnly) != 0) 1606 { 1607 attribs = attribs & (~FileAttributes.ReadOnly); 1608 File.SetAttributes(strFileName, attribs); 1609 } 1610 } 1611 ByteArrayToString(byte[] byteArray)1612 private string ByteArrayToString(byte[] byteArray) 1613 { 1614 if (byteArray == null) return null; 1615 1616 System.Text.StringBuilder output = new System.Text.StringBuilder(byteArray.Length); 1617 foreach (byte byteValue in byteArray) 1618 output.Append(byteValue.ToString("X02", CultureInfo.InvariantCulture)); 1619 1620 return output.ToString(); 1621 } 1622 GetFileHash(string filePath)1623 private string GetFileHash(string filePath) 1624 { 1625 FileInfo fi = new System.IO.FileInfo(filePath); 1626 String retVal = null; 1627 1628 // Bootstrapper is always signed with the SHA-256 algorithm, no matter which version of 1629 // the .NET Framework we are targeting. In ideal situations, bootstrapper files will be 1630 // pre-signed anwyay; this is a fallback in case we ever encounter a bootstrapper that is 1631 // not signed. 1632 System.Security.Cryptography.SHA256CryptoServiceProvider sha = new System.Security.Cryptography.SHA256CryptoServiceProvider(); 1633 1634 using (Stream s = fi.OpenRead()) 1635 { 1636 retVal = ByteArrayToString(sha.ComputeHash(s)); 1637 } 1638 return retVal; 1639 } 1640 ReplaceAttributeString(XmlNode node, string attributeName, XmlNode stringsNode)1641 private void ReplaceAttributeString(XmlNode node, string attributeName, XmlNode stringsNode) 1642 { 1643 string stringNodeLookupTemplate = BOOTSTRAPPER_PREFIX + ":String[@Name='{0}']"; 1644 XmlAttribute attribute = (XmlAttribute)(node.Attributes.GetNamedItem(attributeName)); 1645 if (attribute != null) 1646 { 1647 XmlNode stringNode = stringsNode.SelectSingleNode(string.Format(CultureInfo.InvariantCulture, stringNodeLookupTemplate, attribute.Value), _xmlNamespaceManager); 1648 if (stringNode != null) 1649 attribute.Value = stringNode.InnerText; 1650 } 1651 } 1652 GetPackageForSettings(BuildSettings settings, ProductBuilder builder, BuildResults results)1653 private Package GetPackageForSettings(BuildSettings settings, ProductBuilder builder, BuildResults results) 1654 { 1655 CultureInfo ci = Util.GetCultureInfoFromString(settings.Culture); 1656 CultureInfo fallbackCI = Util.GetCultureInfoFromString(settings.FallbackCulture); 1657 Package package = null; 1658 1659 if (builder.Product.Packages.Count == 0) 1660 { 1661 if (results != null) 1662 results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.ProductCultureNotFound", builder.Name)); 1663 return null; 1664 } 1665 1666 if (ci != null) 1667 { 1668 package = builder.Product.Packages.Package(ci.Name); 1669 if (package != null) return package; 1670 1671 // 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 1672 // Note: there is no warning if the parent culture of the requested culture is found 1673 CultureInfo parentCulture = ci.Parent; 1674 1675 // Keep going up the chain of parents, stopping at the invariant culture 1676 while (parentCulture != null && parentCulture != CultureInfo.InvariantCulture) 1677 { 1678 package = GetPackageForSettings_Helper(ci, parentCulture, builder, results, false); 1679 if (package != null) return package; 1680 1681 parentCulture = parentCulture.Parent; 1682 } 1683 } 1684 1685 1686 if (fallbackCI != null) 1687 { 1688 package = GetPackageForSettings_Helper(ci, fallbackCI, builder, results, true); 1689 if (package != null) return package; 1690 1691 if (!fallbackCI.IsNeutralCulture) 1692 { 1693 package = GetPackageForSettings_Helper(ci, fallbackCI.Parent, builder, results, true); 1694 if (package != null) return package; 1695 } 1696 } 1697 1698 package = GetPackageForSettings_Helper(ci, Util.DefaultCultureInfo, builder, results, true); 1699 if (package != null) return package; 1700 1701 if (!Util.DefaultCultureInfo.IsNeutralCulture) 1702 { 1703 package = GetPackageForSettings_Helper(ci, Util.DefaultCultureInfo.Parent, builder, results, true); 1704 if (package != null) return package; 1705 } 1706 1707 if (results != null && ci != null) 1708 results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.UsingProductCulture", ci.Name, builder.Name, builder.Product.Packages.Item(0).Culture)); 1709 return builder.Product.Packages.Item(0); 1710 } 1711 GetPackageForSettings_Helper(CultureInfo culture, CultureInfo altCulture, ProductBuilder builder, BuildResults results, bool fShowWarning)1712 private Package GetPackageForSettings_Helper(CultureInfo culture, CultureInfo altCulture, ProductBuilder builder, BuildResults results, bool fShowWarning) 1713 { 1714 if (altCulture == null) 1715 return null; 1716 Package package = builder.Product.Packages.Package(altCulture.Name); 1717 if (package != null) 1718 { 1719 if (fShowWarning && culture != null && results != null) 1720 results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.UsingProductCulture", culture.Name, builder.Name, altCulture.Name)); 1721 return package; 1722 } 1723 return null; 1724 } 1725 BuildResources(BuildSettings settings, ResourceUpdater resourceUpdater)1726 private bool BuildResources(BuildSettings settings, ResourceUpdater resourceUpdater) 1727 { 1728 if (_cultures.Count == 0) 1729 { 1730 if (_results != null) 1731 _results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.NoResources")); 1732 return false; 1733 } 1734 1735 int codePage = -1; 1736 XmlNode resourcesNode = GetResourcesNodeForSettings(settings, _results, ref codePage); 1737 XmlNode stringsNode = resourcesNode.SelectSingleNode("Strings"); 1738 XmlNode fontsNode = resourcesNode.SelectSingleNode("Fonts"); 1739 1740 if (stringsNode == null) 1741 { 1742 if (_results != null) 1743 _results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.NoStringsForCulture", resourcesNode.Attributes.GetNamedItem("Culture").Value)); 1744 return false; 1745 } 1746 1747 XmlNodeList stringNodes = stringsNode.SelectNodes("String"); 1748 1749 foreach (XmlNode stringNode in stringNodes) 1750 { 1751 XmlAttribute resourceIdAttribute = (XmlAttribute)stringNode.Attributes.GetNamedItem("Name"); 1752 1753 if (resourceIdAttribute != null) 1754 { 1755 resourceUpdater.AddStringResource(MESSAGE_TABLE, resourceIdAttribute.Value.ToUpper(CultureInfo.InvariantCulture), stringNode.InnerText); 1756 } 1757 } 1758 1759 if (fontsNode != null) 1760 { 1761 foreach (XmlNode fontNode in fontsNode.SelectNodes("Font")) 1762 { 1763 ConvertChildsNodeToAttributes(fontNode); 1764 } 1765 string fontsConfig = XmlToConfigurationFile(fontsNode); 1766 resourceUpdater.AddStringResource(RESOURCE_TABLE, "SETUPRES", fontsConfig); 1767 DumpXmlToFile(fontsNode, "fonts.cfg.xml"); 1768 DumpStringToFile(fontsConfig, "fonts.cfg", false); 1769 if (codePage != -1) 1770 resourceUpdater.AddStringResource(RESOURCE_TABLE, "CODEPAGE", codePage.ToString(CultureInfo.InvariantCulture)); 1771 } 1772 return true; 1773 } 1774 GetResourcesNodeForSettings(BuildSettings settings, BuildResults results, ref int codepage)1775 private XmlNode GetResourcesNodeForSettings(BuildSettings settings, BuildResults results, ref int codepage) 1776 { 1777 CultureInfo ci = Util.GetCultureInfoFromString(settings.Culture); 1778 CultureInfo fallbackCI = Util.GetCultureInfoFromString(settings.FallbackCulture); 1779 XmlNode cultureNode = null; 1780 1781 1782 if (ci != null) 1783 { 1784 // 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 1785 cultureNode = GetResourcesNodeForSettings_Helper(ci, ci, results, ref codepage, false); 1786 if (cultureNode != null) return cultureNode; 1787 CultureInfo parentCulture = ci.Parent; 1788 1789 // Keep going up the chain of parents, stopping at the invariant culture 1790 while (parentCulture != null && parentCulture != CultureInfo.InvariantCulture) 1791 { 1792 cultureNode = GetResourcesNodeForSettings_Helper(ci, parentCulture, results, ref codepage, false); 1793 if (cultureNode != null) return cultureNode; 1794 1795 parentCulture = parentCulture.Parent; 1796 } 1797 } 1798 1799 if (fallbackCI != null) 1800 { 1801 cultureNode = GetResourcesNodeForSettings_Helper(ci, fallbackCI, results, ref codepage, true); 1802 if (cultureNode != null) return cultureNode; 1803 1804 if (!fallbackCI.IsNeutralCulture) 1805 { 1806 cultureNode = GetResourcesNodeForSettings_Helper(ci, fallbackCI.Parent, results, ref codepage, true); 1807 if (cultureNode != null) return cultureNode; 1808 } 1809 } 1810 1811 cultureNode = GetResourcesNodeForSettings_Helper(ci, Util.DefaultCultureInfo, results, ref codepage, true); 1812 if (cultureNode != null) return cultureNode; 1813 1814 if (!Util.DefaultCultureInfo.IsNeutralCulture) 1815 { 1816 cultureNode = GetResourcesNodeForSettings_Helper(ci, Util.DefaultCultureInfo.Parent, results, ref codepage, true); 1817 if (cultureNode != null) return cultureNode; 1818 } 1819 1820 IEnumerator keys = _cultures.Keys.GetEnumerator(); 1821 keys.MoveNext(); 1822 string altCulture = (string)keys.Current; 1823 if (ci != null && results != null) 1824 results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.UsingResourcesCulture", ci.Name, altCulture)); 1825 GetCodePage(altCulture, ref codepage); 1826 return (XmlNode)_cultures[altCulture.ToLowerInvariant()]; 1827 } 1828 GetResourcesNodeForSettings_Helper(CultureInfo culture, CultureInfo altCulture, BuildResults results, ref int codepage, bool fShowWarning)1829 private XmlNode GetResourcesNodeForSettings_Helper(CultureInfo culture, CultureInfo altCulture, BuildResults results, ref int codepage, bool fShowWarning) 1830 { 1831 if (altCulture != null && _cultures.Contains(altCulture.Name.ToLowerInvariant())) 1832 { 1833 if (fShowWarning && culture != null && results != null) 1834 results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.UsingResourcesCulture", culture.Name, altCulture.Name)); 1835 1836 codepage = altCulture.TextInfo.ANSICodePage; 1837 return (XmlNode)_cultures[altCulture.Name.ToLowerInvariant()]; 1838 } 1839 1840 return null; 1841 } 1842 GetCodePage(string culture, ref int codePage)1843 private void GetCodePage(string culture, ref int codePage) 1844 { 1845 try 1846 { 1847 System.Globalization.CultureInfo info = new System.Globalization.CultureInfo(culture); 1848 codePage = info.TextInfo.ANSICodePage; 1849 } 1850 catch (ArgumentException ex) 1851 { 1852 Debug.Fail(ex.Message); 1853 } 1854 } 1855 ReplacePackageFileAttributes(XmlNode targetNodes, string targetAttribute, XmlNode sourceNodes, string sourceSubNodeName, string sourceOldName, string sourceNewName)1856 private void ReplacePackageFileAttributes(XmlNode targetNodes, string targetAttribute, XmlNode sourceNodes, string sourceSubNodeName, string sourceOldName, string sourceNewName) 1857 { 1858 XmlNodeList sourceNodeList = sourceNodes.SelectNodes(BOOTSTRAPPER_PREFIX + ":" + sourceSubNodeName, _xmlNamespaceManager); 1859 1860 foreach (XmlNode sourceNode in sourceNodeList) 1861 { 1862 XmlAttribute oldNameAttribute = (XmlAttribute)(sourceNode.Attributes.GetNamedItem(sourceOldName)); 1863 XmlAttribute newNameAttribute = (XmlAttribute)(sourceNode.Attributes.GetNamedItem(sourceNewName)); 1864 1865 if (oldNameAttribute != null && newNameAttribute != null) 1866 { 1867 ReplaceAttributes(targetNodes, targetAttribute, oldNameAttribute.Value, newNameAttribute.Value); 1868 } 1869 } 1870 } 1871 CreateApplicationElement(XmlElement configElement, BuildSettings settings)1872 private XmlElement CreateApplicationElement(XmlElement configElement, BuildSettings settings) 1873 { 1874 XmlElement applicationElement = null; 1875 1876 if (!String.IsNullOrEmpty(settings.ApplicationName) || !String.IsNullOrEmpty(settings.ApplicationFile)) 1877 { 1878 applicationElement = configElement.OwnerDocument.CreateElement("Application"); 1879 if (!String.IsNullOrEmpty(settings.ApplicationName)) 1880 { 1881 AddAttribute(applicationElement, "Name", settings.ApplicationName); 1882 } 1883 AddAttribute(applicationElement, "RequiresElevation", settings.ApplicationRequiresElevation ? "true" : "false"); 1884 1885 if (!String.IsNullOrEmpty(settings.ApplicationFile)) 1886 { 1887 XmlElement filesNode = applicationElement.OwnerDocument.CreateElement("Files"); 1888 XmlElement fileNode = filesNode.OwnerDocument.CreateElement("File"); 1889 AddAttribute(fileNode, "Name", settings.ApplicationFile); 1890 AddAttribute(fileNode, URLNAME_ATTRIBUTE, Uri.EscapeUriString(settings.ApplicationFile)); 1891 filesNode.AppendChild(fileNode); 1892 applicationElement.AppendChild(filesNode); 1893 } 1894 } 1895 return applicationElement; 1896 } 1897 AddAttribute(XmlNode node, string attributeName, string attributeValue)1898 private void AddAttribute(XmlNode node, string attributeName, string attributeValue) 1899 { 1900 XmlAttribute attrib = node.OwnerDocument.CreateAttribute(attributeName); 1901 attrib.Value = attributeValue; 1902 node.Attributes.Append(attrib); 1903 } 1904 1905 [SuppressMessage("Microsoft.Security.Xml", "CA3073: ReviewTrustedXsltUse.", Justification = "Input style sheet comes from our own assemblies. Hence it is a trusted source.")] 1906 [SuppressMessage("Microsoft.Security.Xml", "CA3059: UseXmlReaderForXPathDocument.", Justification = "Input style sheet comes from our own assemblies. Hence it is a trusted source.")] 1907 [SuppressMessage("Microsoft.Security.Xml", "CA3052: UseXmlResolver.", Justification = "Input style sheet comes from our own assemblies. Hence it is a trusted source.")] XmlToConfigurationFile(XmlNode input)1908 private string XmlToConfigurationFile(XmlNode input) 1909 { 1910 using (XmlNodeReader reader = new XmlNodeReader(input)) 1911 { 1912 Stream s = GetEmbeddedResourceStream(CONFIG_TRANSFORM); 1913 XPathDocument d = new XPathDocument(s); 1914 XslCompiledTransform xslc = new XslCompiledTransform(); 1915 // Using the Trusted Xslt is fine as the style sheet comes from our own assembly. 1916 xslc.Load(d, XsltSettings.TrustedXslt, new XmlUrlResolver()); 1917 1918 XPathDocument xml = new XPathDocument(reader); 1919 1920 using (MemoryStream m = new MemoryStream()) 1921 { 1922 using (StreamWriter w = new StreamWriter(m)) 1923 { 1924 xslc.Transform(xml, null, w); 1925 1926 w.Flush(); 1927 m.Position = 0; 1928 1929 using (StreamReader r = new StreamReader(m)) 1930 { 1931 // HACKHACK 1932 string str = r.ReadToEnd(); 1933 str = str.Replace("%NEWLINE%", Environment.NewLine); 1934 return str; 1935 } 1936 } 1937 } 1938 } 1939 } 1940 GetEmbeddedResourceStream(string name)1941 private Stream GetEmbeddedResourceStream(string name) 1942 { 1943 Assembly a = Assembly.GetExecutingAssembly(); 1944 Stream s = a.GetManifestResourceStream(String.Format(CultureInfo.InvariantCulture, "{0}.{1}", typeof(BootstrapperBuilder).Namespace, name)); 1945 Debug.Assert(s != null, String.Format(CultureInfo.CurrentCulture, "EmbeddedResource '{0}' not found", name)); 1946 return s; 1947 } 1948 GetAssemblyPath()1949 private string GetAssemblyPath() 1950 { 1951 return System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); 1952 } 1953 DumpXmlToFile(XmlNode node, string fileName)1954 private void DumpXmlToFile(XmlNode node, string fileName) 1955 { 1956 if (s_logging) 1957 { 1958 try 1959 { 1960 using (XmlTextWriter xmlwriter = new XmlTextWriter(System.IO.Path.Combine(s_logPath, fileName), System.Text.Encoding.UTF8)) 1961 { 1962 xmlwriter.Formatting = Formatting.Indented; 1963 xmlwriter.Indentation = 4; 1964 xmlwriter.WriteNode(new XmlNodeReader(node), true); 1965 } 1966 } 1967 catch (IOException) 1968 { 1969 // can't write info to a log file? This is a trouble-shooting helper only, and 1970 // this exception can be ignored 1971 } 1972 catch (UnauthorizedAccessException) 1973 { 1974 // can't write info to a log file? This is a trouble-shooting helper only, and 1975 // this exception can be ignored 1976 } 1977 catch (ArgumentException) 1978 { 1979 // can't write info to a log file? This is a trouble-shooting helper only, and 1980 // this exception can be ignored 1981 } 1982 catch (NotSupportedException) 1983 { 1984 // can't write info to a log file? This is a trouble-shooting helper only, and 1985 // this exception can be ignored 1986 } 1987 catch (XmlException) 1988 { 1989 // can't write info to a log file? This is a trouble-shooting helper only, and 1990 // this exception can be ignored 1991 } 1992 } 1993 } 1994 DumpStringToFile(string text, string fileName, bool append)1995 private void DumpStringToFile(string text, string fileName, bool append) 1996 { 1997 if (s_logging) 1998 { 1999 try 2000 { 2001 using (StreamWriter fileWriter = new StreamWriter(System.IO.Path.Combine(s_logPath, fileName), append)) 2002 { 2003 fileWriter.Write(text); 2004 } 2005 } 2006 catch (IOException) 2007 { 2008 // can't write info to a log file? This is a trouble-shooting helper only, and 2009 // this exception can be ignored 2010 } 2011 catch (UnauthorizedAccessException) 2012 { 2013 // can't write info to a log file? This is a trouble-shooting helper only, and 2014 // this exception can be ignored 2015 } 2016 catch (ArgumentException) 2017 { 2018 // can't write info to a log file? This is a trouble-shooting helper only, and 2019 // this exception can be ignored 2020 } 2021 catch (NotSupportedException) 2022 { 2023 // can't write info to a log file? This is a trouble-shooting helper only, and 2024 // this exception can be ignored 2025 } 2026 } 2027 } 2028 VerifyHomeSiteInformation(XmlNode packageFileNode, ProductBuilder builder, BuildSettings settings, BuildResults results)2029 private bool VerifyHomeSiteInformation(XmlNode packageFileNode, ProductBuilder builder, BuildSettings settings, BuildResults results) 2030 { 2031 if (settings.ComponentsLocation != ComponentsLocation.HomeSite) 2032 { 2033 return true; 2034 } 2035 2036 XmlAttribute homesiteAttribute = packageFileNode.Attributes[HOMESITE_ATTRIBUTE]; 2037 2038 if (homesiteAttribute == null && builder.Product.CopyAllPackageFiles != CopyAllFilesType.CopyAllFilesIfNotHomeSite) 2039 { 2040 if (results != null) 2041 results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.PackageHomeSiteMissing", builder.Name)); 2042 return false; 2043 } 2044 2045 return true; 2046 } 2047 AddVerificationInformation(XmlNode packageFileNode, string fileSource, string fileName, ProductBuilder builder, BuildSettings settings, BuildResults results)2048 private bool AddVerificationInformation(XmlNode packageFileNode, string fileSource, string fileName, ProductBuilder builder, BuildSettings settings, BuildResults results) 2049 { 2050 XmlAttribute hashAttribute = packageFileNode.Attributes[HASH_ATTRIBUTE]; 2051 XmlAttribute publicKeyAttribute = packageFileNode.Attributes[PUBLICKEY_ATTRIBUTE]; 2052 2053 if (File.Exists(fileSource)) 2054 { 2055 string publicKey = GetPublicKeyOfFile(fileSource); 2056 if (hashAttribute == null && publicKeyAttribute == null) 2057 { 2058 // If neither the Hash nor PublicKey attributes were specified in the manifest, add it 2059 if (publicKey != null) 2060 { 2061 AddAttribute(packageFileNode, PUBLICKEY_ATTRIBUTE, publicKey); 2062 } 2063 else 2064 { 2065 AddAttribute(packageFileNode, HASH_ATTRIBUTE, GetFileHash(fileSource)); 2066 } 2067 } 2068 if (publicKeyAttribute != null) 2069 { 2070 // Always use the PublicKey of the file on disk 2071 if (publicKey != null) 2072 ReplaceAttribute(packageFileNode, PUBLICKEY_ATTRIBUTE, publicKey); 2073 else 2074 { 2075 // File on disk is not signed. Remove the public key info, and make sure the hash is written instead 2076 packageFileNode.Attributes.RemoveNamedItem(PUBLICKEY_ATTRIBUTE); 2077 if (hashAttribute == null) 2078 AddAttribute(packageFileNode, HASH_ATTRIBUTE, GetFileHash(fileSource)); 2079 } 2080 2081 // If the public key in the file doesn't match the public key on disk, issue a build warning 2082 if (publicKey == null || !publicKey.ToLowerInvariant().Equals(publicKeyAttribute.Value.ToLowerInvariant())) 2083 { 2084 if (results != null) 2085 results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.DifferingPublicKeys", PUBLICKEY_ATTRIBUTE, builder.Name, fileSource)); 2086 } 2087 } 2088 if (hashAttribute != null) 2089 { 2090 string fileHash = GetFileHash(fileSource); 2091 2092 // Always use the Hash of the file on disk 2093 ReplaceAttribute(packageFileNode, HASH_ATTRIBUTE, fileHash); 2094 2095 // If the public key in the file doesn't match the public key on disk, issue a build warning 2096 if (!fileHash.ToLowerInvariant().Equals(hashAttribute.Value.ToLowerInvariant())) 2097 { 2098 if (results != null) 2099 results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Warning, "GenerateBootstrapper.DifferingPublicKeys", "Hash", builder.Name, fileSource)); 2100 } 2101 } 2102 } 2103 else if (settings.ComponentsLocation == ComponentsLocation.HomeSite) 2104 { 2105 if (hashAttribute == null && publicKeyAttribute == null) 2106 { 2107 if (results != null) 2108 { 2109 results.AddMessage(BuildMessage.CreateMessage(BuildMessageSeverity.Error, "GenerateBootstrapper.MissingVerificationInformation", fileName, builder.Name)); 2110 } 2111 return false; 2112 } 2113 } 2114 2115 return true; 2116 } 2117 GetPublicKeyOfFile(string fileSource)2118 private string GetPublicKeyOfFile(string fileSource) 2119 { 2120 if (File.Exists(fileSource)) 2121 { 2122 try 2123 { 2124 X509Certificate cert = new X509Certificate(fileSource); 2125 string publicKey = cert.GetPublicKeyString(); 2126 return publicKey; 2127 } 2128 catch (System.Security.Cryptography.CryptographicException) 2129 { 2130 // This just means the file is not signed. 2131 } 2132 } 2133 2134 return null; 2135 } 2136 ConvertChildsNodeToAttributes(XmlNode node)2137 private void ConvertChildsNodeToAttributes(XmlNode node) 2138 { 2139 XmlNode childNode = node.FirstChild; 2140 while (childNode != null) 2141 { 2142 // Need to get the next child node now because when the current node is removed, the NextSibling 2143 // will be null 2144 XmlNode currentNode = childNode; 2145 childNode = currentNode.NextSibling; 2146 if (currentNode.Attributes.Count == 0 && currentNode.InnerText.Length > 0) 2147 { 2148 AddAttribute(node, currentNode.Name, currentNode.InnerText); 2149 node.RemoveChild(currentNode); 2150 } 2151 } 2152 } 2153 GetLogPath()2154 private static string GetLogPath() 2155 { 2156 if (!s_logging) return null; 2157 string logPath = System.IO.Path.Combine( 2158 Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), 2159 @"Microsoft\VisualStudio\" + VisualStudioConstants.CurrentVisualStudioVersion + @"\VSPLOG"); 2160 if (!Directory.Exists(logPath)) 2161 Directory.CreateDirectory(logPath); 2162 return logPath; 2163 } 2164 GetIncludedProducts(Product product)2165 private Dictionary<string, Product> GetIncludedProducts(Product product) 2166 { 2167 Dictionary<string, Product> includedProducts = new Dictionary<string, Product>(StringComparer.OrdinalIgnoreCase); 2168 2169 // Add in this product in case there is a circular includes: 2170 // we won't continue to explore this product. It will be removed later. 2171 includedProducts.Add(product.ProductCode, product); 2172 2173 // Recursively add included products 2174 foreach (Product p in product.Includes) 2175 { 2176 AddIncludedProducts(p, includedProducts); 2177 } 2178 2179 includedProducts.Remove(product.ProductCode); 2180 return includedProducts; 2181 } 2182 AddIncludedProducts(Product product, Dictionary<string, Product> includedProducts)2183 private void AddIncludedProducts(Product product, Dictionary<string, Product> includedProducts) 2184 { 2185 if (!includedProducts.ContainsKey(product.ProductCode)) 2186 { 2187 includedProducts.Add(product.ProductCode, product); 2188 foreach (Product p in product.Includes) 2189 { 2190 AddIncludedProducts(p, includedProducts); 2191 } 2192 } 2193 } 2194 MapLCIDToCultureName(int lcid)2195 private string MapLCIDToCultureName(int lcid) 2196 { 2197 if (lcid == 0) 2198 return Util.DefaultCultureInfo.Name; 2199 2200 try 2201 { 2202 CultureInfo ci = new CultureInfo(lcid); 2203 return ci.Name; 2204 } 2205 catch (ArgumentException) 2206 { 2207 // Can't convert this lcid to a CultureInfo? Just return the default CultureInfo instead... 2208 return Util.DefaultCultureInfo.Name; 2209 } 2210 } 2211 } 2212 } 2213