diff options
Diffstat (limited to 'src/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs')
-rw-r--r-- | src/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs | 189 |
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 | |||
3 | namespace 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 | } | ||