| 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.Core.WindowsInstaller.Bind |
| 4 | { |
| 5 | using System; |
| 6 | using System.Collections.Generic; |
| 7 | using System.IO; |
| 8 | using System.Linq; |
| 9 | using System.Threading; |
| 10 | using WixToolset.Data; |
| 11 | using WixToolset.Data.Symbols; |
| 12 | using WixToolset.Data.WindowsInstaller; |
| 13 | using WixToolset.Extensibility; |
| 14 | using WixToolset.Extensibility.Data; |
| 15 | using WixToolset.Extensibility.Services; |
| 16 | |
| 17 | /// <summary> |
| 18 | /// Binds a databse. |
| 19 | /// </summary> |
| 20 | internal class BindDatabaseCommand |
| 21 | { |
| 22 | // As outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs. |
| 23 | internal static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}"); |
| 24 | |
| 25 | public BindDatabaseCommand(IBindContext context, IEnumerable<IWindowsInstallerBackendBinderExtension> backendExtension, IEnumerable<SubStorage> patchSubStorages = null) |
| 26 | { |
| 27 | this.ServiceProvider = context.ServiceProvider; |
| 28 | |
| 29 | this.Messaging = context.ServiceProvider.GetService<IMessaging>(); |
| 30 | |
| 31 | this.WindowsInstallerBackendHelper = context.ServiceProvider.GetService<IWindowsInstallerBackendHelper>(); |
| 32 | this.FileSystem = context.ServiceProvider.GetService<IFileSystem>(); |
| 33 | this.PathResolver = context.ServiceProvider.GetService<IPathResolver>(); |
| 34 | |
| 35 | this.CabbingThreadCount = context.CabbingThreadCount; |
| 36 | this.CabCachePath = context.CabCachePath; |
| 37 | this.DefaultCompressionLevel = context.DefaultCompressionLevel; |
| 38 | this.DelayedFields = context.DelayedFields; |
| 39 | this.ExpectedEmbeddedFiles = context.ExpectedEmbeddedFiles; |
| 40 | this.FileSystemManager = new FileSystemManager(this.FileSystem, context.FileSystemExtensions); |
| 41 | this.Intermediate = context.IntermediateRepresentation; |
| 42 | this.IntermediateFolder = context.IntermediateFolder; |
| 43 | this.OutputPath = context.OutputPath; |
| 44 | this.OutputPdbPath = context.PdbPath; |
| 45 | this.PdbType = context.PdbType; |
| 46 | this.ResolvedCodepage = context.ResolvedCodepage; |
| 47 | this.ResolvedSummaryInformationCodepage = context.ResolvedSummaryInformationCodepage; |
| 48 | this.ResolvedLcid = context.ResolvedLcid; |
| 49 | this.SuppressLayout = context.SuppressLayout; |
| 50 | |
| 51 | this.PatchSubStorages = patchSubStorages; |
| 52 | |
| 53 | this.BackendExtensions = backendExtension; |
| 54 | |
| 55 | this.CancellationToken = context.CancellationToken; |
| 56 | } |
| 57 | |
| 58 | private IServiceProvider ServiceProvider { get; } |
| 59 | |
| 60 | private IMessaging Messaging { get; } |
| 61 | |
| 62 | private IWindowsInstallerBackendHelper WindowsInstallerBackendHelper { get; } |
| 63 | |
| 64 | private IFileSystem FileSystem { get; } |
| 65 | |
| 66 | private IPathResolver PathResolver { get; } |
| 67 | |
| 68 | private int CabbingThreadCount { get; } |
| 69 | |
| 70 | private string CabCachePath { get; } |
| 71 | |
| 72 | private CompressionLevel? DefaultCompressionLevel { get; } |
| 73 | |
| 74 | public IEnumerable<IDelayedField> DelayedFields { get; } |
| 75 | |
| 76 | public IEnumerable<IExpectedExtractFile> ExpectedEmbeddedFiles { get; } |
| 77 | |
| 78 | public FileSystemManager FileSystemManager { get; } |
| 79 | |
| 80 | public bool DeltaBinaryPatch { get; set; } |
| 81 | |
| 82 | private IEnumerable<IWindowsInstallerBackendBinderExtension> BackendExtensions { get; } |
| 83 | |
| 84 | private IEnumerable<SubStorage> PatchSubStorages { get; } |
| 85 | |
| 86 | private Intermediate Intermediate { get; } |
| 87 | |
| 88 | private string OutputPath { get; } |
| 89 | |
| 90 | public PdbType PdbType { get; set; } |
| 91 | |
| 92 | private string OutputPdbPath { get; } |
| 93 | |
| 94 | private int? ResolvedCodepage { get; } |
| 95 | |
| 96 | private int? ResolvedSummaryInformationCodepage { get; } |
| 97 | |
| 98 | private int? ResolvedLcid { get; } |
| 99 | |
| 100 | private bool SuppressAddingValidationRows { get; } |
| 101 | |
| 102 | private bool SuppressLayout { get; } |
| 103 | |
| 104 | private string IntermediateFolder { get; } |
| 105 | |
| 106 | private CancellationToken CancellationToken { get; } |
| 107 | |
| 108 | private int CalculateCabbingThreadCount() |
| 109 | { |
| 110 | var processorCount = Environment.ProcessorCount; |
| 111 | |
| 112 | // If the number of processors is invalid, default to a single processor. |
| 113 | if (processorCount == 0) |
| 114 | { |
| 115 | processorCount = 1; |
| 116 | |
| 117 | this.Messaging.Write(WarningMessages.InvalidEnvironmentVariable("NUMBER_OF_PROCESSORS", Environment.ProcessorCount.ToString(), processorCount.ToString())); |
| 118 | } |
| 119 | |
| 120 | // If the cabbing thread count was provided, and it isn't more than double the number of processors, use it. |
| 121 | if (0 < this.CabbingThreadCount && this.CabbingThreadCount < processorCount * 2) |
| 122 | { |
| 123 | processorCount = this.CabbingThreadCount; |
| 124 | } |
| 125 | |
| 126 | this.Messaging.Write(VerboseMessages.SetCabbingThreadCount(processorCount.ToString())); |
| 127 | |
| 128 | return processorCount; |
| 129 | } |
| 130 | |
| 131 | public IBindResult Execute() |
| 132 | { |
| 133 | if (!this.Intermediate.HasLevel(Data.IntermediateLevels.Linked) || !this.Intermediate.HasLevel(Data.IntermediateLevels.Resolved)) |
| 134 | { |
| 135 | this.Messaging.Write(ErrorMessages.IntermediatesMustBeResolved(this.Intermediate.Id)); |
| 136 | } |
| 137 | |
| 138 | var section = this.Intermediate.Sections.Single(); |
| 139 | |
| 140 | var packageSymbol = (section.Type == SectionType.Package) ? this.GetSingleSymbol<WixPackageSymbol>(section) : null; |
| 141 | var moduleSymbol = (section.Type == SectionType.Module) ? this.GetSingleSymbol<WixModuleSymbol>(section) : null; |
| 142 | var patchSymbol = (section.Type == SectionType.Patch) ? this.GetSingleSymbol<WixPatchSymbol>(section) : null; |
| 143 | |
| 144 | var fileTransfers = new List<IFileTransfer>(); |
| 145 | var trackedFiles = new List<ITrackedFile>(); |
| 146 | |
| 147 | var containsMergeModules = false; |
| 148 | |
| 149 | int calculatedCabbingThreadCount = this.CalculateCabbingThreadCount(); |
| 150 | |
| 151 | // Load standard tables, authored custom tables, and extension custom tables. |
| 152 | TableDefinitionCollection tableDefinitions; |
| 153 | { |
| 154 | var command = new LoadTableDefinitionsCommand(this.Messaging, section, this.BackendExtensions); |
| 155 | command.Execute(); |
| 156 | |
| 157 | tableDefinitions = command.TableDefinitions; |
| 158 | } |
| 159 | |
| 160 | if (section.Type == SectionType.Package) |
| 161 | { |
| 162 | this.ProcessProductVersion(packageSymbol, section, validate: false); |
| 163 | } |
| 164 | |
| 165 | // Calculate codepage |
| 166 | var codepage = this.CalculateCodepage(packageSymbol, moduleSymbol, patchSymbol); |
| 167 | |
| 168 | // Process properties and create the delayed variable cache if needed. |
| 169 | Dictionary<string, string> variableCache = null; |
| 170 | string productLanguage = null; |
| 171 | { |
| 172 | var command = new ProcessPropertiesCommand(section, packageSymbol, this.ResolvedLcid ?? 0, this.DelayedFields.Any(), this.WindowsInstallerBackendHelper); |
| 173 | command.Execute(); |
| 174 | |
| 175 | variableCache = command.DelayedVariablesCache; |
| 176 | productLanguage = command.ProductLanguage; |
| 177 | } |
| 178 | |
| 179 | // Process the summary information table after properties are processed. |
| 180 | bool compressed; |
| 181 | bool longNames; |
| 182 | int installerVersion; |
| 183 | Platform platform; |
| 184 | string modularizationSuffix; |
| 185 | { |
| 186 | var branding = this.ServiceProvider.GetService<IWixBranding>(); |
| 187 | |
| 188 | var command = new BindSummaryInfoCommand(section, this.ResolvedSummaryInformationCodepage, productLanguage, this.WindowsInstallerBackendHelper, branding); |
| 189 | command.Execute(); |
| 190 | |
| 191 | compressed = command.Compressed; |
| 192 | longNames = command.LongNames; |
| 193 | installerVersion = command.InstallerVersion; |
| 194 | platform = command.Platform; |
| 195 | modularizationSuffix = command.ModularizationSuffix; |
| 196 | } |
| 197 | |
| 198 | // Sequence all the actions. |
| 199 | { |
| 200 | var command = new SequenceActionsCommand(this.Messaging, section); |
| 201 | command.Execute(); |
| 202 | } |
| 203 | |
| 204 | { |
| 205 | var command = new CreateSpecialPropertiesCommand(section); |
| 206 | command.Execute(); |
| 207 | } |
| 208 | |
| 209 | // Add missing CreateFolder symbols to null-keypath components. |
| 210 | { |
| 211 | var command = new AddCreateFoldersCommand(section); |
| 212 | command.Execute(); |
| 213 | } |
| 214 | |
| 215 | if (this.Messaging.EncounteredError) |
| 216 | { |
| 217 | return null; |
| 218 | } |
| 219 | |
| 220 | // Process dependency references. |
| 221 | if (SectionType.Package == section.Type || SectionType.Module == section.Type) |
| 222 | { |
| 223 | var dependencyRefs = section.Symbols.OfType<WixDependencyRefSymbol>().ToList(); |
| 224 | |
| 225 | if (dependencyRefs.Any()) |
| 226 | { |
| 227 | var command = new ProcessDependencyReferencesCommand(this.WindowsInstallerBackendHelper, section, dependencyRefs); |
| 228 | command.Execute(); |
| 229 | } |
| 230 | } |
| 231 | |
| 232 | // Process SoftwareTags in MSI packages. |
| 233 | if (SectionType.Package == section.Type) |
| 234 | { |
| 235 | var softwareTags = section.Symbols.OfType<WixPackageTagSymbol>().ToList(); |
| 236 | |
| 237 | if (softwareTags.Any()) |
| 238 | { |
| 239 | var command = new ProcessPackageSoftwareTagsCommand(section, this.WindowsInstallerBackendHelper, this.FileSystem, softwareTags, this.IntermediateFolder); |
| 240 | command.Execute(); |
| 241 | |
| 242 | trackedFiles.AddRange(command.TrackedFiles); |
| 243 | } |
| 244 | } |
| 245 | |
| 246 | // Extract files that come from binary .wixlibs and WixExtensions (this does not extract files from merge modules). |
| 247 | { |
| 248 | var extractedFiles = this.WindowsInstallerBackendHelper.ExtractEmbeddedFiles(this.ExpectedEmbeddedFiles); |
| 249 | |
| 250 | trackedFiles.AddRange(extractedFiles); |
| 251 | } |
| 252 | |
| 253 | // Update symbols that reference text files on disk. Some of those files may have come from .wixlibs and WixExtensions |
| 254 | // extracted above. |
| 255 | { |
| 256 | var command = new UpdateFromTextFilesCommand(this.Messaging, section); |
| 257 | command.Execute(); |
| 258 | } |
| 259 | |
| 260 | this.Intermediate.UpdateLevel(Data.WindowsInstaller.IntermediateLevels.FullyBound); |
| 261 | this.Messaging.Write(VerboseMessages.UpdatingFileInformation()); |
| 262 | |
| 263 | // This must occur after all variables and source paths have been resolved. |
| 264 | List<IFileFacade> allFileFacades; |
| 265 | List<IFileFacade> fileFacadesFromIntermediate; |
| 266 | List<IFileFacade> fileFacadesFromModule = null; |
| 267 | if (section.Type == SectionType.Patch) |
| 268 | { |
| 269 | var command = new GetFileFacadesFromTransforms(this.Messaging, this.WindowsInstallerBackendHelper, this.FileSystemManager, this.PatchSubStorages); |
| 270 | command.Execute(); |
| 271 | |
| 272 | allFileFacades = fileFacadesFromIntermediate = command.FileFacades; |
| 273 | } |
| 274 | else |
| 275 | { |
| 276 | var command = new GetFileFacadesCommand(section, this.WindowsInstallerBackendHelper); |
| 277 | command.Execute(); |
| 278 | |
| 279 | allFileFacades = fileFacadesFromIntermediate = command.FileFacades; |
| 280 | } |
| 281 | |
| 282 | // Retrieve file information from merge modules. |
| 283 | if (SectionType.Package == section.Type) |
| 284 | { |
| 285 | var wixMergeSymbols = section.Symbols.OfType<WixMergeSymbol>().ToList(); |
| 286 | |
| 287 | if (wixMergeSymbols.Any()) |
| 288 | { |
| 289 | containsMergeModules = true; |
| 290 | |
| 291 | var command = new ExtractMergeModuleFilesCommand(this.Messaging, this.WindowsInstallerBackendHelper, wixMergeSymbols, fileFacadesFromIntermediate, installerVersion, this.IntermediateFolder, this.SuppressLayout); |
| 292 | command.Execute(); |
| 293 | |
| 294 | fileFacadesFromModule = new List<IFileFacade>(command.MergeModulesFileFacades); |
| 295 | allFileFacades.AddRange(fileFacadesFromModule); |
| 296 | trackedFiles.AddRange(command.TrackedFiles); |
| 297 | } |
| 298 | } |
| 299 | |
| 300 | // stop processing if an error previously occurred |
| 301 | if (this.Messaging.EncounteredError) |
| 302 | { |
| 303 | return null; |
| 304 | } |
| 305 | |
| 306 | // Gather information about files that do not come from merge modules. |
| 307 | { |
| 308 | var command = new UpdateFileFacadesCommand(this.Messaging, this.FileSystem, section, allFileFacades, fileFacadesFromIntermediate, variableCache, overwriteHash: true, this.CancellationToken, calculatedCabbingThreadCount); |
| 309 | command.Execute(); |
| 310 | } |
| 311 | |
| 312 | // stop processing if an error previously occurred |
| 313 | if (this.Messaging.EncounteredError) |
| 314 | { |
| 315 | return null; |
| 316 | } |
| 317 | |
| 318 | // Now that the variable cache is populated, resolve any delayed fields. |
| 319 | if (this.DelayedFields.Any()) |
| 320 | { |
| 321 | this.WindowsInstallerBackendHelper.ResolveDelayedFields(this.DelayedFields, variableCache); |
| 322 | } |
| 323 | |
| 324 | // Now that delayed fields are processed, fixup the package version (if needed) and validate it |
| 325 | // which will short circuit duplicate errors later if the ProductVersion is invalid. |
| 326 | if (SectionType.Package == section.Type) |
| 327 | { |
| 328 | this.ProcessProductVersion(packageSymbol, section, validate: true); |
| 329 | } |
| 330 | |
| 331 | // If there are any backend extensions, give them the opportunity to process |
| 332 | // the section now that the fields have all be resolved. |
| 333 | // |
| 334 | if (this.BackendExtensions.Any()) |
| 335 | { |
| 336 | using (new IntermediateFieldContext("wix.bind.finalize")) |
| 337 | { |
| 338 | foreach (var extension in this.BackendExtensions) |
| 339 | { |
| 340 | extension.SymbolsFinalized(section); |
| 341 | } |
| 342 | |
| 343 | var reresolvedFiles = section.Symbols |
| 344 | .OfType<FileSymbol>() |
| 345 | .Where(s => s.Fields.Any(f => f?.Context == "wix.bind.finalize")) |
| 346 | .ToList(); |
| 347 | |
| 348 | if (reresolvedFiles.Any()) |
| 349 | { |
| 350 | var updatedFacades = reresolvedFiles.Select(f => allFileFacades.First(ff => ff.Id == f.Id?.Id)); |
| 351 | |
| 352 | var command = new UpdateFileFacadesCommand(this.Messaging, this.FileSystem, section, allFileFacades, updatedFacades, variableCache, overwriteHash: false, this.CancellationToken, calculatedCabbingThreadCount); |
| 353 | command.Execute(); |
| 354 | } |
| 355 | } |
| 356 | |
| 357 | if (this.Messaging.EncounteredError) |
| 358 | { |
| 359 | return null; |
| 360 | } |
| 361 | } |
| 362 | |
| 363 | if (SectionType.Package == section.Type) |
| 364 | { |
| 365 | var command = new ValidateWindowsInstallerProductConstraints(this.Messaging, section); |
| 366 | command.Execute(); |
| 367 | |
| 368 | if (this.Messaging.EncounteredError) |
| 369 | { |
| 370 | return null; |
| 371 | } |
| 372 | } |
| 373 | |
| 374 | // Assign files to media and update file sequences. |
| 375 | Dictionary<MediaSymbol, IEnumerable<IFileFacade>> filesByCabinetMedia; |
| 376 | IEnumerable<IFileFacade> uncompressedFiles; |
| 377 | { |
| 378 | var order = new OptimizeFileFacadesOrderCommand(this.WindowsInstallerBackendHelper, this.PathResolver, section, platform, allFileFacades); |
| 379 | order.Execute(); |
| 380 | |
| 381 | allFileFacades = order.FileFacades; |
| 382 | |
| 383 | var assign = new AssignMediaCommand(section, this.Messaging, allFileFacades, compressed); |
| 384 | assign.Execute(); |
| 385 | |
| 386 | filesByCabinetMedia = assign.FileFacadesByCabinetMedia; |
| 387 | uncompressedFiles = assign.UncompressedFileFacades; |
| 388 | |
| 389 | var update = new UpdateMediaSequencesCommand(section, allFileFacades); |
| 390 | update.Execute(); |
| 391 | } |
| 392 | |
| 393 | // stop processing if an error previously occurred |
| 394 | if (this.Messaging.EncounteredError) |
| 395 | { |
| 396 | return null; |
| 397 | } |
| 398 | |
| 399 | // Copy updated file facade data back into the transforms or symbols as appropriate. |
| 400 | if (section.Type == SectionType.Patch) |
| 401 | { |
| 402 | var command = new UpdateTransformsWithFileFacades(this.Messaging, section, this.PatchSubStorages, tableDefinitions, allFileFacades); |
| 403 | command.Execute(); |
| 404 | } |
| 405 | else |
| 406 | { |
| 407 | var command = new UpdateSymbolsWithFileFacadesCommand(section, allFileFacades); |
| 408 | command.Execute(); |
| 409 | } |
| 410 | |
| 411 | // Set generated component guids and validate all guids. |
| 412 | { |
| 413 | var command = new FinalizeComponentGuids(this.Messaging, this.WindowsInstallerBackendHelper, this.PathResolver, section, platform); |
| 414 | command.Execute(); |
| 415 | } |
| 416 | |
| 417 | // Time to create the WindowsInstallerData object. Try to put as much above here as possible, updating the IR is better. |
| 418 | WindowsInstallerData data; |
| 419 | { |
| 420 | var command = new CreateWindowsInstallerDataFromIRCommand(this.Messaging, section, tableDefinitions, codepage, this.BackendExtensions, this.WindowsInstallerBackendHelper); |
| 421 | data = command.Execute(); |
| 422 | } |
| 423 | |
| 424 | IEnumerable<string> suppressedTableNames = null; |
| 425 | if (data.Type == OutputType.Module) |
| 426 | { |
| 427 | // Modularize identifiers. |
| 428 | var modularize = new ModularizeCommand(this.WindowsInstallerBackendHelper, data, modularizationSuffix, section.Symbols.OfType<WixSuppressModularizationSymbol>()); |
| 429 | modularize.Execute(); |
| 430 | |
| 431 | // Ensure all sequence tables in place because mergemod.dll requires them. |
| 432 | var unsuppress = new AddBackSuppressedSequenceTablesCommand(data, tableDefinitions); |
| 433 | suppressedTableNames = unsuppress.Execute(); |
| 434 | } |
| 435 | else if (data.Type == OutputType.Package) // we can create instance transforms since Component Guids and Outputs are created. |
| 436 | { |
| 437 | var command = new CreateInstanceTransformsCommand(section, data, tableDefinitions, this.WindowsInstallerBackendHelper); |
| 438 | command.Execute(); |
| 439 | |
| 440 | foreach (var storage in command.SubStorages) |
| 441 | { |
| 442 | data.SubStorages.Add(storage); |
| 443 | } |
| 444 | } |
| 445 | else if (data.Type == OutputType.Patch) |
| 446 | { |
| 447 | foreach (var storage in this.PatchSubStorages) |
| 448 | { |
| 449 | data.SubStorages.Add(storage); |
| 450 | } |
| 451 | } |
| 452 | |
| 453 | // Stop processing if an error previously occurred. |
| 454 | if (this.Messaging.EncounteredError) |
| 455 | { |
| 456 | return null; |
| 457 | } |
| 458 | |
| 459 | if (section.Type == SectionType.Patch && this.DeltaBinaryPatch) |
| 460 | { |
| 461 | var command = new CreateDeltaPatchesCommand(allFileFacades, this.IntermediateFolder, section.Symbols.OfType<WixPatchSymbol>().FirstOrDefault()); |
| 462 | command.Execute(); |
| 463 | } |
| 464 | |
| 465 | // Create cabinet files. |
| 466 | if (!this.SuppressLayout || OutputType.Module == data.Type) |
| 467 | { |
| 468 | this.Messaging.Write(VerboseMessages.CreatingCabinetFiles()); |
| 469 | |
| 470 | var command = new CreateCabinetsCommand(this.ServiceProvider, this.Messaging, this.WindowsInstallerBackendHelper, this.BackendExtensions, section, this.CabCachePath, calculatedCabbingThreadCount, this.OutputPath, this.IntermediateFolder, this.DefaultCompressionLevel, compressed, modularizationSuffix, filesByCabinetMedia, data, tableDefinitions, this.ResolveMedia); |
| 471 | command.Execute(); |
| 472 | |
| 473 | fileTransfers.AddRange(command.FileTransfers); |
| 474 | trackedFiles.AddRange(command.TrackedFiles); |
| 475 | } |
| 476 | |
| 477 | // stop processing if an error previously occurred |
| 478 | if (this.Messaging.EncounteredError) |
| 479 | { |
| 480 | return null; |
| 481 | } |
| 482 | |
| 483 | // Generate database file. |
| 484 | { |
| 485 | this.Messaging.Write(VerboseMessages.GeneratingDatabase()); |
| 486 | |
| 487 | var trackMsi = this.WindowsInstallerBackendHelper.TrackFile(this.OutputPath, TrackedFileType.BuiltTargetOutput); |
| 488 | trackedFiles.Add(trackMsi); |
| 489 | |
| 490 | var command = new GenerateDatabaseCommand(this.Messaging, this.WindowsInstallerBackendHelper, this.FileSystem, this.FileSystemManager, data, trackMsi.Path, tableDefinitions, this.IntermediateFolder, keepAddedColumns: false, this.SuppressAddingValidationRows, useSubdirectory: false); |
| 491 | command.Execute(); |
| 492 | |
| 493 | trackedFiles.AddRange(command.GeneratedTemporaryFiles); |
| 494 | } |
| 495 | |
| 496 | // Stop processing if an error previously occurred. |
| 497 | if (this.Messaging.EncounteredError) |
| 498 | { |
| 499 | return null; |
| 500 | } |
| 501 | |
| 502 | // Merge modules. |
| 503 | if (containsMergeModules) |
| 504 | { |
| 505 | this.Messaging.Write(VerboseMessages.MergingModules()); |
| 506 | |
| 507 | var command = new MergeModulesCommand(this.Messaging, this.WindowsInstallerBackendHelper, fileFacadesFromModule, section, suppressedTableNames, this.OutputPath, this.IntermediateFolder); |
| 508 | command.Execute(); |
| 509 | |
| 510 | if (command.TrackedFiles != null) |
| 511 | { |
| 512 | trackedFiles.AddRange(command.TrackedFiles); |
| 513 | } |
| 514 | } |
| 515 | |
| 516 | if (this.Messaging.EncounteredError) |
| 517 | { |
| 518 | return null; |
| 519 | } |
| 520 | |
| 521 | // Process uncompressed files. |
| 522 | if (!this.SuppressLayout && uncompressedFiles.Any()) |
| 523 | { |
| 524 | var command = new ProcessUncompressedFilesCommand(section, this.WindowsInstallerBackendHelper, this.PathResolver, uncompressedFiles, this.OutputPath, compressed, longNames, this.ResolveMedia); |
| 525 | command.Execute(); |
| 526 | |
| 527 | fileTransfers.AddRange(command.FileTransfers); |
| 528 | trackedFiles.AddRange(command.TrackedFiles); |
| 529 | } |
| 530 | |
| 531 | // Best effort check to see if the MSI file is too large for the Windows Installer. |
| 532 | try |
| 533 | { |
| 534 | var fi = new FileInfo(this.OutputPath); |
| 535 | if (fi.Length > Int32.MaxValue) |
| 536 | { |
| 537 | this.Messaging.Write(WarningMessages.WindowsInstallerFileTooLarge(null, this.OutputPath, data.Type.ToString())); |
| 538 | } |
| 539 | } |
| 540 | catch |
| 541 | { |
| 542 | } |
| 543 | |
| 544 | var trackedInputFiles = this.TrackInputFiles(data, trackedFiles); |
| 545 | trackedFiles.AddRange(trackedInputFiles); |
| 546 | |
| 547 | var result = this.ServiceProvider.GetService<IBindResult>(); |
| 548 | result.FileTransfers = fileTransfers; |
| 549 | result.TrackedFiles = trackedFiles; |
| 550 | result.Wixout = this.CreateWixout(trackedFiles, this.Intermediate, data); |
| 551 | |
| 552 | return result; |
| 553 | } |
| 554 | |
| 555 | private void ProcessProductVersion(WixPackageSymbol packageSymbol, IntermediateSection section, bool validate) |
| 556 | { |
| 557 | if (this.WindowsInstallerBackendHelper.TryParseMsiProductVersion(packageSymbol.Version, strict: false, out var version)) |
| 558 | { |
| 559 | if (packageSymbol.Version != version) |
| 560 | { |
| 561 | packageSymbol.Version = version; |
| 562 | |
| 563 | var productVersionProperty = section.Symbols.OfType<PropertySymbol>().FirstOrDefault(p => p.Id.Id == "ProductVersion"); |
| 564 | productVersionProperty.Value = version; |
| 565 | } |
| 566 | } |
| 567 | else if (validate) |
| 568 | { |
| 569 | this.Messaging.Write(WarningMessages.InvalidMsiProductVersion(packageSymbol.SourceLineNumbers, packageSymbol.Version)); |
| 570 | } |
| 571 | } |
| 572 | |
| 573 | private int CalculateCodepage(WixPackageSymbol packageSymbol, WixModuleSymbol moduleSymbol, WixPatchSymbol patchSymbol) |
| 574 | { |
| 575 | var codepage = packageSymbol?.Codepage ?? moduleSymbol?.Codepage ?? patchSymbol?.Codepage; |
| 576 | |
| 577 | if (String.IsNullOrEmpty(codepage)) |
| 578 | { |
| 579 | codepage = this.ResolvedCodepage?.ToString() ?? "65001"; |
| 580 | |
| 581 | if (packageSymbol != null) |
| 582 | { |
| 583 | packageSymbol.Codepage = codepage; |
| 584 | } |
| 585 | else if (moduleSymbol != null) |
| 586 | { |
| 587 | moduleSymbol.Codepage = codepage; |
| 588 | } |
| 589 | else if (patchSymbol != null) |
| 590 | { |
| 591 | patchSymbol.Codepage = codepage; |
| 592 | } |
| 593 | } |
| 594 | |
| 595 | return this.WindowsInstallerBackendHelper.GetValidCodePage(codepage); |
| 596 | } |
| 597 | |
| 598 | private T GetSingleSymbol<T>(IntermediateSection section) where T : IntermediateSymbol |
| 599 | { |
| 600 | var symbols = section.Symbols.OfType<T>().ToList(); |
| 601 | |
| 602 | if (1 != symbols.Count) |
| 603 | { |
| 604 | throw new WixException($"Expected to find a single symbol of type {typeof(T).Name} but found {symbols.Count}"); |
| 605 | } |
| 606 | |
| 607 | return symbols[0]; |
| 608 | } |
| 609 | |
| 610 | private WixOutput CreateWixout(List<ITrackedFile> trackedFiles, Intermediate intermediate, WindowsInstallerData data) |
| 611 | { |
| 612 | WixOutput wixout; |
| 613 | |
| 614 | if (String.IsNullOrEmpty(this.OutputPdbPath)) |
| 615 | { |
| 616 | wixout = WixOutput.Create(); |
| 617 | } |
| 618 | else |
| 619 | { |
| 620 | var trackPdb = this.WindowsInstallerBackendHelper.TrackFile(this.OutputPdbPath, TrackedFileType.BuiltPdbOutput); |
| 621 | trackedFiles.Add(trackPdb); |
| 622 | |
| 623 | wixout = WixOutput.Create(trackPdb.Path); |
| 624 | } |
| 625 | |
| 626 | intermediate.Save(wixout); |
| 627 | |
| 628 | data.Save(wixout); |
| 629 | |
| 630 | wixout.Reopen(); |
| 631 | |
| 632 | return wixout; |
| 633 | } |
| 634 | |
| 635 | private string ResolveMedia(MediaSymbol media, string mediaLayoutDirectory, string layoutDirectory) |
| 636 | { |
| 637 | string layout = null; |
| 638 | |
| 639 | foreach (var extension in this.BackendExtensions) |
| 640 | { |
| 641 | layout = extension.ResolveMedia(media, mediaLayoutDirectory, layoutDirectory); |
| 642 | if (!String.IsNullOrEmpty(layout)) |
| 643 | { |
| 644 | break; |
| 645 | } |
| 646 | } |
| 647 | |
| 648 | // If no binder file manager resolved the layout, do the default behavior. |
| 649 | if (String.IsNullOrEmpty(layout)) |
| 650 | { |
| 651 | if (String.IsNullOrEmpty(mediaLayoutDirectory)) |
| 652 | { |
| 653 | layout = layoutDirectory; |
| 654 | } |
| 655 | else if (Path.IsPathRooted(mediaLayoutDirectory)) |
| 656 | { |
| 657 | layout = mediaLayoutDirectory; |
| 658 | } |
| 659 | else |
| 660 | { |
| 661 | layout = Path.Combine(layoutDirectory, mediaLayoutDirectory); |
| 662 | } |
| 663 | } |
| 664 | |
| 665 | return layout; |
| 666 | } |
| 667 | |
| 668 | private IEnumerable<ITrackedFile> TrackInputFiles(WindowsInstallerData data, List<ITrackedFile> trackedFiles) |
| 669 | { |
| 670 | var trackedInputFiles = new List<ITrackedFile>(); |
| 671 | var intermediateAndTemporaryPaths = new HashSet<string>(trackedFiles.Where(t => t.Type == TrackedFileType.Intermediate || t.Type == TrackedFileType.Temporary).Select(t => t.Path), StringComparer.OrdinalIgnoreCase); |
| 672 | |
| 673 | foreach (var row in data.Tables.SelectMany(t => t.Rows)) |
| 674 | { |
| 675 | foreach (var field in row.Fields.Where(f => f.Column.Type == ColumnType.Object)) |
| 676 | { |
| 677 | var path = field.AsString(); |
| 678 | |
| 679 | if (!intermediateAndTemporaryPaths.Contains(path)) |
| 680 | { |
| 681 | trackedInputFiles.Add(this.WindowsInstallerBackendHelper.TrackFile(path, TrackedFileType.Input, row.SourceLineNumbers)); |
| 682 | } |
| 683 | } |
| 684 | } |
| 685 | |
| 686 | return trackedInputFiles; |
| 687 | } |
| 688 | } |
| 689 | } |