// 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; } } }