From dbde9e7104b907bbbaea17e21247d8cafc8b3a4c Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Sat, 14 Oct 2017 16:12:07 -0700 Subject: Massive refactoring to introduce the concept of IBackend --- .../Bundles/ProcessMspPackageCommand.cs | 189 +++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 src/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs (limited to 'src/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs') diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs new file mode 100644 index 00000000..2d849d03 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs @@ -0,0 +1,189 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Core.Burn.Bundles +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Text; + using System.Xml; + using WixToolset.Data; + using WixToolset.Data.Rows; + using Dtf = WixToolset.Dtf.WindowsInstaller; + + /// + /// Initializes package state from the Msp contents. + /// + internal class ProcessMspPackageCommand + { + private const string PatchMetadataFormat = "SELECT `Value` FROM `MsiPatchMetadata` WHERE `Property` = '{0}'"; + private static readonly Encoding XmlOutputEncoding = new UTF8Encoding(false); + + public RowDictionary AuthoredPayloads { private get; set; } + + public PackageFacade Facade { private get; set; } + + public Table WixBundlePatchTargetCodeTable { private get; set; } + + /// + /// Processes the Msp packages to add properties and payloads from the Msp packages. + /// + public void Execute() + { + WixBundlePayloadRow packagePayload = this.AuthoredPayloads.Get(this.Facade.Package.PackagePayload); + + string sourcePath = packagePayload.FullFileName; + + try + { + // Read data out of the msp database... + using (Dtf.SummaryInfo sumInfo = new Dtf.SummaryInfo(sourcePath, false)) + { + this.Facade.MspPackage.PatchCode = sumInfo.RevisionNumber.Substring(0, 38); + } + + using (Dtf.Database db = new Dtf.Database(sourcePath)) + { + if (String.IsNullOrEmpty(this.Facade.Package.DisplayName)) + { + this.Facade.Package.DisplayName = ProcessMspPackageCommand.GetPatchMetadataProperty(db, "DisplayName"); + } + + if (String.IsNullOrEmpty(this.Facade.Package.Description)) + { + this.Facade.Package.Description = ProcessMspPackageCommand.GetPatchMetadataProperty(db, "Description"); + } + + this.Facade.MspPackage.Manufacturer = ProcessMspPackageCommand.GetPatchMetadataProperty(db, "ManufacturerName"); + } + + this.ProcessPatchXml(packagePayload, sourcePath); + } + catch (Dtf.InstallerException e) + { + Messaging.Instance.OnMessage(WixErrors.UnableToReadPackageInformation(packagePayload.SourceLineNumbers, sourcePath, e.Message)); + return; + } + + if (String.IsNullOrEmpty(this.Facade.Package.CacheId)) + { + this.Facade.Package.CacheId = this.Facade.MspPackage.PatchCode; + } + } + + private void ProcessPatchXml(WixBundlePayloadRow packagePayload, string sourcePath) + { + HashSet uniqueTargetCodes = new HashSet(); + + string patchXml = Dtf.Installer.ExtractPatchXmlData(sourcePath); + + XmlDocument doc = new XmlDocument(); + doc.LoadXml(patchXml); + + XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable); + nsmgr.AddNamespace("p", "http://www.microsoft.com/msi/patch_applicability.xsd"); + + // Determine target ProductCodes and/or UpgradeCodes. + foreach (XmlNode node in doc.SelectNodes("/p:MsiPatch/p:TargetProduct", nsmgr)) + { + // If this patch targets a product code, this is the best case. + XmlNode targetCodeElement = node.SelectSingleNode("p:TargetProductCode", nsmgr); + WixBundlePatchTargetCodeAttributes attributes = WixBundlePatchTargetCodeAttributes.None; + + if (ProcessMspPackageCommand.TargetsCode(targetCodeElement)) + { + attributes = WixBundlePatchTargetCodeAttributes.TargetsProductCode; + } + else // maybe targets an upgrade code? + { + targetCodeElement = node.SelectSingleNode("p:UpgradeCode", nsmgr); + if (ProcessMspPackageCommand.TargetsCode(targetCodeElement)) + { + attributes = WixBundlePatchTargetCodeAttributes.TargetsUpgradeCode; + } + else // this patch targets an unknown number of products + { + this.Facade.MspPackage.Attributes |= WixBundleMspPackageAttributes.TargetUnspecified; + } + } + + string targetCode = targetCodeElement.InnerText; + + if (uniqueTargetCodes.Add(targetCode)) + { + WixBundlePatchTargetCodeRow row = (WixBundlePatchTargetCodeRow)this.WixBundlePatchTargetCodeTable.CreateRow(packagePayload.SourceLineNumbers); + row.MspPackageId = packagePayload.Id; + row.TargetCode = targetCode; + row.Attributes = attributes; + } + } + + // Suppress patch sequence data for improved performance. + XmlNode root = doc.DocumentElement; + foreach (XmlNode node in root.SelectNodes("p:SequenceData", nsmgr)) + { + root.RemoveChild(node); + } + + // Save the XML as compact as possible. + using (StringWriter writer = new StringWriter()) + { + XmlWriterSettings settings = new XmlWriterSettings() + { + Encoding = ProcessMspPackageCommand.XmlOutputEncoding, + Indent = false, + NewLineChars = string.Empty, + NewLineHandling = NewLineHandling.Replace, + }; + + using (XmlWriter xmlWriter = XmlWriter.Create(writer, settings)) + { + doc.WriteTo(xmlWriter); + } + + this.Facade.MspPackage.PatchXml = writer.ToString(); + } + } + + /// + /// Queries a Windows Installer patch database for a Property value from the MsiPatchMetadata table. + /// + /// Database to query. + /// Property to examine. + /// String value for result or null if query doesn't match a single result. + private static string GetPatchMetadataProperty(Dtf.Database db, string property) + { + try + { + return db.ExecuteScalar(PatchMetadataPropertyQuery(property)).ToString(); + } + catch (Dtf.InstallerException) + { + } + + return null; + } + + private static string PatchMetadataPropertyQuery(string property) + { + // quick sanity check that we'll be creating a valid query... + // TODO: Are there any other special characters we should be looking for? + Debug.Assert(!property.Contains("'")); + + return String.Format(CultureInfo.InvariantCulture, ProcessMspPackageCommand.PatchMetadataFormat, property); + } + + private static bool TargetsCode(XmlNode node) + { + if (null != node) + { + XmlAttribute attr = node.Attributes["Validate"]; + return null != attr && "true".Equals(attr.Value); + } + + return false; + } + } +} -- cgit v1.2.3-55-g6feb