aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core/PatchTransform.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.Core/PatchTransform.cs')
-rw-r--r--src/WixToolset.Core/PatchTransform.cs274
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
3namespace 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}