aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core.WindowsInstaller/Bind/FinalizeComponentGuids.cs
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2021-04-10 15:04:04 -0700
committerRob Mensching <rob@firegiant.com>2021-04-12 10:24:01 -0700
commit3441afb46c4dc056493ab84f9b27434c4185d713 (patch)
treeec5d6b43292d34e256ebcbf6bf8795b4da46bc80 /src/WixToolset.Core.WindowsInstaller/Bind/FinalizeComponentGuids.cs
parente7b745b08c51b173c80253432a82583e73c46157 (diff)
downloadwix-3441afb46c4dc056493ab84f9b27434c4185d713.tar.gz
wix-3441afb46c4dc056493ab84f9b27434c4185d713.tar.bz2
wix-3441afb46c4dc056493ab84f9b27434c4185d713.zip
Improve implicit Component/@Id generation and duplicate GUID errors
Diffstat (limited to 'src/WixToolset.Core.WindowsInstaller/Bind/FinalizeComponentGuids.cs')
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/FinalizeComponentGuids.cs262
1 files changed, 262 insertions, 0 deletions
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/FinalizeComponentGuids.cs b/src/WixToolset.Core.WindowsInstaller/Bind/FinalizeComponentGuids.cs
new file mode 100644
index 00000000..3cdc0c28
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/FinalizeComponentGuids.cs
@@ -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
3namespace 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}