aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core/Melter.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.Core/Melter.cs')
-rw-r--r--src/WixToolset.Core/Melter.cs398
1 files changed, 398 insertions, 0 deletions
diff --git a/src/WixToolset.Core/Melter.cs b/src/WixToolset.Core/Melter.cs
new file mode 100644
index 00000000..ccc0cb6f
--- /dev/null
+++ b/src/WixToolset.Core/Melter.cs
@@ -0,0 +1,398 @@
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.CodeDom.Compiler;
7 using System.Collections;
8 using System.Collections.Generic;
9 using System.Collections.Specialized;
10 using System.Globalization;
11 using System.IO;
12 using System.Text;
13 using System.Text.RegularExpressions;
14 using WixToolset.Data;
15 using Wix = WixToolset.Data.Serialize;
16
17 /// <summary>
18 /// Converts a wixout representation of an MSM database into a ComponentGroup the form of WiX source.
19 /// </summary>
20 public sealed class Melter
21 {
22 private MelterCore core;
23 private Decompiler decompiler;
24
25 private Wix.ComponentGroup componentGroup;
26 private Wix.DirectoryRef primaryDirectoryRef;
27 private Wix.Fragment fragment;
28
29 private string id;
30 private string moduleId;
31 private const string nullGuid = "{00000000-0000-0000-0000-000000000000}";
32
33 public string Id
34 {
35 get { return this.id; }
36 set { this.id = value; }
37 }
38
39 public Decompiler Decompiler
40 {
41 get { return this.decompiler; }
42 set { this.decompiler = value; }
43 }
44
45 /// <summary>
46 /// Creates a new melter object.
47 /// </summary>
48 /// <param name="decompiler">The decompiler to use during the melting process.</param>
49 /// <param name="id">The Id to use for the ComponentGroup, DirectoryRef, and WixVariables. If null, defaults to the Module's Id</param>
50 public Melter(Decompiler decompiler, string id)
51 {
52 this.core = new MelterCore();
53
54 this.componentGroup = new Wix.ComponentGroup();
55 this.fragment = new Wix.Fragment();
56 this.primaryDirectoryRef = new Wix.DirectoryRef();
57
58 this.decompiler = decompiler;
59 this.id = id;
60
61 if (null == this.decompiler)
62 {
63 this.core.OnMessage(WixErrors.ExpectedDecompiler("The melting process"));
64 }
65 }
66
67 /// <summary>
68 /// Converts a Module wixout into a ComponentGroup.
69 /// </summary>
70 /// <param name="wixout">The output object representing the unbound merge module to melt.</param>
71 /// <returns>The converted Module as a ComponentGroup.</returns>
72 public Wix.Wix Melt(Output wixout)
73 {
74 this.moduleId = GetModuleId(wixout);
75
76 // Assign the default componentGroupId if none was specified
77 if (null == this.id)
78 {
79 this.id = this.moduleId;
80 }
81
82 this.componentGroup.Id = this.id;
83 this.primaryDirectoryRef.Id = this.id;
84
85 PreDecompile(wixout);
86
87 wixout.Type = OutputType.Product;
88 this.decompiler.TreatProductAsModule = true;
89 Wix.Wix wix = this.decompiler.Decompile(wixout);
90
91 if (null == wix)
92 {
93 return wix;
94 }
95
96 ConvertModule(wix);
97
98 return wix;
99 }
100
101 /// <summary>
102 /// Converts a Module to a ComponentGroup and adds all of its relevant elements to the main fragment.
103 /// </summary>
104 /// <param name="wix">The output object representing an unbound merge module.</param>
105 private void ConvertModule(Wix.Wix wix)
106 {
107 Wix.Product product = Melter.GetProduct(wix);
108
109 List<string> customActionsRemoved = new List<string>();
110 Dictionary<Wix.Custom, Wix.InstallExecuteSequence> customsToRemove = new Dictionary<Wix.Custom, Wix.InstallExecuteSequence>();
111
112 foreach (Wix.ISchemaElement child in product.Children)
113 {
114 Wix.Directory childDir = child as Wix.Directory;
115 if (null != childDir)
116 {
117 bool isTargetDir = this.WalkDirectory(childDir);
118 if (isTargetDir)
119 {
120 continue;
121 }
122 }
123 else
124 {
125 Wix.Dependency childDep = child as Wix.Dependency;
126 if (null != childDep)
127 {
128 this.AddPropertyRef(childDep.RequiredId);
129 continue;
130 }
131 else if (child is Wix.Package)
132 {
133 continue;
134 }
135 else if (child is Wix.CustomAction)
136 {
137 Wix.CustomAction customAction = child as Wix.CustomAction;
138 string directoryId;
139 if (StartsWithStandardDirectoryId(customAction.Id, out directoryId) && customAction.Property == customAction.Id)
140 {
141 customActionsRemoved.Add(customAction.Id);
142 continue;
143 }
144 }
145 else if (child is Wix.InstallExecuteSequence)
146 {
147 Wix.InstallExecuteSequence installExecuteSequence = child as Wix.InstallExecuteSequence;
148
149 foreach (Wix.ISchemaElement sequenceChild in installExecuteSequence.Children)
150 {
151 Wix.Custom custom = sequenceChild as Wix.Custom;
152 string directoryId;
153 if (custom != null && StartsWithStandardDirectoryId(custom.Action, out directoryId))
154 {
155 customsToRemove.Add(custom, installExecuteSequence);
156 }
157 }
158 }
159 }
160
161 this.fragment.AddChild(child);
162 }
163
164 // For any customaction that we removed, also remove the scheduling of that action.
165 foreach (Wix.Custom custom in customsToRemove.Keys)
166 {
167 if (customActionsRemoved.Contains(custom.Action))
168 {
169 ((Wix.InstallExecuteSequence)customsToRemove[custom]).RemoveChild(custom);
170 }
171 }
172
173 AddProperty(this.moduleId, this.id);
174
175 wix.RemoveChild(product);
176 wix.AddChild(this.fragment);
177
178 this.fragment.AddChild(this.componentGroup);
179 this.fragment.AddChild(this.primaryDirectoryRef);
180 }
181
182 /// <summary>
183 /// Gets the module from the Wix object.
184 /// </summary>
185 /// <param name="wix">The Wix object.</param>
186 /// <returns>The Module in the Wix object, null if no Module was found</returns>
187 private static Wix.Product GetProduct(Wix.Wix wix)
188 {
189 foreach (Wix.ISchemaElement element in wix.Children)
190 {
191 Wix.Product productElement = element as Wix.Product;
192 if (null != productElement)
193 {
194 return productElement;
195 }
196 }
197 return null;
198 }
199
200 /// <summary>
201 /// Adds a PropertyRef to the main Fragment.
202 /// </summary>
203 /// <param name="propertyRefId">Id of the PropertyRef.</param>
204 private void AddPropertyRef(string propertyRefId)
205 {
206 Wix.PropertyRef propertyRef = new Wix.PropertyRef();
207 propertyRef.Id = propertyRefId;
208 this.fragment.AddChild(propertyRef);
209 }
210
211 /// <summary>
212 /// Adds a Property to the main Fragment.
213 /// </summary>
214 /// <param name="propertyId">Id of the Property.</param>
215 /// <param name="value">Value of the Property.</param>
216 private void AddProperty(string propertyId, string value)
217 {
218 Wix.Property property = new Wix.Property();
219 property.Id = propertyId;
220 property.Value = value;
221 this.fragment.AddChild(property);
222 }
223
224 /// <summary>
225 /// Walks a directory structure obtaining Component Id's and Standard Directory Id's.
226 /// </summary>
227 /// <param name="directory">The Directory to walk.</param>
228 /// <returns>true if the directory is TARGETDIR.</returns>
229 private bool WalkDirectory(Wix.Directory directory)
230 {
231 bool isTargetDir = false;
232 if ("TARGETDIR" == directory.Id)
233 {
234 isTargetDir = true;
235 }
236
237 string standardDirectoryId = null;
238 if (Melter.StartsWithStandardDirectoryId(directory.Id, out standardDirectoryId) && !isTargetDir)
239 {
240 this.AddSetPropertyCustomAction(directory.Id, String.Format(CultureInfo.InvariantCulture, "[{0}]", standardDirectoryId));
241 }
242
243 foreach (Wix.ISchemaElement child in directory.Children)
244 {
245 Wix.Directory childDir = child as Wix.Directory;
246 if (null != childDir)
247 {
248 if (isTargetDir)
249 {
250 this.primaryDirectoryRef.AddChild(child);
251 }
252 this.WalkDirectory(childDir);
253 }
254 else
255 {
256 Wix.Component childComponent = child as Wix.Component;
257 if (null != childComponent)
258 {
259 if (isTargetDir)
260 {
261 this.primaryDirectoryRef.AddChild(child);
262 }
263 this.AddComponentRef(childComponent);
264 }
265 }
266 }
267
268 return isTargetDir;
269 }
270
271 /// <summary>
272 /// Gets the module Id out of the Output object.
273 /// </summary>
274 /// <param name="wixout">The output object.</param>
275 /// <returns>The module Id from the Output object.</returns>
276 private string GetModuleId(Output wixout)
277 {
278 // get the moduleId from the wixout
279 Table moduleSignatureTable = wixout.Tables["ModuleSignature"];
280 if (null == moduleSignatureTable || 0 >= moduleSignatureTable.Rows.Count)
281 {
282 this.core.OnMessage(WixErrors.ExpectedTableInMergeModule("ModuleSignature"));
283 }
284 return moduleSignatureTable.Rows[0].Fields[0].Data.ToString();
285 }
286
287 /// <summary>
288 /// Determines if the directory Id starts with a standard directory id.
289 /// </summary>
290 /// <param name="directoryId">The directory id.</param>
291 /// <param name="standardDirectoryId">The standard directory id.</param>
292 /// <returns>true if the directory starts with a standard directory id.</returns>
293 private static bool StartsWithStandardDirectoryId(string directoryId, out string standardDirectoryId)
294 {
295 standardDirectoryId = null;
296 foreach (string id in WindowsInstallerStandard.GetStandardDirectories())
297 {
298 if (directoryId.StartsWith(id, StringComparison.Ordinal))
299 {
300 standardDirectoryId = id;
301 return true;
302 }
303 }
304 return false;
305 }
306
307 /// <summary>
308 /// Adds a ComponentRef to the main ComponentGroup.
309 /// </summary>
310 /// <param name="component">The component to add.</param>
311 private void AddComponentRef(Wix.Component component)
312 {
313 Wix.ComponentRef componentRef = new Wix.ComponentRef();
314 componentRef.Id = component.Id;
315 this.componentGroup.AddChild(componentRef);
316 }
317
318 /// <summary>
319 /// Adds a SetProperty CA for a Directory.
320 /// </summary>
321 /// <param name="propertyId">The Id of the Property to set.</param>
322 /// <param name="value">The value to set the Property to.</param>
323 private void AddSetPropertyCustomAction(string propertyId, string value)
324 {
325 // Add the action
326 Wix.CustomAction customAction = new Wix.CustomAction();
327 customAction.Id = propertyId;
328 customAction.Property = propertyId;
329 customAction.Value = value;
330 this.fragment.AddChild(customAction);
331
332 // Schedule the action
333 Wix.InstallExecuteSequence installExecuteSequence = new Wix.InstallExecuteSequence();
334 Wix.Custom custom = new Wix.Custom();
335 custom.Action = customAction.Id;
336 custom.Before = "CostInitialize";
337 installExecuteSequence.AddChild(custom);
338 this.fragment.AddChild(installExecuteSequence);
339 }
340
341 /// <summary>
342 /// Does any operations to the wixout that would need to be done before decompiling.
343 /// </summary>
344 /// <param name="wixout">The output object representing the unbound merge module.</param>
345 private void PreDecompile(Output wixout)
346 {
347 string wixVariable = String.Format(CultureInfo.InvariantCulture, "!(wix.{0}", this.id);
348
349 foreach (Table table in wixout.Tables)
350 {
351 // Determine if the table has a feature foreign key
352 bool hasFeatureForeignKey = false;
353 foreach (ColumnDefinition columnDef in table.Definition.Columns)
354 {
355 if (null != columnDef.KeyTable)
356 {
357 string[] keyTables = columnDef.KeyTable.Split(';');
358 foreach (string keyTable in keyTables)
359 {
360 if ("Feature" == keyTable)
361 {
362 hasFeatureForeignKey = true;
363 break;
364 }
365 }
366 }
367 }
368
369 // If this table has no foreign keys to the feature table, skip it.
370 if (!hasFeatureForeignKey)
371 {
372 continue;
373 }
374
375 // Go through all the rows and replace the null guid with the wix variable
376 // for columns that are foreign keys into the feature table.
377 foreach (Row row in table.Rows)
378 {
379 foreach (Field field in row.Fields)
380 {
381 if (null != field.Column.KeyTable)
382 {
383 string[] keyTables = field.Column.KeyTable.Split(';');
384 foreach (string keyTable in keyTables)
385 {
386 if ("Feature" == keyTable)
387 {
388 field.Data = field.Data.ToString().Replace(nullGuid, wixVariable);
389 break;
390 }
391 }
392 }
393 }
394 }
395 }
396 }
397 }
398}