main
cs 235 lines 9 KB
Raw
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
3 namespace WixToolset.Core.Burn.Bundles
4 {
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using System.Text;
10 using System.Xml;
11 using WixToolset.Core.Native.Msi;
12 using WixToolset.Data;
13 using WixToolset.Data.Symbols;
14 using WixToolset.Extensibility.Services;
15
16 /// <summary>
17 /// Initializes package state from the Msp contents.
18 /// </summary>
19 internal class ProcessMspPackageCommand
20 {
21 private const string PatchMetadataQuery = "SELECT `Value` FROM `MsiPatchMetadata` WHERE `Property` = ?";
22 private static readonly XmlWriterSettings XmlSettings = new XmlWriterSettings()
23 {
24 Encoding = new UTF8Encoding(false),
25 Indent = false,
26 NewLineChars = String.Empty,
27 NewLineHandling = NewLineHandling.Replace,
28 };
29
30 public ProcessMspPackageCommand(IMessaging messaging, IntermediateSection section, PackageFacade facade, Dictionary<string, WixBundlePayloadSymbol> payloadSymbols)
31 {
32 this.Messaging = messaging;
33 this.Section = section;
34
35 this.ChainPackage = facade.PackageSymbol;
36 this.MspPackage = (WixBundleMspPackageSymbol)facade.SpecificPackageSymbol;
37 this.PackagePayload = payloadSymbols[this.ChainPackage.PayloadRef];
38 }
39
40 private IMessaging Messaging { get; }
41
42 private WixBundlePackageSymbol ChainPackage { get; }
43
44 private WixBundleMspPackageSymbol MspPackage { get; }
45
46 private WixBundlePayloadSymbol PackagePayload { get; }
47
48 private IntermediateSection Section { get; }
49
50 /// <summary>
51 /// Processes the Msp packages to add properties and payloads from the Msp packages.
52 /// </summary>
53 public void Execute()
54 {
55 var harvestedMspPackage = this.Section.Symbols.OfType<WixBundleHarvestedMspPackageSymbol>()
56 .Where(h => h.Id.Id == this.PackagePayload.Id.Id)
57 .SingleOrDefault();
58
59 if (harvestedMspPackage == null)
60 {
61 harvestedMspPackage = this.HarvestPackage();
62
63 if (harvestedMspPackage == null)
64 {
65 return;
66 }
67 }
68
69 this.MspPackage.PatchCode = harvestedMspPackage.PatchCode;
70 this.MspPackage.Manufacturer = harvestedMspPackage.ManufacturerName;
71 this.MspPackage.PatchXml = harvestedMspPackage.PatchXml;
72
73 if (String.IsNullOrEmpty(this.ChainPackage.DisplayName))
74 {
75 this.ChainPackage.DisplayName = harvestedMspPackage.DisplayName;
76 }
77
78 if (String.IsNullOrEmpty(this.ChainPackage.Description))
79 {
80 this.ChainPackage.Description = harvestedMspPackage.Description;
81 }
82
83 if (String.IsNullOrEmpty(this.ChainPackage.CacheId))
84 {
85 this.ChainPackage.CacheId = CacheIdGenerator.GenerateLocalCacheId(this.Messaging, harvestedMspPackage, this.PackagePayload, this.MspPackage.SourceLineNumbers, "MspPackage");
86 }
87 }
88
89 private WixBundleHarvestedMspPackageSymbol HarvestPackage()
90 {
91 string patchCode;
92 string displayName;
93 string description;
94 string manufacturerName;
95 string patchXml;
96
97 var sourcePath = this.PackagePayload.SourceFile.Path;
98
99 try
100 {
101 using (var db = new Database(sourcePath, OpenDatabase.ReadOnly | OpenDatabase.OpenPatchFile))
102 {
103 // Read data out of the msp database...
104 using (var sumInfo = new SummaryInformation(db))
105 {
106 var patchCodeValue = sumInfo.GetProperty(SummaryInformation.Patch.PatchCode);
107 patchCode = patchCodeValue.Substring(0, 38);
108 }
109
110 using (var view = db.OpenView(PatchMetadataQuery))
111 {
112 displayName = ProcessMspPackageCommand.GetPatchMetadataProperty(view, "DisplayName");
113 description = ProcessMspPackageCommand.GetPatchMetadataProperty(view, "Description");
114 manufacturerName = ProcessMspPackageCommand.GetPatchMetadataProperty(view, "ManufacturerName");
115 }
116 }
117
118 patchXml = ProcessMspPackageCommand.ProcessPatchXml(sourcePath, this.Section, this.PackagePayload.SourceLineNumbers, this.PackagePayload.Id);
119 }
120 catch (MsiException e)
121 {
122 this.Messaging.Write(ErrorMessages.UnableToReadPackageInformation(this.PackagePayload.SourceLineNumbers, sourcePath, e.Message));
123 return null;
124 }
125
126 return this.Section.AddSymbol(new WixBundleHarvestedMspPackageSymbol(this.PackagePayload.SourceLineNumbers, this.PackagePayload.Id)
127 {
128 PatchCode = patchCode,
129 DisplayName = displayName,
130 Description = description,
131 ManufacturerName = manufacturerName,
132 PatchXml = patchXml,
133 });
134 }
135
136 private static string ProcessPatchXml(string sourcePath, IntermediateSection section, SourceLineNumber sourceLineNumbers, Identifier id)
137 {
138 var uniqueTargetCodes = new Dictionary<string, WixBundlePatchTargetCodeSymbol>();
139
140 var patchXml = Installer.ExtractPatchXml(sourcePath);
141
142 var doc = new XmlDocument();
143 doc.LoadXml(patchXml);
144
145 var nsmgr = new XmlNamespaceManager(doc.NameTable);
146 nsmgr.AddNamespace("p", "http://www.microsoft.com/msi/patch_applicability.xsd");
147
148 // Determine target ProductCodes and/or UpgradeCodes.
149 foreach (XmlNode node in doc.SelectNodes("/p:MsiPatch/p:TargetProduct", nsmgr))
150 {
151 // If this patch targets a product code, this is the best case.
152 var targetCodeElement = node.SelectSingleNode("p:TargetProductCode", nsmgr);
153 WixBundlePatchTargetCodeType type;
154
155 if (ProcessMspPackageCommand.TargetsCode(targetCodeElement))
156 {
157 type = WixBundlePatchTargetCodeType.ProductCode;
158 }
159 else // maybe targets an upgrade code?
160 {
161 targetCodeElement = node.SelectSingleNode("p:UpgradeCode", nsmgr);
162 if (ProcessMspPackageCommand.TargetsCode(targetCodeElement))
163 {
164 type = WixBundlePatchTargetCodeType.UpgradeCode;
165 }
166 else // this patch targets an unknown number of products
167 {
168 type = WixBundlePatchTargetCodeType.Unspecified;
169 }
170 }
171
172 var targetCode = targetCodeElement.InnerText;
173
174 if (!uniqueTargetCodes.TryGetValue(targetCode, out var existing))
175 {
176 var symbol = section.AddSymbol(new WixBundlePatchTargetCodeSymbol(sourceLineNumbers)
177 {
178 PackagePayloadRef = id.Id,
179 TargetCode = targetCode,
180 Attributes = 0,
181 Type = type,
182 });
183
184 uniqueTargetCodes.Add(targetCode, symbol);
185 }
186 else if (type == WixBundlePatchTargetCodeType.Unspecified)
187 {
188 existing.Type = type;
189 }
190 }
191
192 // Suppress patch sequence data for improved performance.
193 var root = doc.DocumentElement;
194 foreach (XmlNode node in root.SelectNodes("p:SequenceData", nsmgr))
195 {
196 root.RemoveChild(node);
197 }
198
199 string compactPatchXml;
200
201 // Save the XML as compact as possible.
202 using (var writer = new StringWriter())
203 {
204 using (var xmlWriter = XmlWriter.Create(writer, XmlSettings))
205 {
206 doc.WriteTo(xmlWriter);
207 }
208
209 compactPatchXml = writer.ToString();
210 }
211
212 return compactPatchXml;
213 }
214
215 private static string GetPatchMetadataProperty(View view, string property)
216 {
217 using (var queryRecord = new Record(1))
218 {
219 queryRecord[1] = property;
220
221 view.Execute(queryRecord);
222
223 using (var record = view.Fetch())
224 {
225 return record?.GetString(1);
226 }
227 }
228 }
229
230 private static bool TargetsCode(XmlNode node)
231 {
232 return "true" == node?.Attributes["Validate"]?.Value;
233 }
234 }
235 }