diff options
Diffstat (limited to 'src/WixToolset.Core/PatchTransform.cs')
-rw-r--r-- | src/WixToolset.Core/PatchTransform.cs | 274 |
1 files changed, 274 insertions, 0 deletions
diff --git a/src/WixToolset.Core/PatchTransform.cs b/src/WixToolset.Core/PatchTransform.cs new file mode 100644 index 00000000..c87b1a21 --- /dev/null +++ b/src/WixToolset.Core/PatchTransform.cs | |||
@@ -0,0 +1,274 @@ | |||
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 | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections; | ||
7 | using System.Globalization; | ||
8 | using System.Text; | ||
9 | using System.Text.RegularExpressions; | ||
10 | using WixToolset.Data; | ||
11 | using WixToolset.Extensibility; | ||
12 | |||
13 | public class PatchTransform : IMessageHandler | ||
14 | { | ||
15 | private string baseline; | ||
16 | private Output transform; | ||
17 | private string transformPath; | ||
18 | |||
19 | public string Baseline | ||
20 | { | ||
21 | get { return this.baseline; } | ||
22 | } | ||
23 | |||
24 | public Output Transform | ||
25 | { | ||
26 | get | ||
27 | { | ||
28 | if (null == this.transform) | ||
29 | { | ||
30 | this.transform = Output.Load(this.transformPath, false); | ||
31 | } | ||
32 | |||
33 | return this.transform; | ||
34 | } | ||
35 | } | ||
36 | |||
37 | public string TransformPath | ||
38 | { | ||
39 | get { return this.transformPath; } | ||
40 | } | ||
41 | |||
42 | public PatchTransform(string transformPath, string baseline) | ||
43 | { | ||
44 | this.transformPath = transformPath; | ||
45 | this.baseline = baseline; | ||
46 | } | ||
47 | |||
48 | /// <summary> | ||
49 | /// Validates that the differences in the transform are valid for patch transforms. | ||
50 | /// </summary> | ||
51 | public void Validate() | ||
52 | { | ||
53 | // Changing the ProdocutCode in a patch transform is not recommended. | ||
54 | Table propertyTable = this.Transform.Tables["Property"]; | ||
55 | if (null != propertyTable) | ||
56 | { | ||
57 | foreach (Row row in propertyTable.Rows) | ||
58 | { | ||
59 | // Only interested in modified rows; fast check. | ||
60 | if (RowOperation.Modify == row.Operation) | ||
61 | { | ||
62 | if (0 == String.CompareOrdinal("ProductCode", (string)row[0])) | ||
63 | { | ||
64 | this.OnMessage(WixWarnings.MajorUpgradePatchNotRecommended()); | ||
65 | } | ||
66 | } | ||
67 | } | ||
68 | } | ||
69 | |||
70 | // If there is nothing in the component table we can return early because the remaining checks are component based. | ||
71 | Table componentTable = this.Transform.Tables["Component"]; | ||
72 | if (null == componentTable) | ||
73 | { | ||
74 | return; | ||
75 | } | ||
76 | |||
77 | // Index Feature table row operations | ||
78 | Table featureTable = this.Transform.Tables["Feature"]; | ||
79 | Table featureComponentsTable = this.Transform.Tables["FeatureComponents"]; | ||
80 | Hashtable featureOps = null; | ||
81 | if (null != featureTable) | ||
82 | { | ||
83 | int capacity = featureTable.Rows.Count; | ||
84 | featureOps = new Hashtable(capacity); | ||
85 | |||
86 | foreach (Row row in featureTable.Rows) | ||
87 | { | ||
88 | featureOps[(string)row[0]] = row.Operation; | ||
89 | } | ||
90 | } | ||
91 | else | ||
92 | { | ||
93 | featureOps = new Hashtable(); | ||
94 | } | ||
95 | |||
96 | // Index Component table and check for keypath modifications | ||
97 | Hashtable deletedComponent = new Hashtable(); | ||
98 | Hashtable componentKeyPath = new Hashtable(); | ||
99 | foreach (Row row in componentTable.Rows) | ||
100 | { | ||
101 | string id = row.Fields[0].Data.ToString(); | ||
102 | string keypath = (null == row.Fields[5].Data) ? String.Empty : row.Fields[5].Data.ToString(); | ||
103 | |||
104 | componentKeyPath.Add(id, keypath); | ||
105 | if (RowOperation.Delete == row.Operation) | ||
106 | { | ||
107 | deletedComponent.Add(id, row); | ||
108 | } | ||
109 | else if (RowOperation.Modify == row.Operation) | ||
110 | { | ||
111 | if (row.Fields[1].Modified) | ||
112 | { | ||
113 | // Changing the guid of a component is equal to deleting the old one and adding a new one. | ||
114 | deletedComponent.Add(id, row); | ||
115 | } | ||
116 | |||
117 | // If the keypath is modified its an error | ||
118 | if (row.Fields[5].Modified) | ||
119 | { | ||
120 | this.OnMessage(WixErrors.InvalidKeypathChange(row.SourceLineNumbers, id, this.transformPath)); | ||
121 | } | ||
122 | } | ||
123 | } | ||
124 | |||
125 | // Verify changes in the file table | ||
126 | Table fileTable = this.Transform.Tables["File"]; | ||
127 | if (null != fileTable) | ||
128 | { | ||
129 | Hashtable componentWithChangedKeyPath = new Hashtable(); | ||
130 | foreach (Row row in fileTable.Rows) | ||
131 | { | ||
132 | if (RowOperation.None != row.Operation) | ||
133 | { | ||
134 | string fileId = row.Fields[0].Data.ToString(); | ||
135 | string componentId = row.Fields[1].Data.ToString(); | ||
136 | |||
137 | // If this file is the keypath of a component | ||
138 | if (String.Equals((string)componentKeyPath[componentId], fileId, StringComparison.Ordinal)) | ||
139 | { | ||
140 | if (row.Fields[2].Modified) | ||
141 | { | ||
142 | // You cant change the filename of a file that is the keypath of a component. | ||
143 | this.OnMessage(WixErrors.InvalidKeypathChange(row.SourceLineNumbers, componentId, this.transformPath)); | ||
144 | } | ||
145 | |||
146 | if (!componentWithChangedKeyPath.ContainsKey(componentId)) | ||
147 | { | ||
148 | componentWithChangedKeyPath.Add(componentId, fileId); | ||
149 | } | ||
150 | } | ||
151 | |||
152 | if (RowOperation.Delete == row.Operation) | ||
153 | { | ||
154 | // If the file is removed from a component that is not deleted. | ||
155 | if (!deletedComponent.ContainsKey(componentId)) | ||
156 | { | ||
157 | bool foundRemoveFileEntry = false; | ||
158 | string filename = Msi.Installer.GetName((string)row[2], false, true); | ||
159 | |||
160 | Table removeFileTable = this.Transform.Tables["RemoveFile"]; | ||
161 | if (null != removeFileTable) | ||
162 | { | ||
163 | foreach (Row removeFileRow in removeFileTable.Rows) | ||
164 | { | ||
165 | if (RowOperation.Delete == removeFileRow.Operation) | ||
166 | { | ||
167 | continue; | ||
168 | } | ||
169 | |||
170 | if (componentId == (string)removeFileRow[1]) | ||
171 | { | ||
172 | // Check if there is a RemoveFile entry for this file | ||
173 | if (null != removeFileRow[2]) | ||
174 | { | ||
175 | string removeFileName = Msi.Installer.GetName((string)removeFileRow[2], false, true); | ||
176 | |||
177 | // Convert the MSI format for a wildcard string to Regex format. | ||
178 | removeFileName = removeFileName.Replace('.', '|').Replace('?', '.').Replace("*", ".*").Replace("|", "\\."); | ||
179 | |||
180 | Regex regex = new Regex(removeFileName, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); | ||
181 | if (regex.IsMatch(filename)) | ||
182 | { | ||
183 | foundRemoveFileEntry = true; | ||
184 | break; | ||
185 | } | ||
186 | } | ||
187 | } | ||
188 | } | ||
189 | } | ||
190 | |||
191 | if (!foundRemoveFileEntry) | ||
192 | { | ||
193 | this.OnMessage(WixWarnings.InvalidRemoveFile(row.SourceLineNumbers, fileId, componentId)); | ||
194 | } | ||
195 | } | ||
196 | } | ||
197 | } | ||
198 | } | ||
199 | } | ||
200 | |||
201 | if (0 < deletedComponent.Count) | ||
202 | { | ||
203 | // Index FeatureComponents table. | ||
204 | Hashtable featureComponents = new Hashtable(); | ||
205 | |||
206 | if (null != featureComponentsTable) | ||
207 | { | ||
208 | foreach (Row row in featureComponentsTable.Rows) | ||
209 | { | ||
210 | ArrayList features; | ||
211 | string componentId = row.Fields[1].Data.ToString(); | ||
212 | |||
213 | if (featureComponents.Contains(componentId)) | ||
214 | { | ||
215 | features = (ArrayList)featureComponents[componentId]; | ||
216 | } | ||
217 | else | ||
218 | { | ||
219 | features = new ArrayList(); | ||
220 | featureComponents.Add(componentId, features); | ||
221 | } | ||
222 | features.Add(row.Fields[0].Data.ToString()); | ||
223 | } | ||
224 | } | ||
225 | |||
226 | // Check to make sure if a component was deleted, the feature was too. | ||
227 | foreach (DictionaryEntry entry in deletedComponent) | ||
228 | { | ||
229 | if (featureComponents.Contains(entry.Key.ToString())) | ||
230 | { | ||
231 | ArrayList features = (ArrayList)featureComponents[entry.Key.ToString()]; | ||
232 | foreach (string featureId in features) | ||
233 | { | ||
234 | if (!featureOps.ContainsKey(featureId) || RowOperation.Delete != (RowOperation)featureOps[featureId]) | ||
235 | { | ||
236 | // The feature was not deleted. | ||
237 | this.OnMessage(WixErrors.InvalidRemoveComponent(((Row)entry.Value).SourceLineNumbers, entry.Key.ToString(), featureId, this.transformPath)); | ||
238 | } | ||
239 | } | ||
240 | } | ||
241 | } | ||
242 | } | ||
243 | |||
244 | // Warn if new components are added to existing features | ||
245 | if (null != featureComponentsTable) | ||
246 | { | ||
247 | foreach (Row row in featureComponentsTable.Rows) | ||
248 | { | ||
249 | if (RowOperation.Add == row.Operation) | ||
250 | { | ||
251 | // Check if the feature is in the Feature table | ||
252 | string feature_ = (string)row[0]; | ||
253 | string component_ = (string)row[1]; | ||
254 | |||
255 | // Features may not be present if not referenced | ||
256 | if (!featureOps.ContainsKey(feature_) || RowOperation.Add != (RowOperation)featureOps[feature_]) | ||
257 | { | ||
258 | this.OnMessage(WixWarnings.NewComponentAddedToExistingFeature(row.SourceLineNumbers, component_, feature_, this.transformPath)); | ||
259 | } | ||
260 | } | ||
261 | } | ||
262 | } | ||
263 | } | ||
264 | |||
265 | /// <summary> | ||
266 | /// Sends a message to the message delegate if there is one. | ||
267 | /// </summary> | ||
268 | /// <param name="mea">Message event arguments.</param> | ||
269 | public void OnMessage(MessageEventArgs e) | ||
270 | { | ||
271 | Messaging.Instance.OnMessage(e); | ||
272 | } | ||
273 | } | ||
274 | } | ||