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