| 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.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 | |
| 16 | /// <summary> |
| 17 | /// Converts a wixout representation of an MSM database into a ComponentGroup the form of WiX source. |
| 18 | /// </summary> |
| 19 | public sealed class Melter |
| 20 | { |
| 21 | #if TODO_MELT |
| 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.Package; |
| 88 | this.decompiler.KeepModularizationIds = 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 | #endif |
| 398 | } |
| 399 | } |