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>