// 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.WindowsInstaller.Bind { using System; using System.Collections.Generic; using System.IO; using System.Linq; using WixToolset.Bind; using WixToolset.Core.Native; using WixToolset.Data; using WixToolset.Data.Tuples; using WixToolset.Extensibility.Services; /// /// Set the guids for components with generatable guids. /// internal class CalculateComponentGuids { public CalculateComponentGuids(IMessaging messaging, IntermediateSection section) { this.Messaging = messaging; this.Section = section; } private IMessaging Messaging { get; } private IntermediateSection Section { get; } public void Execute() { Dictionary registryKeyRows = null; Dictionary targetPathsByDirectoryId = null; Dictionary componentIdGenSeeds = null; Dictionary> filesByComponentId = null; // Find components with generatable guids. foreach (var componentRow in this.Section.Tuples.OfType()) { // Skip components that do not specify generate guid. if (componentRow.ComponentId != "*") { continue; } var odbcDataSourceKeyPath = (componentRow.Attributes & MsiInterop.MsidbComponentAttributesODBCDataSource) != 0; if (String.IsNullOrEmpty(componentRow.KeyPath) || odbcDataSourceKeyPath) { this.Messaging.Write(ErrorMessages.IllegalComponentWithAutoGeneratedGuid(componentRow.SourceLineNumbers)); continue; } var registryKeyPath = (componentRow.Attributes & MsiInterop.MsidbComponentAttributesRegistryKeyPath) != 0; if (registryKeyPath) { if (registryKeyRows is null) { registryKeyRows = this.Section.Tuples.OfType().ToDictionary(t => t.Registry); } if (registryKeyRows.TryGetValue(componentRow.KeyPath, out var foundRow)) { var is64Bit = (componentRow.Attributes & MsiInterop.MsidbComponentAttributes64bit) != 0; var bitness = is64Bit ? "64" : String.Empty; var regkey = String.Concat(bitness, foundRow[1], "\\", foundRow[2], "\\", foundRow[3]); componentRow.ComponentId = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, regkey.ToLowerInvariant()).ToString("B").ToUpperInvariant(); } } else // must be a File KeyPath. { // If the directory table hasn't been loaded into an indexed hash // of directory ids to target names do that now. if (targetPathsByDirectoryId is null) { var directories = this.Section.Tuples.OfType().ToList(); targetPathsByDirectoryId = new Dictionary(directories.Count); // Get the target paths for all directories. foreach (var row in directories) { // If the directory Id already exists, we will skip it here since // checking for duplicate primary keys is done later when importing tables // into database if (targetPathsByDirectoryId.ContainsKey(row.Directory)) { continue; } var targetName = Common.GetName(row.DefaultDir, false, true); targetPathsByDirectoryId.Add(row.Directory, new ResolvedDirectory(row.Directory_Parent, targetName)); } } // If the component id generation seeds have not been indexed // from the WixDirectory table do that now. if (componentIdGenSeeds is null) { // If there are any WixDirectory rows, build up the Component Guid // generation seeds indexed by Directory/@Id. componentIdGenSeeds = this.Section.Tuples.OfType() .Where(t => !String.IsNullOrEmpty(t.ComponentGuidGenerationSeed)) .ToDictionary(t => t.Directory_, t => t.ComponentGuidGenerationSeed); } // if the file rows have not been indexed by File.Component yet // then do that now if (filesByComponentId is null) { var files = this.Section.Tuples.OfType().ToList(); filesByComponentId = new Dictionary>(files.Count); foreach (var file in files) { if (!filesByComponentId.TryGetValue(file.Component_, out var componentFiles)) { componentFiles = new List(); filesByComponentId.Add(file.Component_, componentFiles); } componentFiles.Add(file); } } // validate component meets all the conditions to have a generated guid var currentComponentFiles = filesByComponentId[componentRow.Component]; var numFilesInComponent = currentComponentFiles.Count; string path = null; foreach (var fileRow in currentComponentFiles) { if (fileRow.File == componentRow.KeyPath) { // calculate the key file's canonical target path string directoryPath = Binder.GetDirectoryPath(targetPathsByDirectoryId, componentIdGenSeeds, componentRow.Directory_, true); string fileName = Common.GetName(fileRow.LongFileName, false, true).ToLowerInvariant(); path = Path.Combine(directoryPath, fileName); // find paths that are not canonicalized if (path.StartsWith(@"PersonalFolder\my pictures", StringComparison.Ordinal) || path.StartsWith(@"ProgramFilesFolder\common files", StringComparison.Ordinal) || path.StartsWith(@"ProgramMenuFolder\startup", StringComparison.Ordinal) || path.StartsWith("TARGETDIR", StringComparison.Ordinal) || path.StartsWith(@"StartMenuFolder\programs", StringComparison.Ordinal) || path.StartsWith(@"WindowsFolder\fonts", StringComparison.Ordinal)) { this.Messaging.Write(ErrorMessages.IllegalPathForGeneratedComponentGuid(componentRow.SourceLineNumbers, fileRow.Component_, path)); } // if component has more than one file, the key path must be versioned if (1 < numFilesInComponent && String.IsNullOrEmpty(fileRow.Version)) { this.Messaging.Write(ErrorMessages.IllegalGeneratedGuidComponentUnversionedKeypath(componentRow.SourceLineNumbers)); } } else { // not a key path, so it must be an unversioned file if component has more than one file if (1 < numFilesInComponent && !String.IsNullOrEmpty(fileRow.Version)) { this.Messaging.Write(ErrorMessages.IllegalGeneratedGuidComponentVersionedNonkeypath(componentRow.SourceLineNumbers)); } } } // if the rules were followed, reward with a generated guid if (!this.Messaging.EncounteredError) { componentRow.ComponentId = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, path).ToString("B").ToUpperInvariant(); } } } } } }