From dbde9e7104b907bbbaea17e21247d8cafc8b3a4c Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Sat, 14 Oct 2017 16:12:07 -0700 Subject: Massive refactoring to introduce the concept of IBackend --- src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs | 942 +++++++++++++++++++++ 1 file changed, 942 insertions(+) create mode 100644 src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs (limited to 'src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs') diff --git a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs new file mode 100644 index 00000000..212b1e81 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs @@ -0,0 +1,942 @@ +// 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. + +namespace WixToolset.Core.Burn +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Reflection; + using WixToolset.Bind; + using WixToolset.Core.Bind; + using WixToolset.Core.Burn.Bundles; + using WixToolset.Data; + using WixToolset.Data.Bind; + using WixToolset.Data.Rows; + using WixToolset.Extensibility; + + // TODO: (4.0) Refactor so that these don't need to be copied. + // Copied verbatim from ext\UtilExtension\wixext\UtilCompiler.cs + [Flags] + internal enum WixFileSearchAttributes + { + Default = 0x001, + MinVersionInclusive = 0x002, + MaxVersionInclusive = 0x004, + MinSizeInclusive = 0x008, + MaxSizeInclusive = 0x010, + MinDateInclusive = 0x020, + MaxDateInclusive = 0x040, + WantVersion = 0x080, + WantExists = 0x100, + IsDirectory = 0x200, + } + + [Flags] + internal enum WixRegistrySearchAttributes + { + Raw = 0x01, + Compatible = 0x02, + ExpandEnvironmentVariables = 0x04, + WantValue = 0x08, + WantExists = 0x10, + Win64 = 0x20, + } + + internal enum WixComponentSearchAttributes + { + KeyPath = 0x1, + State = 0x2, + WantDirectory = 0x4, + } + + [Flags] + internal enum WixProductSearchAttributes + { + Version = 0x1, + Language = 0x2, + State = 0x4, + Assignment = 0x8, + UpgradeCode = 0x10, + } + + /// + /// Binds a this.bundle. + /// + internal class BindBundleCommand + { + public BindBundleCommand(IBindContext context) + { + this.TableDefinitions = WindowsInstallerStandard.GetTableDefinitions(); + + this.DelayedFields = context.DelayedFields; + this.ExpectedEmbeddedFiles = context.ExpectedEmbeddedFiles; + + this.BackendExtensions = context.ExtensionManager.Create(); + } + + public CompressionLevel DefaultCompressionLevel { private get; set; } + + public IEnumerable DelayedFields { get; } + + public IEnumerable ExpectedEmbeddedFiles { get; } + + private IEnumerable BackendExtensions { get; } + + public IEnumerable Extensions { private get; set; } + + public Output Output { private get; set; } + + public string OutputPath { private get; set; } + + public string PdbFile { private get; set; } + + public TableDefinitionCollection TableDefinitions { private get; set; } + + public string IntermediateFolder { private get; set; } + + public IBindVariableResolver WixVariableResolver { private get; set; } + + public IEnumerable FileTransfers { get; private set; } + + public IEnumerable ContentFilePaths { get; private set; } + + public void Execute() + { + this.FileTransfers = Enumerable.Empty(); + this.ContentFilePaths = Enumerable.Empty(); + + // First look for data we expect to find... Chain, WixGroups, etc. + + // We shouldn't really get past the linker phase if there are + // no group items... that means that there's no UX, no Chain, + // *and* no Containers! + Table chainPackageTable = this.GetRequiredTable("WixBundlePackage"); + + Table wixGroupTable = this.GetRequiredTable("WixGroup"); + + // Ensure there is one and only one row in the WixBundle table. + // The compiler and linker behavior should have colluded to get + // this behavior. + WixBundleRow bundleRow = (WixBundleRow)this.GetSingleRowTable("WixBundle"); + + bundleRow.PerMachine = true; // default to per-machine but the first-per user package wil flip the bundle per-user. + + // Ensure there is one and only one row in the WixBootstrapperApplication table. + // The compiler and linker behavior should have colluded to get + // this behavior. + Row baRow = this.GetSingleRowTable("WixBootstrapperApplication"); + + // Ensure there is one and only one row in the WixChain table. + // The compiler and linker behavior should have colluded to get + // this behavior. + WixChainRow chainRow = (WixChainRow)this.GetSingleRowTable("WixChain"); + + if (Messaging.Instance.EncounteredError) + { + return; + } + + // If there are any fields to resolve later, create the cache to populate during bind. + IDictionary variableCache = null; + if (this.DelayedFields.Any()) + { + variableCache = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + } + + // TODO: Although the WixSearch tables are defined in the Util extension, + // the Bundle Binder has to know all about them. We hope to revisit all + // of this in the 4.0 timeframe. + IEnumerable orderedSearches = this.OrderSearches(); + + // Extract files that come from cabinet files (this does not extract files from merge modules). + { + var extractEmbeddedFilesCommand = new ExtractEmbeddedFilesCommand(); + extractEmbeddedFilesCommand.FilesWithEmbeddedFiles = ExpectedEmbeddedFiles; + extractEmbeddedFilesCommand.Execute(); + } + + // Get the explicit payloads. + RowDictionary payloads = new RowDictionary(this.Output.Tables["WixBundlePayload"]); + + // Update explicitly authored payloads with their parent package and container (as appropriate) + // to make it easier to gather the payloads later. + foreach (WixGroupRow row in wixGroupTable.RowsAs()) + { + if (ComplexReferenceChildType.Payload == row.ChildType) + { + WixBundlePayloadRow payload = payloads.Get(row.ChildId); + + if (ComplexReferenceParentType.Package == row.ParentType) + { + Debug.Assert(String.IsNullOrEmpty(payload.Package)); + payload.Package = row.ParentId; + } + else if (ComplexReferenceParentType.Container == row.ParentType) + { + Debug.Assert(String.IsNullOrEmpty(payload.Container)); + payload.Container = row.ParentId; + } + else if (ComplexReferenceParentType.Layout == row.ParentType) + { + payload.LayoutOnly = true; + } + } + } + + List fileTransfers = new List(); + string layoutDirectory = Path.GetDirectoryName(this.OutputPath); + + // Process the explicitly authored payloads. + ISet processedPayloads; + { + ProcessPayloadsCommand command = new ProcessPayloadsCommand(); + command.Payloads = payloads.Values; + command.DefaultPackaging = bundleRow.DefaultPackagingType; + command.LayoutDirectory = layoutDirectory; + command.Execute(); + + fileTransfers.AddRange(command.FileTransfers); + + processedPayloads = new HashSet(payloads.Keys); + } + + IDictionary facades; + { + GetPackageFacadesCommand command = new GetPackageFacadesCommand(); + command.PackageTable = chainPackageTable; + command.ExePackageTable = this.Output.Tables["WixBundleExePackage"]; + command.MsiPackageTable = this.Output.Tables["WixBundleMsiPackage"]; + command.MspPackageTable = this.Output.Tables["WixBundleMspPackage"]; + command.MsuPackageTable = this.Output.Tables["WixBundleMsuPackage"]; + command.Execute(); + + facades = command.PackageFacades; + } + + // Process each package facade. Note this is likely to add payloads and other rows to tables so + // note that any indexes created above may be out of date now. + foreach (PackageFacade facade in facades.Values) + { + switch (facade.Package.Type) + { + case WixBundlePackageType.Exe: + { + ProcessExePackageCommand command = new ProcessExePackageCommand(); + command.AuthoredPayloads = payloads; + command.Facade = facade; + command.Execute(); + + // ? variableCache.Add(String.Concat("packageManufacturer.", facade.Package.WixChainItemId), facade.ExePackage.Manufacturer); + } + break; + + case WixBundlePackageType.Msi: + { + var command = new ProcessMsiPackageCommand(); + command.AuthoredPayloads = payloads; + command.Facade = facade; + command.BackendExtensions = this.BackendExtensions; + command.MsiFeatureTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleMsiFeature"]); + command.MsiPropertyTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleMsiProperty"]); + command.PayloadTable = this.Output.Tables["WixBundlePayload"]; + command.RelatedPackageTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleRelatedPackage"]); + command.Execute(); + + if (null != variableCache) + { + variableCache.Add(String.Concat("packageLanguage.", facade.Package.WixChainItemId), facade.MsiPackage.ProductLanguage.ToString()); + + if (null != facade.MsiPackage.Manufacturer) + { + variableCache.Add(String.Concat("packageManufacturer.", facade.Package.WixChainItemId), facade.MsiPackage.Manufacturer); + } + } + + } + break; + + case WixBundlePackageType.Msp: + { + ProcessMspPackageCommand command = new ProcessMspPackageCommand(); + command.AuthoredPayloads = payloads; + command.Facade = facade; + command.WixBundlePatchTargetCodeTable = this.Output.EnsureTable(this.TableDefinitions["WixBundlePatchTargetCode"]); + command.Execute(); + } + break; + + case WixBundlePackageType.Msu: + { + ProcessMsuPackageCommand command = new ProcessMsuPackageCommand(); + command.Facade = facade; + command.Execute(); + } + break; + } + + if (null != variableCache) + { + BindBundleCommand.PopulatePackageVariableCache(facade.Package, variableCache); + } + } + + // Reindex the payloads now that all the payloads (minus the manifest payloads that will be created later) + // are present. + payloads = new RowDictionary(this.Output.Tables["WixBundlePayload"]); + + // Process the payloads that were added by processing the packages. + { + ProcessPayloadsCommand command = new ProcessPayloadsCommand(); + command.Payloads = payloads.Values.Where(r => !processedPayloads.Contains(r.Id)).ToList(); + command.DefaultPackaging = bundleRow.DefaultPackagingType; + command.LayoutDirectory = layoutDirectory; + command.Execute(); + + fileTransfers.AddRange(command.FileTransfers); + + processedPayloads = null; + } + + // Set the package metadata from the payloads now that we have the complete payload information. + ILookup payloadsByPackage = payloads.Values.ToLookup(p => p.Package); + + { + foreach (PackageFacade facade in facades.Values) + { + facade.Package.Size = 0; + + IEnumerable packagePayloads = payloadsByPackage[facade.Package.WixChainItemId]; + + foreach (WixBundlePayloadRow payload in packagePayloads) + { + facade.Package.Size += payload.FileSize; + } + + if (!facade.Package.InstallSize.HasValue) + { + facade.Package.InstallSize = facade.Package.Size; + + } + + WixBundlePayloadRow packagePayload = payloads[facade.Package.PackagePayload]; + + if (String.IsNullOrEmpty(facade.Package.Description)) + { + facade.Package.Description = packagePayload.Description; + } + + if (String.IsNullOrEmpty(facade.Package.DisplayName)) + { + facade.Package.DisplayName = packagePayload.DisplayName; + } + } + } + + + // Give the UX payloads their embedded IDs... + int uxPayloadIndex = 0; + { + foreach (WixBundlePayloadRow payload in payloads.Values.Where(p => Compiler.BurnUXContainerId == p.Container)) + { + // In theory, UX payloads could be embedded in the UX CAB, external to the bundle EXE, or even + // downloaded. The current engine requires the UX to be fully present before any downloading starts, + // so that rules out downloading. Also, the burn engine does not currently copy external UX payloads + // into the temporary UX directory correctly, so we don't allow external either. + if (PackagingType.Embedded != payload.Packaging) + { + Messaging.Instance.OnMessage(WixWarnings.UxPayloadsOnlySupportEmbedding(payload.SourceLineNumbers, payload.FullFileName)); + payload.Packaging = PackagingType.Embedded; + } + + payload.EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, uxPayloadIndex); + ++uxPayloadIndex; + } + + if (0 == uxPayloadIndex) + { + // If we didn't get any UX payloads, it's an error! + throw new WixException(WixErrors.MissingBundleInformation("BootstrapperApplication")); + } + + // Give the embedded payloads without an embedded id yet an embedded id. + int payloadIndex = 0; + foreach (WixBundlePayloadRow payload in payloads.Values) + { + Debug.Assert(PackagingType.Unknown != payload.Packaging); + + if (PackagingType.Embedded == payload.Packaging && String.IsNullOrEmpty(payload.EmbeddedId)) + { + payload.EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnAttachedContainerEmbeddedIdFormat, payloadIndex); + ++payloadIndex; + } + } + } + + // Determine patches to automatically slipstream. + { + AutomaticallySlipstreamPatchesCommand command = new AutomaticallySlipstreamPatchesCommand(); + command.PackageFacades = facades.Values; + command.SlipstreamMspTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleSlipstreamMsp"]); + command.WixBundlePatchTargetCodeTable = this.Output.EnsureTable(this.TableDefinitions["WixBundlePatchTargetCode"]); + command.Execute(); + } + + // If catalog files exist, non-embedded payloads should validate with the catalogs. + IEnumerable catalogs = this.Output.Tables["WixBundleCatalog"].RowsAs(); + + if (catalogs.Any()) + { + VerifyPayloadsWithCatalogCommand command = new VerifyPayloadsWithCatalogCommand(); + command.Catalogs = catalogs; + command.Payloads = payloads.Values; + command.Execute(); + } + + if (Messaging.Instance.EncounteredError) + { + return; + } + + IEnumerable orderedFacades; + IEnumerable boundaries; + { + OrderPackagesAndRollbackBoundariesCommand command = new OrderPackagesAndRollbackBoundariesCommand(); + command.Boundaries = new RowDictionary(this.Output.Tables["WixBundleRollbackBoundary"]); + command.PackageFacades = facades; + command.WixGroupTable = wixGroupTable; + command.Execute(); + + orderedFacades = command.OrderedPackageFacades; + boundaries = command.UsedRollbackBoundaries; + } + + // Resolve any delayed fields before generating the manifest. + if (this.DelayedFields.Any()) + { + var resolveDelayedFieldsCommand = new ResolveDelayedFieldsCommand(); + resolveDelayedFieldsCommand.OutputType = this.Output.Type; + resolveDelayedFieldsCommand.DelayedFields = this.DelayedFields; + resolveDelayedFieldsCommand.ModularizationGuid = null; + resolveDelayedFieldsCommand.VariableCache = variableCache; + resolveDelayedFieldsCommand.Execute(); + } + + // Set the overridable bundle provider key. + this.SetBundleProviderKey(this.Output, bundleRow); + + // Import or generate dependency providers for packages in the manifest. + this.ProcessDependencyProviders(this.Output, facades); + + // Update the bundle per-machine/per-user scope based on the chained packages. + this.ResolveBundleInstallScope(bundleRow, orderedFacades); + + // Generate the core-defined BA manifest tables... + { + CreateBootstrapperApplicationManifestCommand command = new CreateBootstrapperApplicationManifestCommand(); + command.BundleRow = bundleRow; + command.ChainPackages = orderedFacades; + command.LastUXPayloadIndex = uxPayloadIndex; + command.MsiFeatures = this.Output.Tables["WixBundleMsiFeature"].RowsAs(); + command.Output = this.Output; + command.Payloads = payloads; + command.TableDefinitions = this.TableDefinitions; + command.TempFilesLocation = this.IntermediateFolder; + command.Execute(); + + WixBundlePayloadRow baManifestPayload = command.BootstrapperApplicationManifestPayloadRow; + payloads.Add(baManifestPayload); + } + + //foreach (BinderExtension extension in this.Extensions) + //{ + // extension.PostBind(this.Context); + //} + + // Create all the containers except the UX container first so the manifest (that goes in the UX container) + // can contain all size and hash information about the non-UX containers. + RowDictionary containers = new RowDictionary(this.Output.Tables["WixBundleContainer"]); + + ILookup payloadsByContainer = payloads.Values.ToLookup(p => p.Container); + + int attachedContainerIndex = 1; // count starts at one because UX container is "0". + + IEnumerable uxContainerPayloads = Enumerable.Empty(); + + foreach (WixBundleContainerRow container in containers.Values) + { + IEnumerable containerPayloads = payloadsByContainer[container.Id]; + + if (!containerPayloads.Any()) + { + if (container.Id != Compiler.BurnDefaultAttachedContainerId) + { + // TODO: display warning that we're ignoring container that ended up with no paylods in it. + } + } + else if (Compiler.BurnUXContainerId == container.Id) + { + container.WorkingPath = Path.Combine(this.IntermediateFolder, container.Name); + container.AttachedContainerIndex = 0; + + // Gather the list of UX payloads but ensure the BootstrapperApplication Payload is the first + // in the list since that is the Payload that Burn attempts to load. + List uxPayloads = new List(); + + string baPayloadId = baRow.FieldAsString(0); + + foreach (WixBundlePayloadRow uxPayload in containerPayloads) + { + if (uxPayload.Id == baPayloadId) + { + uxPayloads.Insert(0, uxPayload); + } + else + { + uxPayloads.Add(uxPayload); + } + } + + uxContainerPayloads = uxPayloads; + } + else + { + container.WorkingPath = Path.Combine(this.IntermediateFolder, container.Name); + + // Add detached containers to the list of file transfers. + if (ContainerType.Detached == container.Type) + { + FileTransfer transfer; + if (FileTransfer.TryCreate(container.WorkingPath, Path.Combine(layoutDirectory, container.Name), true, "Container", container.SourceLineNumbers, out transfer)) + { + transfer.Built = true; + fileTransfers.Add(transfer); + } + } + else // update the attached container index. + { + Debug.Assert(ContainerType.Attached == container.Type); + + container.AttachedContainerIndex = attachedContainerIndex; + ++attachedContainerIndex; + } + + this.CreateContainer(container, containerPayloads, null); + } + } + + // Create the bundle manifest then UX container. + string manifestPath = Path.Combine(this.IntermediateFolder, "bundle-manifest.xml"); + { + var command = new CreateBurnManifestCommand(); + command.BackendExtensions = this.BackendExtensions; + command.Output = this.Output; + + command.BundleInfo = bundleRow; + command.Chain = chainRow; + command.Containers = containers; + command.Catalogs = catalogs; + command.ExecutableName = Path.GetFileName(this.OutputPath); + command.OrderedPackages = orderedFacades; + command.OutputPath = manifestPath; + command.RollbackBoundaries = boundaries; + command.OrderedSearches = orderedSearches; + command.Payloads = payloads; + command.UXContainerPayloads = uxContainerPayloads; + command.Execute(); + } + + WixBundleContainerRow uxContainer = containers[Compiler.BurnUXContainerId]; + this.CreateContainer(uxContainer, uxContainerPayloads, manifestPath); + + // Copy the burn.exe to a writable location then mark it to be moved to its final build location. Note + // that today, the x64 Burn uses the x86 stub. + string stubPlatform = (Platform.X64 == bundleRow.Platform) ? "x86" : bundleRow.Platform.ToString(); + + string stubFile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), stubPlatform, "burn.exe"); + string bundleTempPath = Path.Combine(this.IntermediateFolder, Path.GetFileName(this.OutputPath)); + + Messaging.Instance.OnMessage(WixVerboses.GeneratingBundle(bundleTempPath, stubFile)); + + string bundleFilename = Path.GetFileName(this.OutputPath); + if ("setup.exe".Equals(bundleFilename, StringComparison.OrdinalIgnoreCase)) + { + Messaging.Instance.OnMessage(WixErrors.InsecureBundleFilename(bundleFilename)); + } + + FileTransfer bundleTransfer; + if (FileTransfer.TryCreate(bundleTempPath, this.OutputPath, true, "Bundle", bundleRow.SourceLineNumbers, out bundleTransfer)) + { + bundleTransfer.Built = true; + fileTransfers.Add(bundleTransfer); + } + + File.Copy(stubFile, bundleTempPath, true); + File.SetAttributes(bundleTempPath, FileAttributes.Normal); + + this.UpdateBurnResources(bundleTempPath, this.OutputPath, bundleRow); + + // Update the .wixburn section to point to at the UX and attached container(s) then attach the containers + // if they should be attached. + using (BurnWriter writer = BurnWriter.Open(bundleTempPath)) + { + FileInfo burnStubFile = new FileInfo(bundleTempPath); + writer.InitializeBundleSectionData(burnStubFile.Length, bundleRow.BundleId); + + // Always attach the UX container first + writer.AppendContainer(uxContainer.WorkingPath, BurnWriter.Container.UX); + + // Now append all other attached containers + foreach (WixBundleContainerRow container in containers.Values) + { + if (ContainerType.Attached == container.Type) + { + // The container was only created if it had payloads. + if (!String.IsNullOrEmpty(container.WorkingPath) && Compiler.BurnUXContainerId != container.Id) + { + writer.AppendContainer(container.WorkingPath, BurnWriter.Container.Attached); + } + } + } + } + + if (null != this.PdbFile) + { + Pdb pdb = new Pdb(); + pdb.Output = Output; + pdb.Save(this.PdbFile); + } + + this.FileTransfers = fileTransfers; + this.ContentFilePaths = payloads.Values.Where(p => p.ContentFile).Select(p => p.FullFileName).ToList(); + } + + private Table GetRequiredTable(string tableName) + { + Table table = this.Output.Tables[tableName]; + if (null == table || 0 == table.Rows.Count) + { + throw new WixException(WixErrors.MissingBundleInformation(tableName)); + } + + return table; + } + + private Row GetSingleRowTable(string tableName) + { + Table table = this.Output.Tables[tableName]; + if (null == table || 1 != table.Rows.Count) + { + throw new WixException(WixErrors.MissingBundleInformation(tableName)); + } + + return table.Rows[0]; + } + + private List OrderSearches() + { + Dictionary allSearches = new Dictionary(); + Table wixFileSearchTable = this.Output.Tables["WixFileSearch"]; + if (null != wixFileSearchTable && 0 < wixFileSearchTable.Rows.Count) + { + foreach (Row row in wixFileSearchTable.Rows) + { + WixFileSearchInfo fileSearchInfo = new WixFileSearchInfo(row); + allSearches.Add(fileSearchInfo.Id, fileSearchInfo); + } + } + + Table wixRegistrySearchTable = this.Output.Tables["WixRegistrySearch"]; + if (null != wixRegistrySearchTable && 0 < wixRegistrySearchTable.Rows.Count) + { + foreach (Row row in wixRegistrySearchTable.Rows) + { + WixRegistrySearchInfo registrySearchInfo = new WixRegistrySearchInfo(row); + allSearches.Add(registrySearchInfo.Id, registrySearchInfo); + } + } + + Table wixComponentSearchTable = this.Output.Tables["WixComponentSearch"]; + if (null != wixComponentSearchTable && 0 < wixComponentSearchTable.Rows.Count) + { + foreach (Row row in wixComponentSearchTable.Rows) + { + WixComponentSearchInfo componentSearchInfo = new WixComponentSearchInfo(row); + allSearches.Add(componentSearchInfo.Id, componentSearchInfo); + } + } + + Table wixProductSearchTable = this.Output.Tables["WixProductSearch"]; + if (null != wixProductSearchTable && 0 < wixProductSearchTable.Rows.Count) + { + foreach (Row row in wixProductSearchTable.Rows) + { + WixProductSearchInfo productSearchInfo = new WixProductSearchInfo(row); + allSearches.Add(productSearchInfo.Id, productSearchInfo); + } + } + + // Merge in the variable/condition info and get the canonical ordering for + // the searches. + List orderedSearches = new List(); + Table wixSearchTable = this.Output.Tables["WixSearch"]; + if (null != wixSearchTable && 0 < wixSearchTable.Rows.Count) + { + orderedSearches.Capacity = wixSearchTable.Rows.Count; + foreach (Row row in wixSearchTable.Rows) + { + WixSearchInfo searchInfo = allSearches[(string)row[0]]; + searchInfo.AddWixSearchRowInfo(row); + orderedSearches.Add(searchInfo); + } + } + + return orderedSearches; + } + + /// + /// Populates the variable cache with specific package properties. + /// + /// The package with properties to cache. + /// The property cache. + private static void PopulatePackageVariableCache(WixBundlePackageRow package, IDictionary variableCache) + { + string id = package.WixChainItemId; + + variableCache.Add(String.Concat("packageDescription.", id), package.Description); + //variableCache.Add(String.Concat("packageLanguage.", id), package.Language); + //variableCache.Add(String.Concat("packageManufacturer.", id), package.Manufacturer); + variableCache.Add(String.Concat("packageName.", id), package.DisplayName); + variableCache.Add(String.Concat("packageVersion.", id), package.Version); + } + + private void CreateContainer(WixBundleContainerRow container, IEnumerable containerPayloads, string manifestFile) + { + CreateContainerCommand command = new CreateContainerCommand(); + command.DefaultCompressionLevel = this.DefaultCompressionLevel; + command.Payloads = containerPayloads; + command.ManifestFile = manifestFile; + command.OutputPath = container.WorkingPath; + command.Execute(); + + container.Hash = command.Hash; + container.Size = command.Size; + } + + private void ResolveBundleInstallScope(WixBundleRow bundleInfo, IEnumerable facades) + { + foreach (PackageFacade facade in facades) + { + if (bundleInfo.PerMachine && YesNoDefaultType.No == facade.Package.PerMachine) + { + Messaging.Instance.OnMessage(WixVerboses.SwitchingToPerUserPackage(facade.Package.SourceLineNumbers, facade.Package.WixChainItemId)); + + bundleInfo.PerMachine = false; + break; + } + } + + foreach (PackageFacade facade in facades) + { + // Update package scope from bundle scope if default. + if (YesNoDefaultType.Default == facade.Package.PerMachine) + { + facade.Package.PerMachine = bundleInfo.PerMachine ? YesNoDefaultType.Yes : YesNoDefaultType.No; + } + + // We will only register packages in the same scope as the bundle. Warn if any packages with providers + // are in a different scope and not permanent (permanents typically don't need a ref-count). + if (!bundleInfo.PerMachine && YesNoDefaultType.Yes == facade.Package.PerMachine && !facade.Package.Permanent && 0 < facade.Provides.Count) + { + Messaging.Instance.OnMessage(WixWarnings.NoPerMachineDependencies(facade.Package.SourceLineNumbers, facade.Package.WixChainItemId)); + } + } + } + + private void UpdateBurnResources(string bundleTempPath, string outputPath, WixBundleRow bundleInfo) + { + WixToolset.Dtf.Resources.ResourceCollection resources = new WixToolset.Dtf.Resources.ResourceCollection(); + WixToolset.Dtf.Resources.VersionResource version = new WixToolset.Dtf.Resources.VersionResource("#1", 1033); + + version.Load(bundleTempPath); + resources.Add(version); + + // Ensure the bundle info provides a full four part version. + Version fourPartVersion = new Version(bundleInfo.Version); + int major = (fourPartVersion.Major < 0) ? 0 : fourPartVersion.Major; + int minor = (fourPartVersion.Minor < 0) ? 0 : fourPartVersion.Minor; + int build = (fourPartVersion.Build < 0) ? 0 : fourPartVersion.Build; + int revision = (fourPartVersion.Revision < 0) ? 0 : fourPartVersion.Revision; + + if (UInt16.MaxValue < major || UInt16.MaxValue < minor || UInt16.MaxValue < build || UInt16.MaxValue < revision) + { + throw new WixException(WixErrors.InvalidModuleOrBundleVersion(bundleInfo.SourceLineNumbers, "Bundle", bundleInfo.Version)); + } + + fourPartVersion = new Version(major, minor, build, revision); + version.FileVersion = fourPartVersion; + version.ProductVersion = fourPartVersion; + + WixToolset.Dtf.Resources.VersionStringTable strings = version[1033]; + strings["LegalCopyright"] = bundleInfo.Copyright; + strings["OriginalFilename"] = Path.GetFileName(outputPath); + strings["FileVersion"] = bundleInfo.Version; // string versions do not have to be four parts. + strings["ProductVersion"] = bundleInfo.Version; // string versions do not have to be four parts. + + if (!String.IsNullOrEmpty(bundleInfo.Name)) + { + strings["ProductName"] = bundleInfo.Name; + strings["FileDescription"] = bundleInfo.Name; + } + + if (!String.IsNullOrEmpty(bundleInfo.Publisher)) + { + strings["CompanyName"] = bundleInfo.Publisher; + } + else + { + strings["CompanyName"] = String.Empty; + } + + if (!String.IsNullOrEmpty(bundleInfo.IconPath)) + { + Dtf.Resources.GroupIconResource iconGroup = new Dtf.Resources.GroupIconResource("#1", 1033); + iconGroup.ReadFromFile(bundleInfo.IconPath); + resources.Add(iconGroup); + + foreach (Dtf.Resources.Resource icon in iconGroup.Icons) + { + resources.Add(icon); + } + } + + if (!String.IsNullOrEmpty(bundleInfo.SplashScreenBitmapPath)) + { + Dtf.Resources.BitmapResource bitmap = new Dtf.Resources.BitmapResource("#1", 1033); + bitmap.ReadFromFile(bundleInfo.SplashScreenBitmapPath); + resources.Add(bitmap); + } + + resources.Save(bundleTempPath); + } + + #region DependencyExtension + /// + /// Imports authored dependency providers for each package in the manifest, + /// and generates dependency providers for certain package types that do not + /// have a provider defined. + /// + /// The object for the bundle. + /// An indexed collection of chained packages. + private void ProcessDependencyProviders(Output bundle, IDictionary facades) + { + // First import any authored dependencies. These may merge with imported provides from MSI packages. + Table wixDependencyProviderTable = bundle.Tables["WixDependencyProvider"]; + if (null != wixDependencyProviderTable && 0 < wixDependencyProviderTable.Rows.Count) + { + // Add package information for each dependency provider authored into the manifest. + foreach (Row wixDependencyProviderRow in wixDependencyProviderTable.Rows) + { + string packageId = (string)wixDependencyProviderRow[1]; + + PackageFacade facade = null; + if (facades.TryGetValue(packageId, out facade)) + { + ProvidesDependency dependency = new ProvidesDependency(wixDependencyProviderRow); + + if (String.IsNullOrEmpty(dependency.Key)) + { + switch (facade.Package.Type) + { + // The WixDependencyExtension allows an empty Key for MSIs and MSPs. + case WixBundlePackageType.Msi: + dependency.Key = facade.MsiPackage.ProductCode; + break; + case WixBundlePackageType.Msp: + dependency.Key = facade.MspPackage.PatchCode; + break; + } + } + + if (String.IsNullOrEmpty(dependency.Version)) + { + dependency.Version = facade.Package.Version; + } + + // If the version is still missing, a version could not be harvested from the package and was not authored. + if (String.IsNullOrEmpty(dependency.Version)) + { + Messaging.Instance.OnMessage(WixErrors.MissingDependencyVersion(facade.Package.WixChainItemId)); + } + + if (String.IsNullOrEmpty(dependency.DisplayName)) + { + dependency.DisplayName = facade.Package.DisplayName; + } + + if (!facade.Provides.Merge(dependency)) + { + Messaging.Instance.OnMessage(WixErrors.DuplicateProviderDependencyKey(dependency.Key, facade.Package.WixChainItemId)); + } + } + } + } + + // Generate providers for MSI packages that still do not have providers. + foreach (PackageFacade facade in facades.Values) + { + string key = null; + + if (WixBundlePackageType.Msi == facade.Package.Type && 0 == facade.Provides.Count) + { + key = facade.MsiPackage.ProductCode; + } + else if (WixBundlePackageType.Msp == facade.Package.Type && 0 == facade.Provides.Count) + { + key = facade.MspPackage.PatchCode; + } + + if (!String.IsNullOrEmpty(key)) + { + ProvidesDependency dependency = new ProvidesDependency(key, facade.Package.Version, facade.Package.DisplayName, 0); + + if (!facade.Provides.Merge(dependency)) + { + Messaging.Instance.OnMessage(WixErrors.DuplicateProviderDependencyKey(dependency.Key, facade.Package.WixChainItemId)); + } + } + } + } + + /// + /// Sets the provider key for the bundle. + /// + /// The object for the bundle. + /// The containing the provider key and other information for the bundle. + private void SetBundleProviderKey(Output bundle, WixBundleRow bundleInfo) + { + // From DependencyCommon.cs in the WixDependencyExtension. + const int ProvidesAttributesBundle = 0x10000; + + Table wixDependencyProviderTable = bundle.Tables["WixDependencyProvider"]; + if (null != wixDependencyProviderTable && 0 < wixDependencyProviderTable.Rows.Count) + { + // Search the WixDependencyProvider table for the single bundle provider key. + foreach (Row wixDependencyProviderRow in wixDependencyProviderTable.Rows) + { + object attributes = wixDependencyProviderRow[5]; + if (null != attributes && 0 != (ProvidesAttributesBundle & (int)attributes)) + { + bundleInfo.ProviderKey = (string)wixDependencyProviderRow[2]; + break; + } + } + } + + // Defaults to the bundle ID as the provider key. + } + #endregion + } +} -- cgit v1.2.3-55-g6feb