@joebigelow / wix / commits / 3441afb4

Improve implicit Component/@Id generation and duplicate GUID errors

Rob Mensching committed Apr 10, 2021 at 15:04 UTC 3441afb46c4dc056493ab84f9b27434c4185d713
7 files changed +336 -261
src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
+2 -7
@@ -354,14 +354,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
354 }
355 }
356
357 - // Set generated component guids.
357 + // Set generated component guids and validate all guids.
358 {
359 - var command = new CalculateComponentGuids(this.Messaging, this.WindowsInstallerBackendHelper, this.PathResolver, section, platform);
360 - command.Execute();
361 - }
362 -
363 - {
364 - var command = new ValidateComponentGuidsCommand(this.Messaging, section);
359 + var command = new FinalizeComponentGuids(this.Messaging, this.WindowsInstallerBackendHelper, this.PathResolver, section, platform);
360 command.Execute();
361 }
362
src/WixToolset.Core.WindowsInstaller/Bind/CalculateComponentGuids.cs deleted
-181
@@ -1,181 +0,0 @@
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 WixToolset.Data;
10 - using WixToolset.Data.Symbols;
11 - using WixToolset.Extensibility.Data;
12 - using WixToolset.Extensibility.Services;
13 -
14 - /// <summary>
15 - /// Set the guids for components with generatable guids.
16 - /// </summary>
17 - internal class CalculateComponentGuids
18 - {
19 - internal CalculateComponentGuids(IMessaging messaging, IBackendHelper helper, IPathResolver pathResolver, IntermediateSection section, Platform platform)
20 - {
21 - this.Messaging = messaging;
22 - this.BackendHelper = helper;
23 - this.PathResolver = pathResolver;
24 - this.Section = section;
25 - this.Platform = platform;
26 - }
27 -
28 - private IMessaging Messaging { get; }
29 -
30 - private IBackendHelper BackendHelper { get; }
31 -
32 - private IPathResolver PathResolver { get; }
33 -
34 - private IntermediateSection Section { get; }
35 -
36 - private Platform Platform { get; }
37 -
38 - public void Execute()
39 - {
40 - Dictionary<string, RegistrySymbol> registryKeyRows = null;
41 - Dictionary<string, IResolvedDirectory> targetPathsByDirectoryId = null;
42 - Dictionary<string, string> componentIdGenSeeds = null;
43 - Dictionary<string, List<FileSymbol>> filesByComponentId = null;
44 -
45 - // Find components with generatable guids.
46 - foreach (var componentSymbol in this.Section.Symbols.OfType<ComponentSymbol>())
47 - {
48 - // Skip components that do not specify generate guid.
49 - if (componentSymbol.ComponentId != "*")
50 - {
51 - continue;
52 - }
53 -
54 - if (String.IsNullOrEmpty(componentSymbol.KeyPath) || ComponentKeyPathType.OdbcDataSource == componentSymbol.KeyPathType)
55 - {
56 - this.Messaging.Write(ErrorMessages.IllegalComponentWithAutoGeneratedGuid(componentSymbol.SourceLineNumbers));
57 - continue;
58 - }
59 -
60 - if (ComponentKeyPathType.Registry == componentSymbol.KeyPathType)
61 - {
62 - if (registryKeyRows is null)
63 - {
64 - registryKeyRows = this.Section.Symbols.OfType<RegistrySymbol>().ToDictionary(t => t.Id.Id);
65 - }
66 -
67 - if (registryKeyRows.TryGetValue(componentSymbol.KeyPath, out var foundRow))
68 - {
69 - var bitness = componentSymbol.Win64 ? "64" : String.Empty;
70 - var regkey = String.Concat(bitness, foundRow.Root, "\\", foundRow.Key, "\\", foundRow.Name);
71 - componentSymbol.ComponentId = this.BackendHelper.CreateGuid(BindDatabaseCommand.WixComponentGuidNamespace, regkey.ToLowerInvariant());
72 - }
73 - }
74 - else // must be a File KeyPath.
75 - {
76 - // If the directory table hasn't been loaded into an indexed hash
77 - // of directory ids to target names do that now.
78 - if (targetPathsByDirectoryId is null)
79 - {
80 - var directories = this.Section.Symbols.OfType<DirectorySymbol>().ToList();
81 -
82 - targetPathsByDirectoryId = new Dictionary<string, IResolvedDirectory>(directories.Count);
83 -
84 - // Get the target paths for all directories.
85 - foreach (var directory in directories)
86 - {
87 - // If the directory Id already exists, we will skip it here since
88 - // checking for duplicate primary keys is done later when importing tables
89 - // into database
90 - if (targetPathsByDirectoryId.ContainsKey(directory.Id.Id))
91 - {
92 - continue;
93 - }
94 -
95 - var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(directory.ParentDirectoryRef, directory.Name);
96 - targetPathsByDirectoryId.Add(directory.Id.Id, resolvedDirectory);
97 - }
98 - }
99 -
100 - // If the component id generation seeds have not been indexed
101 - // from the Directory symbols do that now.
102 - if (componentIdGenSeeds is null)
103 - {
104 - // If there are any Directory symbols, build up the Component Guid
105 - // generation seeds indexed by Directory/@Id.
106 - componentIdGenSeeds = this.Section.Symbols.OfType<DirectorySymbol>()
107 - .Where(t => !String.IsNullOrEmpty(t.ComponentGuidGenerationSeed))
108 - .ToDictionary(t => t.Id.Id, t => t.ComponentGuidGenerationSeed);
109 - }
110 -
111 - // if the file rows have not been indexed by File.Component yet
112 - // then do that now
113 - if (filesByComponentId is null)
114 - {
115 - var files = this.Section.Symbols.OfType<FileSymbol>().ToList();
116 -
117 - filesByComponentId = new Dictionary<string, List<FileSymbol>>(files.Count);
118 -
119 - foreach (var file in files)
120 - {
121 - if (!filesByComponentId.TryGetValue(file.ComponentRef, out var componentFiles))
122 - {
123 - componentFiles = new List<FileSymbol>();
124 - filesByComponentId.Add(file.ComponentRef, componentFiles);
125 - }
126 -
127 - componentFiles.Add(file);
128 - }
129 - }
130 -
131 - // validate component meets all the conditions to have a generated guid
132 - var currentComponentFiles = filesByComponentId[componentSymbol.Id.Id];
133 - var numFilesInComponent = currentComponentFiles.Count;
134 - string path = null;
135 -
136 - foreach (var fileRow in currentComponentFiles)
137 - {
138 - if (fileRow.Id.Id == componentSymbol.KeyPath)
139 - {
140 - // calculate the key file's canonical target path
141 - var directoryPath = this.PathResolver.GetCanonicalDirectoryPath(targetPathsByDirectoryId, componentIdGenSeeds, componentSymbol.DirectoryRef, this.Platform);
142 - var fileName = this.BackendHelper.GetMsiFileName(fileRow.Name, false, true).ToLowerInvariant();
143 - path = Path.Combine(directoryPath, fileName);
144 -
145 - // find paths that are not canonicalized
146 - if (path.StartsWith(@"PersonalFolder\my pictures", StringComparison.Ordinal) ||
147 - path.StartsWith(@"ProgramFilesFolder\common files", StringComparison.Ordinal) ||
148 - path.StartsWith(@"ProgramMenuFolder\startup", StringComparison.Ordinal) ||
149 - path.StartsWith("TARGETDIR", StringComparison.Ordinal) ||
150 - path.StartsWith(@"StartMenuFolder\programs", StringComparison.Ordinal) ||
151 - path.StartsWith(@"WindowsFolder\fonts", StringComparison.Ordinal))
152 - {
153 - this.Messaging.Write(ErrorMessages.IllegalPathForGeneratedComponentGuid(componentSymbol.SourceLineNumbers, fileRow.ComponentRef, path));
154 - }
155 -
156 - // if component has more than one file, the key path must be versioned
157 - if (1 < numFilesInComponent && String.IsNullOrEmpty(fileRow.Version))
158 - {
159 - this.Messaging.Write(ErrorMessages.IllegalGeneratedGuidComponentUnversionedKeypath(componentSymbol.SourceLineNumbers));
160 - }
161 - }
162 - else
163 - {
164 - // not a key path, so it must be an unversioned file if component has more than one file
165 - if (1 < numFilesInComponent && !String.IsNullOrEmpty(fileRow.Version))
166 - {
167 - this.Messaging.Write(ErrorMessages.IllegalGeneratedGuidComponentVersionedNonkeypath(componentSymbol.SourceLineNumbers));
168 - }
169 - }
170 - }
171 -
172 - // if the rules were followed, reward with a generated guid
173 - if (!this.Messaging.EncounteredError)
174 - {
175 - componentSymbol.ComponentId = this.BackendHelper.CreateGuid(BindDatabaseCommand.WixComponentGuidNamespace, path);
176 - }
177 - }
178 - }
179 - }
180 - }
181 -}
src/WixToolset.Core.WindowsInstaller/Bind/FinalizeComponentGuids.cs new
+262
@@ -0,0 +1,262 @@
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 WixToolset.Data;
10 + using WixToolset.Data.Symbols;
11 + using WixToolset.Extensibility.Data;
12 + using WixToolset.Extensibility.Services;
13 +
14 + /// <summary>
15 + /// Set the guids for components with generatable guids and validate all are appropriately unique.
16 + /// </summary>
17 + internal class FinalizeComponentGuids
18 + {
19 + internal FinalizeComponentGuids(IMessaging messaging, IBackendHelper helper, IPathResolver pathResolver, IntermediateSection section, Platform platform)
20 + {
21 + this.Messaging = messaging;
22 + this.BackendHelper = helper;
23 + this.PathResolver = pathResolver;
24 + this.Section = section;
25 + this.Platform = platform;
26 + }
27 +
28 + private IMessaging Messaging { get; }
29 +
30 + private IBackendHelper BackendHelper { get; }
31 +
32 + private IPathResolver PathResolver { get; }
33 +
34 + private IntermediateSection Section { get; }
35 +
36 + private Platform Platform { get; }
37 +
38 + private Dictionary<string, string> ComponentIdGenSeeds { get; set; }
39 +
40 + private ILookup<string, FileSymbol> FilesByComponentId { get; set; }
41 +
42 + private Dictionary<string, RegistrySymbol> RegistrySymbolsById { get; set; }
43 +
44 + private Dictionary<string, IResolvedDirectory> TargetPathsByDirectoryId { get; set; }
45 +
46 + public void Execute()
47 + {
48 + var componentGuidConditions = new Dictionary<string, List<ComponentSymbol>>(StringComparer.OrdinalIgnoreCase);
49 + var guidCollisions = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
50 +
51 + foreach (var componentSymbol in this.Section.Symbols.OfType<ComponentSymbol>())
52 + {
53 + if (componentSymbol.ComponentId == "*")
54 + {
55 + this.GenerateComponentGuid(componentSymbol);
56 + }
57 +
58 + // Now check for GUID collisions, but we don't care about unmanaged components and
59 + // if there's a * GUID remaining, there's already an error that explained why it
60 + // was not replaced with a real GUID.
61 + if (!String.IsNullOrEmpty(componentSymbol.ComponentId) && componentSymbol.ComponentId != "*")
62 + {
63 + if (!componentGuidConditions.TryGetValue(componentSymbol.ComponentId, out var components))
64 + {
65 + components = new List<ComponentSymbol>();
66 + componentGuidConditions.Add(componentSymbol.ComponentId, components);
67 + }
68 +
69 + components.Add(componentSymbol);
70 + if (components.Count > 1)
71 + {
72 + guidCollisions.Add(componentSymbol.ComponentId);
73 + }
74 + }
75 + }
76 +
77 + if (guidCollisions.Count > 0)
78 + {
79 + this.ReportGuidCollisions(guidCollisions, componentGuidConditions);
80 + }
81 + }
82 +
83 + private void GenerateComponentGuid(ComponentSymbol componentSymbol)
84 + {
85 + if (String.IsNullOrEmpty(componentSymbol.KeyPath) || ComponentKeyPathType.OdbcDataSource == componentSymbol.KeyPathType)
86 + {
87 + this.Messaging.Write(ErrorMessages.IllegalComponentWithAutoGeneratedGuid(componentSymbol.SourceLineNumbers));
88 + return;
89 + }
90 +
91 + if (ComponentKeyPathType.Registry == componentSymbol.KeyPathType)
92 + {
93 + if (this.RegistrySymbolsById is null)
94 + {
95 + this.RegistrySymbolsById = this.Section.Symbols.OfType<RegistrySymbol>().ToDictionary(t => t.Id.Id);
96 + }
97 +
98 + if (this.RegistrySymbolsById.TryGetValue(componentSymbol.KeyPath, out var registrySymbol))
99 + {
100 + var bitness = componentSymbol.Win64 ? "64" : String.Empty;
101 + var regkey = String.Concat(bitness, registrySymbol.Root, "\\", registrySymbol.Key, "\\", registrySymbol.Name);
102 + componentSymbol.ComponentId = this.BackendHelper.CreateGuid(BindDatabaseCommand.WixComponentGuidNamespace, regkey.ToLowerInvariant());
103 + }
104 + }
105 + else // must be a File KeyPath.
106 + {
107 + // If the directory table hasn't been loaded into an indexed hash
108 + // of directory ids to target names do that now.
109 + if (this.TargetPathsByDirectoryId is null)
110 + {
111 + this.TargetPathsByDirectoryId = this.ResolveDirectoryTargetPaths();
112 + }
113 +
114 + // If the component id generation seeds have not been indexed
115 + // from the Directory symbols do that now.
116 + if (this.ComponentIdGenSeeds is null)
117 + {
118 + // If there are any Directory symbols, build up the Component Guid
119 + // generation seeds indexed by Directory/@Id.
120 + this.ComponentIdGenSeeds = this.Section.Symbols.OfType<DirectorySymbol>()
121 + .Where(t => !String.IsNullOrEmpty(t.ComponentGuidGenerationSeed))
122 + .ToDictionary(t => t.Id.Id, t => t.ComponentGuidGenerationSeed);
123 + }
124 +
125 + // If the file symbols have not been indexed by File's ComponentRef yet
126 + // then do that now.
127 + if (this.FilesByComponentId is null)
128 + {
129 + this.FilesByComponentId = this.Section.Symbols.OfType<FileSymbol>().ToLookup(f => f.ComponentRef);
130 + }
131 +
132 + // validate component meets all the conditions to have a generated guid
133 + var currentComponentFiles = this.FilesByComponentId[componentSymbol.Id.Id];
134 + var numFilesInComponent = currentComponentFiles.Count();
135 + string path = null;
136 +
137 + foreach (var fileSymbol in currentComponentFiles)
138 + {
139 + if (fileSymbol.Id.Id == componentSymbol.KeyPath)
140 + {
141 + // calculate the key file's canonical target path
142 + var directoryPath = this.PathResolver.GetCanonicalDirectoryPath(this.TargetPathsByDirectoryId, this.ComponentIdGenSeeds, componentSymbol.DirectoryRef, this.Platform);
143 + var fileName = this.BackendHelper.GetMsiFileName(fileSymbol.Name, false, true).ToLowerInvariant();
144 + path = Path.Combine(directoryPath, fileName);
145 +
146 + // find paths that are not canonicalized
147 + if (path.StartsWith(@"PersonalFolder\my pictures", StringComparison.Ordinal) ||
148 + path.StartsWith(@"ProgramFilesFolder\common files", StringComparison.Ordinal) ||
149 + path.StartsWith(@"ProgramMenuFolder\startup", StringComparison.Ordinal) ||
150 + path.StartsWith("TARGETDIR", StringComparison.Ordinal) ||
151 + path.StartsWith(@"StartMenuFolder\programs", StringComparison.Ordinal) ||
152 + path.StartsWith(@"WindowsFolder\fonts", StringComparison.Ordinal))
153 + {
154 + this.Messaging.Write(ErrorMessages.IllegalPathForGeneratedComponentGuid(componentSymbol.SourceLineNumbers, fileSymbol.ComponentRef, path));
155 + }
156 +
157 + // if component has more than one file, the key path must be versioned
158 + if (1 < numFilesInComponent && String.IsNullOrEmpty(fileSymbol.Version))
159 + {
160 + this.Messaging.Write(ErrorMessages.IllegalGeneratedGuidComponentUnversionedKeypath(componentSymbol.SourceLineNumbers));
161 + }
162 + }
163 + else
164 + {
165 + // not a key path, so it must be an unversioned file if component has more than one file
166 + if (1 < numFilesInComponent && !String.IsNullOrEmpty(fileSymbol.Version))
167 + {
168 + this.Messaging.Write(ErrorMessages.IllegalGeneratedGuidComponentVersionedNonkeypath(componentSymbol.SourceLineNumbers));
169 + }
170 + }
171 + }
172 +
173 + // if the rules were followed, reward with a generated guid
174 + if (!this.Messaging.EncounteredError)
175 + {
176 + componentSymbol.ComponentId = this.BackendHelper.CreateGuid(BindDatabaseCommand.WixComponentGuidNamespace, path);
177 + }
178 + }
179 + }
180 +
181 + private void ReportGuidCollisions(HashSet<string> guidCollisions, Dictionary<string, List<ComponentSymbol>> componentGuidConditions)
182 + {
183 + Dictionary<string, FileSymbol> fileSymbolsById = null;
184 +
185 + foreach (var guid in guidCollisions)
186 + {
187 + var collidingComponents = componentGuidConditions[guid];
188 + var allComponentsHaveConditions = collidingComponents.All(c => !String.IsNullOrEmpty(c.Condition));
189 +
190 + foreach (var componentSymbol in collidingComponents)
191 + {
192 + string path;
193 + string type;
194 +
195 + if (componentSymbol.KeyPathType == ComponentKeyPathType.File)
196 + {
197 + if (fileSymbolsById is null)
198 + {
199 + fileSymbolsById = this.Section.Symbols.OfType<FileSymbol>().ToDictionary(t => t.Id.Id);
200 + }
201 +
202 + path = fileSymbolsById.TryGetValue(componentSymbol.KeyPath, out var fileSymbol) ? fileSymbol.Source.Path : componentSymbol.KeyPath;
203 + type = "source path";
204 + }
205 + else if (componentSymbol.KeyPathType == ComponentKeyPathType.Registry)
206 + {
207 + if (this.RegistrySymbolsById is null)
208 + {
209 + this.RegistrySymbolsById = this.Section.Symbols.OfType<RegistrySymbol>().ToDictionary(t => t.Id.Id);
210 + }
211 +
212 + path = this.RegistrySymbolsById.TryGetValue(componentSymbol.KeyPath, out var registrySymbol) ? String.Concat(registrySymbol.Key, "\\", registrySymbol.Name) : componentSymbol.KeyPath;
213 + type = "registry path";
214 + }
215 + else
216 + {
217 + if (this.TargetPathsByDirectoryId is null)
218 + {
219 + this.TargetPathsByDirectoryId = this.ResolveDirectoryTargetPaths();
220 + }
221 +
222 + path = this.PathResolver.GetCanonicalDirectoryPath(this.TargetPathsByDirectoryId, componentIdGenSeeds: null, componentSymbol.DirectoryRef, this.Platform);
223 + type = "directory";
224 + }
225 +
226 + if (allComponentsHaveConditions)
227 + {
228 + this.Messaging.Write(WarningMessages.DuplicateComponentGuidsMustHaveMutuallyExclusiveConditions(componentSymbol.SourceLineNumbers, componentSymbol.Id.Id, componentSymbol.ComponentId, type, path));
229 + }
230 + else
231 + {
232 + this.Messaging.Write(ErrorMessages.DuplicateComponentGuids(componentSymbol.SourceLineNumbers, componentSymbol.Id.Id, componentSymbol.ComponentId, type, path));
233 + }
234 + }
235 + }
236 + }
237 +
238 + private Dictionary<string, IResolvedDirectory> ResolveDirectoryTargetPaths()
239 + {
240 + var directories = this.Section.Symbols.OfType<DirectorySymbol>().ToList();
241 +
242 + var targetPathsByDirectoryId = new Dictionary<string, IResolvedDirectory>(directories.Count);
243 +
244 + // Get the target paths for all directories.
245 + foreach (var directory in directories)
246 + {
247 + // If the directory Id already exists, we will skip it here since
248 + // checking for duplicate primary keys is done later when importing tables
249 + // into database
250 + if (targetPathsByDirectoryId.ContainsKey(directory.Id.Id))
251 + {
252 + continue;
253 + }
254 +
255 + var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(directory.ParentDirectoryRef, directory.Name);
256 + targetPathsByDirectoryId.Add(directory.Id.Id, resolvedDirectory);
257 + }
258 +
259 + return targetPathsByDirectoryId;
260 + }
261 + }
262 +}
src/WixToolset.Core.WindowsInstaller/Bind/ValidateComponentGuidsCommand.cs deleted
-63
@@ -1,63 +0,0 @@
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.Linq;
8 - using WixToolset.Data;
9 - using WixToolset.Data.Symbols;
10 - using WixToolset.Extensibility.Services;
11 -
12 - /// <summary>
13 - /// Validate that there are no duplicate GUIDs in the output.
14 - /// </summary>
15 - /// <remarks>
16 - /// Duplicate GUIDs without conditions are an error condition; with conditions, it's a
17 - /// warning, as the conditions might be mutually exclusive.
18 - /// </remarks>
19 - internal class ValidateComponentGuidsCommand
20 - {
21 - internal ValidateComponentGuidsCommand(IMessaging messaging, IntermediateSection section)
22 - {
23 - this.Messaging = messaging;
24 - this.Section = section;
25 - }
26 -
27 - private IMessaging Messaging { get; }
28 -
29 - private IntermediateSection Section { get; }
30 -
31 - public void Execute()
32 - {
33 - var componentGuidConditions = new Dictionary<string, bool>();
34 -
35 - foreach (var componentSymbol in this.Section.Symbols.OfType<ComponentSymbol>())
36 - {
37 - // We don't care about unmanaged components and if there's a * GUID remaining,
38 - // there's already an error that prevented it from being replaced with a real GUID.
39 - if (!String.IsNullOrEmpty(componentSymbol.ComponentId) && "*" != componentSymbol.ComponentId)
40 - {
41 - var thisComponentHasCondition = !String.IsNullOrEmpty(componentSymbol.Condition);
42 - var allComponentsHaveConditions = thisComponentHasCondition;
43 -
44 - if (componentGuidConditions.TryGetValue(componentSymbol.ComponentId, out var alreadyCheckedCondition))
45 - {
46 - allComponentsHaveConditions = thisComponentHasCondition && alreadyCheckedCondition;
47 -
48 - if (allComponentsHaveConditions)
49 - {
50 - this.Messaging.Write(WarningMessages.DuplicateComponentGuidsMustHaveMutuallyExclusiveConditions(componentSymbol.SourceLineNumbers, componentSymbol.Id.Id, componentSymbol.ComponentId));
51 - }
52 - else
53 - {
54 - this.Messaging.Write(ErrorMessages.DuplicateComponentGuids(componentSymbol.SourceLineNumbers, componentSymbol.Id.Id, componentSymbol.ComponentId));
55 - }
56 - }
57 -
58 - componentGuidConditions[componentSymbol.ComponentId] = allComponentsHaveConditions;
59 - }
60 - }
61 - }
62 - }
63 -}
src/WixToolset.Core/Compiler.cs
+13 -10
@@ -2454,24 +2454,27 @@ namespace WixToolset.Core
2454 }
2455 }
2456
2457 - // check for conditions that exclude this component from using generated guids
2458 - var isGeneratableGuidOk = "*" == guid;
2459 - if (isGeneratableGuidOk)
2457 + // Check for conditions that exclude this component from using implicit ids and/or generated guids.
2458 + var allowImplicitIds = true;
2459 + if (encounteredODBCDataSource || ComponentKeyPathType.Directory == keyPathType)
2460 {
2461 - if (encounteredODBCDataSource || ComponentKeyPathType.Directory == keyPathType)
2461 + allowImplicitIds = false;
2462 + if (guid == "*")
2463 {
2464 this.Core.Write(ErrorMessages.IllegalComponentWithAutoGeneratedGuid(sourceLineNumbers));
2464 - isGeneratableGuidOk = false;
2465 }
2466 - else if (0 < files && ComponentKeyPathType.Registry == keyPathType)
2466 + }
2467 + else if (0 < files && ComponentKeyPathType.Registry == keyPathType)
2468 + {
2469 + allowImplicitIds = false;
2470 + if (guid == "*")
2471 {
2472 this.Core.Write(ErrorMessages.IllegalComponentWithAutoGeneratedGuid(sourceLineNumbers, true));
2469 - isGeneratableGuidOk = false;
2473 }
2474 }
2475
2473 - // check for implicit KeyPath which can easily be accidentally changed
2474 - if (this.ShowPedanticMessages && !keyFound && !isGeneratableGuidOk)
2476 + // Check for implicit KeyPath which can easily be accidentally changed
2477 + if (this.ShowPedanticMessages && !keyFound && !allowImplicitIds)
2478 {
2479 this.Core.Write(ErrorMessages.ImplicitComponentKeyPath(sourceLineNumbers, id.Id));
2480 }
@@ -2481,7 +2484,7 @@ namespace WixToolset.Core
2484 // generatable guid must be met.
2485 if (componentIdPlaceholder == id.Id)
2486 {
2484 - if (isGeneratableGuidOk || keyFound && !String.IsNullOrEmpty(keyPath))
2487 + if (allowImplicitIds || keyFound && !String.IsNullOrEmpty(keyPath))
2488 {
2489 this.componentIdPlaceholders.Add(componentIdPlaceholder, keyPath);
2490
src/test/WixToolsetTest.CoreIntegration/ComponentFixture.cs new
+45
@@ -0,0 +1,45 @@
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 WixToolsetTest.CoreIntegration
4 +{
5 + using System;
6 + using System.IO;
7 + using System.Linq;
8 + using WixBuildTools.TestSupport;
9 + using WixToolset.Core.TestPackage;
10 + using WixToolset.Data;
11 + using Xunit;
12 +
13 + public class ComponentFixture
14 + {
15 + [Fact]
16 + public void CanDetectDuplicateComponentGuids()
17 + {
18 + var folder = TestData.Get(@"TestData");
19 +
20 + using (var fs = new DisposableFileSystem())
21 + {
22 + var baseFolder = fs.GetFolder();
23 + var intermediateFolder = Path.Combine(baseFolder, "obj");
24 + var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
25 +
26 + var result = WixRunner.Execute(new[]
27 + {
28 + "build",
29 + Path.Combine(folder, "Component", "GuidCollision.wxs"),
30 + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
31 + "-bindpath", Path.Combine(folder, "SingleFile", "data"),
32 + "-intermediateFolder", intermediateFolder,
33 + "-o", msiPath
34 + });
35 +
36 + var errors = result.Messages.Where(m => m.Level == MessageLevel.Error);
37 + Array.Equals(new[]
38 + {
39 + 369,
40 + 369
41 + }, errors.Select(e => e.Id).ToArray());
42 + }
43 + }
44 + }
45 +}
src/test/WixToolsetTest.CoreIntegration/TestData/Component/GuidCollision.wxs new
+14
@@ -0,0 +1,14 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 + <Fragment>
4 + <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
5 + <Component Guid="5917d193-7b5f-4d5d-bc2e-06aa210699cb">
6 + <RegistryValue Root="HKLM" Key="SOFTWARE\WixToolset" Name="Test" Value="test value" />
7 + </Component>
8 +
9 + <Component Guid="5917d193-7b5f-4d5d-bc2e-06aa210699cb">
10 + <File Source="test.txt" />
11 + </Component>
12 + </ComponentGroup>
13 + </Fragment>
14 +</Wix>