aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Broholm Andersen <090578@gmail.com>2024-05-21 22:35:14 +0200
committerBob Arnson <github@bobs.org>2024-09-02 23:59:47 -0400
commit4357d18fdc6908eab97886868596af3bd3121f2e (patch)
tree546a90f5c19532bee663a7fea9bd32444feaf96a
parent638532e58ab4c06c35f17421d36ae02ef02ffaf2 (diff)
downloadwix-4357d18fdc6908eab97886868596af3bd3121f2e.tar.gz
wix-4357d18fdc6908eab97886868596af3bd3121f2e.tar.bz2
wix-4357d18fdc6908eab97886868596af3bd3121f2e.zip
Replace UpdateFileFacade loop with Parallel.ForEach
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs9
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs541
2 files changed, 331 insertions, 219 deletions
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
index 4db1bfbe..f1c5f851 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
@@ -6,6 +6,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
6 using System.Collections.Generic; 6 using System.Collections.Generic;
7 using System.IO; 7 using System.IO;
8 using System.Linq; 8 using System.Linq;
9 using System.Threading;
9 using WixToolset.Data; 10 using WixToolset.Data;
10 using WixToolset.Data.Symbols; 11 using WixToolset.Data.Symbols;
11 using WixToolset.Data.WindowsInstaller; 12 using WixToolset.Data.WindowsInstaller;
@@ -50,6 +51,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
50 this.PatchSubStorages = patchSubStorages; 51 this.PatchSubStorages = patchSubStorages;
51 52
52 this.BackendExtensions = backendExtension; 53 this.BackendExtensions = backendExtension;
54
55 this.CancellationToken = context.CancellationToken;
53 } 56 }
54 57
55 private IServiceProvider ServiceProvider { get; } 58 private IServiceProvider ServiceProvider { get; }
@@ -100,6 +103,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
100 103
101 private string IntermediateFolder { get; } 104 private string IntermediateFolder { get; }
102 105
106 private CancellationToken CancellationToken { get; }
107
103 public IBindResult Execute() 108 public IBindResult Execute()
104 { 109 {
105 if (!this.Intermediate.HasLevel(Data.IntermediateLevels.Linked) || !this.Intermediate.HasLevel(Data.IntermediateLevels.Resolved)) 110 if (!this.Intermediate.HasLevel(Data.IntermediateLevels.Linked) || !this.Intermediate.HasLevel(Data.IntermediateLevels.Resolved))
@@ -275,7 +280,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
275 280
276 // Gather information about files that do not come from merge modules. 281 // Gather information about files that do not come from merge modules.
277 { 282 {
278 var command = new UpdateFileFacadesCommand(this.Messaging, this.FileSystem, section, allFileFacades, fileFacadesFromIntermediate, variableCache, overwriteHash: true); 283 var command = new UpdateFileFacadesCommand(this.Messaging, this.FileSystem, section, allFileFacades, fileFacadesFromIntermediate, variableCache, overwriteHash: true, this.CancellationToken);
279 command.Execute(); 284 command.Execute();
280 } 285 }
281 286
@@ -319,7 +324,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
319 { 324 {
320 var updatedFacades = reresolvedFiles.Select(f => allFileFacades.First(ff => ff.Id == f.Id?.Id)); 325 var updatedFacades = reresolvedFiles.Select(f => allFileFacades.First(ff => ff.Id == f.Id?.Id));
321 326
322 var command = new UpdateFileFacadesCommand(this.Messaging, this.FileSystem, section, allFileFacades, updatedFacades, variableCache, overwriteHash: false); 327 var command = new UpdateFileFacadesCommand(this.Messaging, this.FileSystem, section, allFileFacades, updatedFacades, variableCache, overwriteHash: false, this.CancellationToken);
323 command.Execute(); 328 command.Execute();
324 } 329 }
325 } 330 }
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs
index b51e3a0f..5792fdcb 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs
@@ -3,11 +3,14 @@
3namespace WixToolset.Core.WindowsInstaller.Bind 3namespace WixToolset.Core.WindowsInstaller.Bind
4{ 4{
5 using System; 5 using System;
6 using System.Collections.Concurrent;
6 using System.Collections.Generic; 7 using System.Collections.Generic;
7 using System.ComponentModel; 8 using System.ComponentModel;
8 using System.Globalization; 9 using System.Globalization;
9 using System.IO; 10 using System.IO;
10 using System.Linq; 11 using System.Linq;
12 using System.Threading;
13 using System.Threading.Tasks;
11 using WixToolset.Core.Native.Msi; 14 using WixToolset.Core.Native.Msi;
12 using WixToolset.Data; 15 using WixToolset.Data;
13 using WixToolset.Data.Symbols; 16 using WixToolset.Data.Symbols;
@@ -19,7 +22,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
19 /// </summary> 22 /// </summary>
20 internal class UpdateFileFacadesCommand 23 internal class UpdateFileFacadesCommand
21 { 24 {
22 public UpdateFileFacadesCommand(IMessaging messaging, IFileSystem fileSystem, IntermediateSection section, IEnumerable<IFileFacade> allFileFacades, IEnumerable<IFileFacade> updateFileFacades, IDictionary<string, string> variableCache, bool overwriteHash) 25 public UpdateFileFacadesCommand(IMessaging messaging, IFileSystem fileSystem, IntermediateSection section, IEnumerable<IFileFacade> allFileFacades, IEnumerable<IFileFacade> updateFileFacades, IDictionary<string, string> variableCache, bool overwriteHash, CancellationToken cancellationToken)
23 { 26 {
24 this.Messaging = messaging; 27 this.Messaging = messaging;
25 this.FileSystem = fileSystem; 28 this.FileSystem = fileSystem;
@@ -28,6 +31,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
28 this.UpdateFileFacades = updateFileFacades; 31 this.UpdateFileFacades = updateFileFacades;
29 this.VariableCache = variableCache; 32 this.VariableCache = variableCache;
30 this.OverwriteHash = overwriteHash; 33 this.OverwriteHash = overwriteHash;
34 this.CancellationToken = cancellationToken;
31 } 35 }
32 36
33 private IMessaging Messaging { get; } 37 private IMessaging Messaging { get; }
@@ -44,306 +48,409 @@ namespace WixToolset.Core.WindowsInstaller.Bind
44 48
45 private IDictionary<string, string> VariableCache { get; } 49 private IDictionary<string, string> VariableCache { get; }
46 50
47 public void Execute() 51 private CancellationToken CancellationToken { get; }
48 {
49 var assemblySymbols = this.Section.Symbols.OfType<AssemblySymbol>().ToDictionary(t => t.Id.Id);
50 var assemblyNameSymbols = this.Section.Symbols.OfType<MsiAssemblyNameSymbol>().ToDictionary(t => t.Id.Id);
51 52
52 foreach (var file in this.UpdateFileFacades.Where(f => f.SourcePath != null)) 53 public void Execute()
53 {
54 this.UpdateFileFacade(file, assemblySymbols, assemblyNameSymbols);
55 }
56 }
57
58 private void UpdateFileFacade(IFileFacade facade, Dictionary<string, AssemblySymbol> assemblySymbols, Dictionary<string, MsiAssemblyNameSymbol> assemblyNameSymbols)
59 { 54 {
60 FileInfo fileInfo = null;
61 try 55 try
62 { 56 {
63 fileInfo = new FileInfo(facade.SourcePath); 57 this.UpdateFileFacadesInParallel(this.UpdateFileFacades.Where(f => f.SourcePath != null));
64 } 58 }
65 catch (ArgumentException) 59 catch (AggregateException ae)
66 { 60 {
67 this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath)); 61 foreach (var ex in ae.Flatten().InnerExceptions)
68 return;
69 }
70 catch (PathTooLongException)
71 {
72 this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath));
73 return;
74 }
75 catch (NotSupportedException)
76 {
77 this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath));
78 return;
79 }
80
81 if (!fileInfo.Exists)
82 {
83 this.Messaging.Write(ErrorMessages.CannotFindFile(facade.SourceLineNumber, facade.Id, facade.FileName, facade.SourcePath));
84 return;
85 }
86
87 using (var fileStream = this.FileSystem.OpenFile(facade.SourceLineNumber, fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read))
88 {
89 if (Int32.MaxValue < fileStream.Length)
90 { 62 {
91 throw new WixException(ErrorMessages.FileTooLarge(facade.SourceLineNumber, facade.SourcePath)); 63 throw ex;
92 } 64 }
93
94 facade.FileSize = Convert.ToInt32(fileStream.Length, CultureInfo.InvariantCulture);
95 } 65 }
66 }
96 67
97 string version = null; 68 private void UpdateFileFacadesInParallel(IEnumerable<IFileFacade> facades)
98 string language = null; 69 {
99 try 70 var mut = new Mutex();
100 { 71 var exceptions = new ConcurrentQueue<Exception>();
101 Installer.GetFileVersion(fileInfo.FullName, out version, out language);
102 }
103 catch (Win32Exception e)
104 {
105 if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND
106 {
107 throw new WixException(ErrorMessages.FileNotFound(facade.SourceLineNumber, fileInfo.FullName));
108 }
109 else
110 {
111 throw new WixException(ErrorMessages.Win32Exception(e.NativeErrorCode, e.Message));
112 }
113 }
114 72
115 // If there is no version, it is assumed there is no language because it won't matter in the versioning of the install. 73 var assemblySymbols = this.Section.Symbols.OfType<AssemblySymbol>().ToDictionary(t => t.Id.Id);
116 if (String.IsNullOrEmpty(version)) // unversioned files have their hashes added to the MsiFileHash table 74
117 { 75 Parallel.ForEach(facades,
118 if (!this.OverwriteHash) 76 new ParallelOptions{
77 CancellationToken = this.CancellationToken
78 },
79 () =>
119 { 80 {
120 // not overwriting hash, so don't do the rest of these options. 81 return new LocalData(
121 } 82 this.Messaging,
122 else if (null != facade.Version) 83 this.FileSystem,
84 this.AllFileFacades,
85 this.OverwriteHash,
86 this.Section.Symbols.OfType<MsiAssemblyNameSymbol>().ToDictionary(t => t.Id.Id),
87 useVariableCache: null != this.VariableCache
88 );
89 },
90 (file, loopstate, local) =>
123 { 91 {
124 // Search all of the file rows available to see if the specified version is actually a companion file. Yes, this looks 92 try
125 // very expensive and you're probably thinking it would be better to create an index of some sort to do an O(1) look up.
126 // That's a reasonable thought but companion file usage is usually pretty rare so we'd be doing something expensive (indexing
127 // all the file rows) for a relatively uncommon situation. Let's not do that.
128 //
129 // Also, if we do not find a matching file identifier then the user provided a default version and is providing a version
130 // for unversioned file. That's allowed but generally a dangerous thing to do so let's point that out to the user.
131 if (!this.AllFileFacades.Any(r => facade.Version.Equals(r.Id, StringComparison.Ordinal)))
132 { 93 {
133 this.Messaging.Write(WarningMessages.DefaultVersionUsedForUnversionedFile(facade.SourceLineNumber, facade.Version, facade.Id)); 94 local.UpdateFileFacade(file, assemblySymbols);
134 } 95 }
135 } 96 catch (Exception ex)
136 else
137 {
138 if (null != facade.Language)
139 { 97 {
140 this.Messaging.Write(WarningMessages.DefaultLanguageUsedForUnversionedFile(facade.SourceLineNumber, facade.Language, facade.Id)); 98 exceptions.Enqueue(ex);
99 loopstate.Stop();
141 } 100 }
142 101
143 int[] hash; 102 return local;
144 try 103 },
145 { 104 (local) =>
146 Installer.GetFileHash(fileInfo.FullName, 0, out hash); 105 {
147 } 106 // Merge local variable cache back into common variable cache
148 catch (Win32Exception e) 107 if (null != this.VariableCache)
149 { 108 {
150 if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND 109 mut.WaitOne();
110 try
151 { 111 {
152 throw new WixException(ErrorMessages.FileNotFound(facade.SourceLineNumber, fileInfo.FullName)); 112 local.MergeVariableCacheWith(this.VariableCache);
153 } 113 }
154 else 114 finally
155 { 115 {
156 throw new WixException(ErrorMessages.Win32Exception(e.NativeErrorCode, fileInfo.FullName, e.Message)); 116 mut.ReleaseMutex();
157 } 117 }
158 } 118 }
119 }
120 );
121
122 if (!exceptions.IsEmpty)
123 {
124 throw new AggregateException(exceptions);
125 }
126 }
159 127
160 // Remember the hash symbol for use later. 128
161 facade.MsiFileHashSymbol = new MsiFileHashSymbol(facade.SourceLineNumber, facade.Identifier) 129 internal class LocalData
130 {
131 public LocalData(IMessaging messaging, IFileSystem fileSystem, IEnumerable<IFileFacade> allFileFacades, bool overwriteHash, Dictionary<string, MsiAssemblyNameSymbol> assemblyNameSymbols, bool useVariableCache)
132 {
133 this.Messaging = messaging;
134 this.FileSystem = fileSystem;
135 this.AllFileFacades = allFileFacades;
136 this.OverwriteHash = overwriteHash;
137 this.AssemblyNameSymbols = assemblyNameSymbols;
138 this.VariableCache = useVariableCache ? new Dictionary<string, string>() : null;
139 }
140
141 private IMessaging Messaging { get; }
142
143 private IFileSystem FileSystem { get; }
144
145 private IEnumerable<IFileFacade> AllFileFacades { get; }
146
147 private bool OverwriteHash { get; }
148
149 private Dictionary<string, MsiAssemblyNameSymbol> AssemblyNameSymbols { get; }
150
151 private Dictionary<string, string> VariableCache { get; }
152
153 public void MergeVariableCacheWith(IDictionary<string, string> variableCache)
154 {
155 if (null != variableCache && null != this.VariableCache)
156 {
157 foreach (var v in this.VariableCache)
162 { 158 {
163 Options = 0, 159 variableCache[v.Key] = v.Value;
164 HashPart1 = hash[0], 160 }
165 HashPart2 = hash[1],
166 HashPart3 = hash[2],
167 HashPart4 = hash[3],
168 };
169 } 161 }
170 } 162 }
171 else // update the file row with the version and language information. 163
164 public void UpdateFileFacade(IFileFacade facade, Dictionary<string, AssemblySymbol> assemblySymbols)
172 { 165 {
173 // If no version was provided by the user, use the version from the file itself. 166 FileInfo fileInfo = null;
174 // This is the most common case. 167 try
175 if (String.IsNullOrEmpty(facade.Version))
176 { 168 {
177 facade.Version = version; 169 fileInfo = new FileInfo(facade.SourcePath);
178 } 170 }
179 else if (!this.AllFileFacades.Any(r => facade.Version.Equals(r.Id, StringComparison.Ordinal))) // this looks expensive, but see explanation below. 171 catch (ArgumentException)
180 { 172 {
181 // The user provided a default version for the file row so we looked for a companion file (a file row with Id matching 173 this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath));
182 // the version value). We didn't find it so, we will override the default version they provided with the actual 174 return;
183 // version from the file itself. Now, I know it looks expensive to search through all the file rows trying to match
184 // on the Id. However, the alternative is to build a big index of all file rows to do look ups. Since this case
185 // where the file version is already present is rare (companion files are pretty uncommon), we'll do the more
186 // CPU intensive search to save on the memory intensive index that wouldn't be used much.
187 //
188 // Also note this case can occur when the file is being updated using the WixBindUpdatedFiles extension mechanism.
189 // That's typically even more rare than companion files so again, no index, just search.
190 facade.Version = version;
191 } 175 }
192 176 catch (PathTooLongException)
193 if (!String.IsNullOrEmpty(facade.Language) && String.IsNullOrEmpty(language))
194 { 177 {
195 this.Messaging.Write(WarningMessages.DefaultLanguageUsedForVersionedFile(facade.SourceLineNumber, facade.Language, facade.Id)); 178 this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath));
179 return;
196 } 180 }
197 else // override the default provided by the user (usually nothing) with the actual language from the file itself. 181 catch (NotSupportedException)
198 { 182 {
199 facade.Language = language; 183 this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath));
184 return;
200 } 185 }
201 }
202 186
203 // Populate the binder variables for this file information if requested. 187 if (!fileInfo.Exists)
204 if (null != this.VariableCache) 188 {
205 { 189 this.Messaging.Write(ErrorMessages.CannotFindFile(facade.SourceLineNumber, facade.Id, facade.FileName, facade.SourcePath));
206 this.VariableCache[$"fileversion.{facade.Id}"] = facade.Version ?? String.Empty; 190 return;
207 this.VariableCache[$"filelanguage.{facade.Id}"] = facade.Language ?? String.Empty; 191 }
208 }
209 192
210 // If there is an assembly for this file. 193 using (var fileStream = this.FileSystem.OpenFile(facade.SourceLineNumber, fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read))
211 if (assemblySymbols.TryGetValue(facade.Id, out var assemblySymbol))
212 {
213 // If this is a CLR assembly, load the assembly and get the assembly name information
214 if (AssemblyType.DotNetAssembly == assemblySymbol.Type)
215 { 194 {
216 try 195 if (Int32.MaxValue < fileStream.Length)
217 { 196 {
218 var assemblyName = AssemblyNameReader.ReadAssembly(this.FileSystem, facade.SourceLineNumber, fileInfo.FullName, version); 197 throw new WixException(ErrorMessages.FileTooLarge(facade.SourceLineNumber, facade.SourcePath));
198 }
219 199
220 this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "name", assemblyName.Name); 200 facade.FileSize = Convert.ToInt32(fileStream.Length, CultureInfo.InvariantCulture);
221 this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "culture", assemblyName.Culture); 201 }
222 this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "version", assemblyName.Version);
223 202
224 if (!String.IsNullOrEmpty(assemblyName.Architecture)) 203 string version = null;
225 { 204 string language = null;
226 this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "processorArchitecture", assemblyName.Architecture); 205 try
227 } 206 {
228 // TODO: WiX v3 seemed to do this but not clear it should actually be done. 207 Installer.GetFileVersion(fileInfo.FullName, out version, out language);
229 //else if (!String.IsNullOrEmpty(file.WixFile.ProcessorArchitecture)) 208 }
230 //{ 209 catch (Win32Exception e)
231 // this.SetMsiAssemblyName(assemblyNameSymbols, file, "processorArchitecture", file.WixFile.ProcessorArchitecture); 210 {
232 //} 211 if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND
212 {
213 throw new WixException(ErrorMessages.FileNotFound(facade.SourceLineNumber, fileInfo.FullName));
214 }
215 else
216 {
217 throw new WixException(ErrorMessages.Win32Exception(e.NativeErrorCode, e.Message));
218 }
219 }
233 220
234 if (assemblyName.StrongNamedSigned) 221 // If there is no version, it is assumed there is no language because it won't matter in the versioning of the install.
222 if (String.IsNullOrEmpty(version)) // unversioned files have their hashes added to the MsiFileHash table
223 {
224 if (!this.OverwriteHash)
225 {
226 // not overwriting hash, so don't do the rest of these options.
227 }
228 else if (null != facade.Version)
229 {
230 // Search all of the file rows available to see if the specified version is actually a companion file. Yes, this looks
231 // very expensive and you're probably thinking it would be better to create an index of some sort to do an O(1) look up.
232 // That's a reasonable thought but companion file usage is usually pretty rare so we'd be doing something expensive (indexing
233 // all the file rows) for a relatively uncommon situation. Let's not do that.
234 //
235 // Also, if we do not find a matching file identifier then the user provided a default version and is providing a version
236 // for unversioned file. That's allowed but generally a dangerous thing to do so let's point that out to the user.
237 if (!this.AllFileFacades.Any(r => facade.Version.Equals(r.Id, StringComparison.Ordinal)))
235 { 238 {
236 this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "publicKeyToken", assemblyName.PublicKeyToken); 239 this.Messaging.Write(WarningMessages.DefaultVersionUsedForUnversionedFile(facade.SourceLineNumber, facade.Version, facade.Id));
237 } 240 }
238 else if (assemblySymbol.ApplicationFileRef == null) 241 }
242 else
243 {
244 if (null != facade.Language)
239 { 245 {
240 throw new WixException(ErrorMessages.GacAssemblyNoStrongName(facade.SourceLineNumber, fileInfo.FullName, facade.ComponentRef)); 246 this.Messaging.Write(WarningMessages.DefaultLanguageUsedForUnversionedFile(facade.SourceLineNumber, facade.Language, facade.Id));
241 } 247 }
242 248
243 if (!String.IsNullOrEmpty(assemblyName.FileVersion)) 249 int[] hash;
250 try
244 { 251 {
245 this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "fileVersion", assemblyName.FileVersion); 252 Installer.GetFileHash(fileInfo.FullName, 0, out hash);
246 } 253 }
247 254 catch (Win32Exception e)
248 // add the assembly name to the information cache
249 if (null != this.VariableCache)
250 { 255 {
251 this.VariableCache[$"assemblyfullname.{facade.Id}"] = assemblyName.GetFullName(); 256 if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND
257 {
258 throw new WixException(ErrorMessages.FileNotFound(facade.SourceLineNumber, fileInfo.FullName));
259 }
260 else
261 {
262 throw new WixException(ErrorMessages.Win32Exception(e.NativeErrorCode, fileInfo.FullName, e.Message));
263 }
252 } 264 }
253 } 265
254 catch (WixException e) 266 // Remember the hash symbol for use later.
255 { 267 facade.MsiFileHashSymbol = new MsiFileHashSymbol(facade.SourceLineNumber, facade.Identifier)
256 this.Messaging.Write(e.Error); 268 {
269 Options = 0,
270 HashPart1 = hash[0],
271 HashPart2 = hash[1],
272 HashPart3 = hash[2],
273 HashPart4 = hash[3],
274 };
257 } 275 }
258 } 276 }
259 else if (AssemblyType.Win32Assembly == assemblySymbol.Type) 277 else // update the file row with the version and language information.
260 { 278 {
261 // TODO: Consider passing in the this.AllFileFacades as an indexed collection instead of searching through 279 // If no version was provided by the user, use the version from the file itself.
262 // all files like this. Even though this is a rare case it looks like we might be able to index the 280 // This is the most common case.
263 // file earlier. 281 if (String.IsNullOrEmpty(facade.Version))
264 var fileManifest = this.AllFileFacades.FirstOrDefault(r => r.Id.Equals(assemblySymbol.ManifestFileRef, StringComparison.Ordinal)); 282 {
265 if (null == fileManifest) 283 facade.Version = version;
284 }
285 else if (!this.AllFileFacades.Any(r => facade.Version.Equals(r.Id, StringComparison.Ordinal))) // this looks expensive, but see explanation below.
266 { 286 {
267 this.Messaging.Write(ErrorMessages.MissingManifestForWin32Assembly(facade.SourceLineNumber, facade.Id, assemblySymbol.ManifestFileRef)); 287 // The user provided a default version for the file row so we looked for a companion file (a file row with Id matching
288 // the version value). We didn't find it so, we will override the default version they provided with the actual
289 // version from the file itself. Now, I know it looks expensive to search through all the file rows trying to match
290 // on the Id. However, the alternative is to build a big index of all file rows to do look ups. Since this case
291 // where the file version is already present is rare (companion files are pretty uncommon), we'll do the more
292 // CPU intensive search to save on the memory intensive index that wouldn't be used much.
293 //
294 // Also note this case can occur when the file is being updated using the WixBindUpdatedFiles extension mechanism.
295 // That's typically even more rare than companion files so again, no index, just search.
296 facade.Version = version;
268 } 297 }
269 298
270 try 299 if (!String.IsNullOrEmpty(facade.Language) && String.IsNullOrEmpty(language))
300 {
301 this.Messaging.Write(WarningMessages.DefaultLanguageUsedForVersionedFile(facade.SourceLineNumber, facade.Language, facade.Id));
302 }
303 else // override the default provided by the user (usually nothing) with the actual language from the file itself.
271 { 304 {
272 var assemblyName = AssemblyNameReader.ReadAssemblyManifest(facade.SourceLineNumber, fileManifest.SourcePath); 305 facade.Language = language;
306 }
307 }
308
309 // Populate the binder variables for this file information if requested.
310 if (null != this.VariableCache)
311 {
312 this.VariableCache[$"fileversion.{facade.Id}"] = facade.Version ?? String.Empty;
313 this.VariableCache[$"filelanguage.{facade.Id}"] = facade.Language ?? String.Empty;
314 }
273 315
274 if (!String.IsNullOrEmpty(assemblyName.Name)) 316 // If there is an assembly for this file.
317 if (assemblySymbols.TryGetValue(facade.Id, out var assemblySymbol))
318 {
319 // If this is a CLR assembly, load the assembly and get the assembly name information
320 if (AssemblyType.DotNetAssembly == assemblySymbol.Type)
321 {
322 try
275 { 323 {
276 this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "name", assemblyName.Name); 324 var assemblyName = AssemblyNameReader.ReadAssembly(this.FileSystem, facade.SourceLineNumber, fileInfo.FullName, version);
325
326 this.SetMsiAssemblyName(facade, assemblySymbol, "name", assemblyName.Name);
327 this.SetMsiAssemblyName(facade, assemblySymbol, "culture", assemblyName.Culture);
328 this.SetMsiAssemblyName(facade, assemblySymbol, "version", assemblyName.Version);
329
330 if (!String.IsNullOrEmpty(assemblyName.Architecture))
331 {
332 this.SetMsiAssemblyName(facade, assemblySymbol, "processorArchitecture", assemblyName.Architecture);
333 }
334 // TODO: WiX v3 seemed to do this but not clear it should actually be done.
335 //else if (!String.IsNullOrEmpty(file.WixFile.ProcessorArchitecture))
336 //{
337 // this.SetMsiAssemblyName(assemblyNameSymbols, file, "processorArchitecture", file.WixFile.ProcessorArchitecture);
338 //}
339
340 if (assemblyName.StrongNamedSigned)
341 {
342 this.SetMsiAssemblyName(facade, assemblySymbol, "publicKeyToken", assemblyName.PublicKeyToken);
343 }
344 else if (assemblySymbol.ApplicationFileRef == null)
345 {
346 throw new WixException(ErrorMessages.GacAssemblyNoStrongName(facade.SourceLineNumber, fileInfo.FullName, facade.ComponentRef));
347 }
348
349 if (!String.IsNullOrEmpty(assemblyName.FileVersion))
350 {
351 this.SetMsiAssemblyName(facade, assemblySymbol, "fileVersion", assemblyName.FileVersion);
352 }
353
354 // add the assembly name to the information cache
355 if (null != this.VariableCache)
356 {
357 this.VariableCache[$"assemblyfullname.{facade.Id}"] = assemblyName.GetFullName();
358 }
277 } 359 }
278 360 catch (WixException e)
279 if (!String.IsNullOrEmpty(assemblyName.Version))
280 { 361 {
281 this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "version", assemblyName.Version); 362 this.Messaging.Write(e.Error);
282 } 363 }
283 364 }
284 if (!String.IsNullOrEmpty(assemblyName.Type)) 365 else if (AssemblyType.Win32Assembly == assemblySymbol.Type)
366 {
367 // TODO: Consider passing in the this.AllFileFacades as an indexed collection instead of searching through
368 // all files like this. Even though this is a rare case it looks like we might be able to index the
369 // file earlier.
370 var fileManifest = this.AllFileFacades.FirstOrDefault(r => r.Id.Equals(assemblySymbol.ManifestFileRef, StringComparison.Ordinal));
371 if (null == fileManifest)
285 { 372 {
286 this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "type", assemblyName.Type); 373 this.Messaging.Write(ErrorMessages.MissingManifestForWin32Assembly(facade.SourceLineNumber, facade.Id, assemblySymbol.ManifestFileRef));
287 } 374 }
288 375
289 if (!String.IsNullOrEmpty(assemblyName.Architecture)) 376 try
290 { 377 {
291 this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "processorArchitecture", assemblyName.Architecture); 378 var assemblyName = AssemblyNameReader.ReadAssemblyManifest(facade.SourceLineNumber, fileManifest.SourcePath);
379
380 if (!String.IsNullOrEmpty(assemblyName.Name))
381 {
382 this.SetMsiAssemblyName(facade, assemblySymbol, "name", assemblyName.Name);
383 }
384
385 if (!String.IsNullOrEmpty(assemblyName.Version))
386 {
387 this.SetMsiAssemblyName(facade, assemblySymbol, "version", assemblyName.Version);
388 }
389
390 if (!String.IsNullOrEmpty(assemblyName.Type))
391 {
392 this.SetMsiAssemblyName(facade, assemblySymbol, "type", assemblyName.Type);
393 }
394
395 if (!String.IsNullOrEmpty(assemblyName.Architecture))
396 {
397 this.SetMsiAssemblyName(facade, assemblySymbol, "processorArchitecture", assemblyName.Architecture);
398 }
399
400 if (!String.IsNullOrEmpty(assemblyName.PublicKeyToken))
401 {
402 this.SetMsiAssemblyName(facade, assemblySymbol, "publicKeyToken", assemblyName.PublicKeyToken);
403 }
292 } 404 }
293 405 catch (WixException e)
294 if (!String.IsNullOrEmpty(assemblyName.PublicKeyToken))
295 { 406 {
296 this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "publicKeyToken", assemblyName.PublicKeyToken); 407 this.Messaging.Write(e.Error);
297 } 408 }
298 } 409 }
299 catch (WixException e)
300 {
301 this.Messaging.Write(e.Error);
302 }
303 } 410 }
304 } 411 }
305 }
306 412
307 private void SetMsiAssemblyName(Dictionary<string, MsiAssemblyNameSymbol> assemblyNameSymbols, IFileFacade facade, AssemblySymbol assemblySymbol, string name, string value) 413 private void SetMsiAssemblyName(IFileFacade facade, AssemblySymbol assemblySymbol, string name, string value)
308 {
309 // check for null value (this can occur when grabbing the file version from an assembly without one)
310 if (String.IsNullOrEmpty(value))
311 { 414 {
312 this.Messaging.Write(WarningMessages.NullMsiAssemblyNameValue(facade.SourceLineNumber, facade.ComponentRef, name)); 415 // check for null value (this can occur when grabbing the file version from an assembly without one)
313 } 416 if (String.IsNullOrEmpty(value))
314 else
315 {
316 // if the assembly will be GAC'd and the name in the file table doesn't match the name in the MsiAssemblyName table, error because the install will fail.
317 if ("name" == name && AssemblyType.DotNetAssembly == assemblySymbol.Type &&
318 String.IsNullOrEmpty(assemblySymbol.ApplicationFileRef) &&
319 !String.Equals(Path.GetFileNameWithoutExtension(facade.FileName), value, StringComparison.OrdinalIgnoreCase))
320 {
321 this.Messaging.Write(ErrorMessages.GACAssemblyIdentityWarning(facade.SourceLineNumber, Path.GetFileNameWithoutExtension(facade.FileName), value));
322 }
323
324 // Override directly authored value, otherwise remember the gathered information on the facade for use later.
325 var lookup = String.Concat(facade.ComponentRef, "/", name);
326 if (assemblyNameSymbols.TryGetValue(lookup, out var assemblyNameSymbol))
327 { 417 {
328 assemblyNameSymbol.Value = value; 418 this.Messaging.Write(WarningMessages.NullMsiAssemblyNameValue(facade.SourceLineNumber, facade.ComponentRef, name));
329 } 419 }
330 else 420 else
331 { 421 {
332 assemblyNameSymbol = new MsiAssemblyNameSymbol(assemblySymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, facade.ComponentRef, name)) 422 // if the assembly will be GAC'd and the name in the file table doesn't match the name in the MsiAssemblyName table, error because the install will fail.
423 if ("name" == name && AssemblyType.DotNetAssembly == assemblySymbol.Type &&
424 String.IsNullOrEmpty(assemblySymbol.ApplicationFileRef) &&
425 !String.Equals(Path.GetFileNameWithoutExtension(facade.FileName), value, StringComparison.OrdinalIgnoreCase))
333 { 426 {
334 ComponentRef = facade.ComponentRef, 427 this.Messaging.Write(ErrorMessages.GACAssemblyIdentityWarning(facade.SourceLineNumber, Path.GetFileNameWithoutExtension(facade.FileName), value));
335 Name = name, 428 }
336 Value = value,
337 };
338 429
339 facade.AssemblyNameSymbols.Add(assemblyNameSymbol); 430 // Override directly authored value, otherwise remember the gathered information on the facade for use later.
340 assemblyNameSymbols.Add(assemblyNameSymbol.Id.Id, assemblyNameSymbol); 431 var lookup = String.Concat(facade.ComponentRef, "/", name);
341 } 432 if (this.AssemblyNameSymbols.TryGetValue(lookup, out var assemblyNameSymbol))
433 {
434 assemblyNameSymbol.Value = value;
435 }
436 else
437 {
438 assemblyNameSymbol = new MsiAssemblyNameSymbol(assemblySymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, facade.ComponentRef, name))
439 {
440 ComponentRef = facade.ComponentRef,
441 Name = name,
442 Value = value,
443 };
342 444
343 if (this.VariableCache != null) 445 facade.AssemblyNameSymbols.Add(assemblyNameSymbol);
344 { 446 this.AssemblyNameSymbols.Add(assemblyNameSymbol.Id.Id, assemblyNameSymbol);
345 var key = String.Format(CultureInfo.InvariantCulture, "assembly{0}.{1}", name, facade.Id).ToLowerInvariant(); 447 }
346 this.VariableCache[key] = value; 448
449 if (this.VariableCache != null)
450 {
451 var key = String.Format(CultureInfo.InvariantCulture, "assembly{0}.{1}", name, facade.Id).ToLowerInvariant();
452 this.VariableCache[key] = value;
453 }
347 } 454 }
348 } 455 }
349 } 456 }