aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs')
-rw-r--r--src/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs189
1 files changed, 189 insertions, 0 deletions
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 @@
1// 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.
2
3namespace WixToolset.Core.Burn.Bundles
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.Globalization;
9 using System.IO;
10 using System.Text;
11 using System.Xml;
12 using WixToolset.Data;
13 using WixToolset.Data.Rows;
14 using Dtf = WixToolset.Dtf.WindowsInstaller;
15
16 /// <summary>
17 /// Initializes package state from the Msp contents.
18 /// </summary>
19 internal class ProcessMspPackageCommand
20 {
21 private const string PatchMetadataFormat = "SELECT `Value` FROM `MsiPatchMetadata` WHERE `Property` = '{0}'";
22 private static readonly Encoding XmlOutputEncoding = new UTF8Encoding(false);
23
24 public RowDictionary<WixBundlePayloadRow> AuthoredPayloads { private get; set; }
25
26 public PackageFacade Facade { private get; set; }
27
28 public Table WixBundlePatchTargetCodeTable { private get; set; }
29
30 /// <summary>
31 /// Processes the Msp packages to add properties and payloads from the Msp packages.
32 /// </summary>
33 public void Execute()
34 {
35 WixBundlePayloadRow packagePayload = this.AuthoredPayloads.Get(this.Facade.Package.PackagePayload);
36
37 string sourcePath = packagePayload.FullFileName;
38
39 try
40 {
41 // Read data out of the msp database...
42 using (Dtf.SummaryInfo sumInfo = new Dtf.SummaryInfo(sourcePath, false))
43 {
44 this.Facade.MspPackage.PatchCode = sumInfo.RevisionNumber.Substring(0, 38);
45 }
46
47 using (Dtf.Database db = new Dtf.Database(sourcePath))
48 {
49 if (String.IsNullOrEmpty(this.Facade.Package.DisplayName))
50 {
51 this.Facade.Package.DisplayName = ProcessMspPackageCommand.GetPatchMetadataProperty(db, "DisplayName");
52 }
53
54 if (String.IsNullOrEmpty(this.Facade.Package.Description))
55 {
56 this.Facade.Package.Description = ProcessMspPackageCommand.GetPatchMetadataProperty(db, "Description");
57 }
58
59 this.Facade.MspPackage.Manufacturer = ProcessMspPackageCommand.GetPatchMetadataProperty(db, "ManufacturerName");
60 }
61
62 this.ProcessPatchXml(packagePayload, sourcePath);
63 }
64 catch (Dtf.InstallerException e)
65 {
66 Messaging.Instance.OnMessage(WixErrors.UnableToReadPackageInformation(packagePayload.SourceLineNumbers, sourcePath, e.Message));
67 return;
68 }
69
70 if (String.IsNullOrEmpty(this.Facade.Package.CacheId))
71 {
72 this.Facade.Package.CacheId = this.Facade.MspPackage.PatchCode;
73 }
74 }
75
76 private void ProcessPatchXml(WixBundlePayloadRow packagePayload, string sourcePath)
77 {
78 HashSet<string> uniqueTargetCodes = new HashSet<string>();
79
80 string patchXml = Dtf.Installer.ExtractPatchXmlData(sourcePath);
81
82 XmlDocument doc = new XmlDocument();
83 doc.LoadXml(patchXml);
84
85 XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
86 nsmgr.AddNamespace("p", "http://www.microsoft.com/msi/patch_applicability.xsd");
87
88 // Determine target ProductCodes and/or UpgradeCodes.
89 foreach (XmlNode node in doc.SelectNodes("/p:MsiPatch/p:TargetProduct", nsmgr))
90 {
91 // If this patch targets a product code, this is the best case.
92 XmlNode targetCodeElement = node.SelectSingleNode("p:TargetProductCode", nsmgr);
93 WixBundlePatchTargetCodeAttributes attributes = WixBundlePatchTargetCodeAttributes.None;
94
95 if (ProcessMspPackageCommand.TargetsCode(targetCodeElement))
96 {
97 attributes = WixBundlePatchTargetCodeAttributes.TargetsProductCode;
98 }
99 else // maybe targets an upgrade code?
100 {
101 targetCodeElement = node.SelectSingleNode("p:UpgradeCode", nsmgr);
102 if (ProcessMspPackageCommand.TargetsCode(targetCodeElement))
103 {
104 attributes = WixBundlePatchTargetCodeAttributes.TargetsUpgradeCode;
105 }
106 else // this patch targets an unknown number of products
107 {
108 this.Facade.MspPackage.Attributes |= WixBundleMspPackageAttributes.TargetUnspecified;
109 }
110 }
111
112 string targetCode = targetCodeElement.InnerText;
113
114 if (uniqueTargetCodes.Add(targetCode))
115 {
116 WixBundlePatchTargetCodeRow row = (WixBundlePatchTargetCodeRow)this.WixBundlePatchTargetCodeTable.CreateRow(packagePayload.SourceLineNumbers);
117 row.MspPackageId = packagePayload.Id;
118 row.TargetCode = targetCode;
119 row.Attributes = attributes;
120 }
121 }
122
123 // Suppress patch sequence data for improved performance.
124 XmlNode root = doc.DocumentElement;
125 foreach (XmlNode node in root.SelectNodes("p:SequenceData", nsmgr))
126 {
127 root.RemoveChild(node);
128 }
129
130 // Save the XML as compact as possible.
131 using (StringWriter writer = new StringWriter())
132 {
133 XmlWriterSettings settings = new XmlWriterSettings()
134 {
135 Encoding = ProcessMspPackageCommand.XmlOutputEncoding,
136 Indent = false,
137 NewLineChars = string.Empty,
138 NewLineHandling = NewLineHandling.Replace,
139 };
140
141 using (XmlWriter xmlWriter = XmlWriter.Create(writer, settings))
142 {
143 doc.WriteTo(xmlWriter);
144 }
145
146 this.Facade.MspPackage.PatchXml = writer.ToString();
147 }
148 }
149
150 /// <summary>
151 /// Queries a Windows Installer patch database for a Property value from the MsiPatchMetadata table.
152 /// </summary>
153 /// <param name="db">Database to query.</param>
154 /// <param name="property">Property to examine.</param>
155 /// <returns>String value for result or null if query doesn't match a single result.</returns>
156 private static string GetPatchMetadataProperty(Dtf.Database db, string property)
157 {
158 try
159 {
160 return db.ExecuteScalar(PatchMetadataPropertyQuery(property)).ToString();
161 }
162 catch (Dtf.InstallerException)
163 {
164 }
165
166 return null;
167 }
168
169 private static string PatchMetadataPropertyQuery(string property)
170 {
171 // quick sanity check that we'll be creating a valid query...
172 // TODO: Are there any other special characters we should be looking for?
173 Debug.Assert(!property.Contains("'"));
174
175 return String.Format(CultureInfo.InvariantCulture, ProcessMspPackageCommand.PatchMetadataFormat, property);
176 }
177
178 private static bool TargetsCode(XmlNode node)
179 {
180 if (null != node)
181 {
182 XmlAttribute attr = node.Attributes["Validate"];
183 return null != attr && "true".Equals(attr.Value);
184 }
185
186 return false;
187 }
188 }
189}