Replace UpdateFileFacade loop with Parallel.ForEach
Martin Broholm Andersen committed
May 21, 2024 at 22:35 UTC
4357d18fdc6908eab97886868596af3bd3121f2e
2 files changed
+331
-219
src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
+7
-2
@@ -6,6 +6,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
6
using System.Collections.Generic;
7
using System.IO;
8
using System.Linq;
9
+ using System.Threading;
10
using WixToolset.Data;
11
using WixToolset.Data.Symbols;
12
using WixToolset.Data.WindowsInstaller;
@@ -50,6 +51,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
51
this.PatchSubStorages = patchSubStorages;
52
53
this.BackendExtensions = backendExtension;
54
+
55
+ this.CancellationToken = context.CancellationToken;
56
}
57
58
private IServiceProvider ServiceProvider { get; }
@@ -100,6 +103,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
103
104
private string IntermediateFolder { get; }
105
106
+ private CancellationToken CancellationToken { get; }
107
+
108
public IBindResult Execute()
109
{
110
if (!this.Intermediate.HasLevel(Data.IntermediateLevels.Linked) || !this.Intermediate.HasLevel(Data.IntermediateLevels.Resolved))
@@ -275,7 +280,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
280
281
// Gather information about files that do not come from merge modules.
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);
284
command.Execute();
285
}
286
@@ -319,7 +324,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
324
{
325
var updatedFacades = reresolvedFiles.Select(f => allFileFacades.First(ff => ff.Id == f.Id?.Id));
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);
328
command.Execute();
329
}
330
}
src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs
+324
-217
@@ -3,11 +3,14 @@
3
namespace WixToolset.Core.WindowsInstaller.Bind
4
{
5
using System;
6
+ using System.Collections.Concurrent;
7
using System.Collections.Generic;
8
using System.ComponentModel;
9
using System.Globalization;
10
using System.IO;
11
using System.Linq;
12
+ using System.Threading;
13
+ using System.Threading.Tasks;
14
using WixToolset.Core.Native.Msi;
15
using WixToolset.Data;
16
using WixToolset.Data.Symbols;
@@ -19,7 +22,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
22
/// </summary>
23
internal class UpdateFileFacadesCommand
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)
26
{
27
this.Messaging = messaging;
28
this.FileSystem = fileSystem;
@@ -28,6 +31,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
31
this.UpdateFileFacades = updateFileFacades;
32
this.VariableCache = variableCache;
33
this.OverwriteHash = overwriteHash;
34
+ this.CancellationToken = cancellationToken;
35
}
36
37
private IMessaging Messaging { get; }
@@ -44,306 +48,409 @@ namespace WixToolset.Core.WindowsInstaller.Bind
48
49
private IDictionary<string, string> VariableCache { get; }
50
47
- public void Execute()
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
+ private CancellationToken CancellationToken { get; }
52
52
- foreach (var file in this.UpdateFileFacades.Where(f => f.SourcePath != null))
53
- {
54
- this.UpdateFileFacade(file, assemblySymbols, assemblyNameSymbols);
55
- }
56
- }
57
-
58
- private void UpdateFileFacade(IFileFacade facade, Dictionary<string, AssemblySymbol> assemblySymbols, Dictionary<string, MsiAssemblyNameSymbol> assemblyNameSymbols)
53
+ public void Execute()
54
{
60
- FileInfo fileInfo = null;
55
try
56
{
63
- fileInfo = new FileInfo(facade.SourcePath);
57
+ this.UpdateFileFacadesInParallel(this.UpdateFileFacades.Where(f => f.SourcePath != null));
58
}
65
- catch (ArgumentException)
59
+ catch (AggregateException ae)
60
{
67
- this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath));
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)
61
+ foreach (var ex in ae.Flatten().InnerExceptions)
62
{
91
- throw new WixException(ErrorMessages.FileTooLarge(facade.SourceLineNumber, facade.SourcePath));
63
+ throw ex;
64
}
93
-
94
- facade.FileSize = Convert.ToInt32(fileStream.Length, CultureInfo.InvariantCulture);
65
}
66
+ }
67
97
- string version = null;
98
- string language = null;
99
- try
100
- {
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
- }
68
+ private void UpdateFileFacadesInParallel(IEnumerable<IFileFacade> facades)
69
+ {
70
+ var mut = new Mutex();
71
+ var exceptions = new ConcurrentQueue<Exception>();
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.
116
- if (String.IsNullOrEmpty(version)) // unversioned files have their hashes added to the MsiFileHash table
117
- {
118
- if (!this.OverwriteHash)
73
+ var assemblySymbols = this.Section.Symbols.OfType<AssemblySymbol>().ToDictionary(t => t.Id.Id);
74
+
75
+ Parallel.ForEach(facades,
76
+ new ParallelOptions{
77
+ CancellationToken = this.CancellationToken
78
+ },
79
+ () =>
80
{
120
- // not overwriting hash, so don't do the rest of these options.
121
- }
122
- else if (null != facade.Version)
81
+ return new LocalData(
82
+ this.Messaging,
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) =>
91
{
124
- // Search all of the file rows available to see if the specified version is actually a companion file. Yes, this looks
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)))
92
+ try
93
{
133
- this.Messaging.Write(WarningMessages.DefaultVersionUsedForUnversionedFile(facade.SourceLineNumber, facade.Version, facade.Id));
94
+ local.UpdateFileFacade(file, assemblySymbols);
95
}
135
- }
136
- else
137
- {
138
- if (null != facade.Language)
96
+ catch (Exception ex)
97
{
140
- this.Messaging.Write(WarningMessages.DefaultLanguageUsedForUnversionedFile(facade.SourceLineNumber, facade.Language, facade.Id));
98
+ exceptions.Enqueue(ex);
99
+ loopstate.Stop();
100
}
101
143
- int[] hash;
144
- try
145
- {
146
- Installer.GetFileHash(fileInfo.FullName, 0, out hash);
147
- }
148
- catch (Win32Exception e)
102
+ return local;
103
+ },
104
+ (local) =>
105
+ {
106
+ // Merge local variable cache back into common variable cache
107
+ if (null != this.VariableCache)
108
{
150
- if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND
109
+ mut.WaitOne();
110
+ try
111
{
152
- throw new WixException(ErrorMessages.FileNotFound(facade.SourceLineNumber, fileInfo.FullName));
112
+ local.MergeVariableCacheWith(this.VariableCache);
113
}
154
- else
114
+ finally
115
{
156
- throw new WixException(ErrorMessages.Win32Exception(e.NativeErrorCode, fileInfo.FullName, e.Message));
116
+ mut.ReleaseMutex();
117
}
118
}
119
+ }
120
+ );
121
+
122
+ if (!exceptions.IsEmpty)
123
+ {
124
+ throw new AggregateException(exceptions);
125
+ }
126
+ }
127
160
- // Remember the hash symbol for use later.
161
- facade.MsiFileHashSymbol = new MsiFileHashSymbol(facade.SourceLineNumber, facade.Identifier)
128
+
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)
158
{
163
- Options = 0,
164
- HashPart1 = hash[0],
165
- HashPart2 = hash[1],
166
- HashPart3 = hash[2],
167
- HashPart4 = hash[3],
168
- };
159
+ variableCache[v.Key] = v.Value;
160
+ }
161
}
162
}
171
- else // update the file row with the version and language information.
163
+
164
+ public void UpdateFileFacade(IFileFacade facade, Dictionary<string, AssemblySymbol> assemblySymbols)
165
{
173
- // If no version was provided by the user, use the version from the file itself.
174
- // This is the most common case.
175
- if (String.IsNullOrEmpty(facade.Version))
166
+ FileInfo fileInfo = null;
167
+ try
168
{
177
- facade.Version = version;
169
+ fileInfo = new FileInfo(facade.SourcePath);
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)
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
182
- // the version value). We didn't find it so, we will override the default version they provided with the actual
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;
173
+ this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath));
174
+ return;
175
}
192
-
193
- if (!String.IsNullOrEmpty(facade.Language) && String.IsNullOrEmpty(language))
176
+ catch (PathTooLongException)
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;
180
}
197
- else // override the default provided by the user (usually nothing) with the actual language from the file itself.
181
+ catch (NotSupportedException)
182
{
199
- facade.Language = language;
183
+ this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath));
184
+ return;
185
}
201
- }
186
203
- // Populate the binder variables for this file information if requested.
204
- if (null != this.VariableCache)
205
- {
206
- this.VariableCache[$"fileversion.{facade.Id}"] = facade.Version ?? String.Empty;
207
- this.VariableCache[$"filelanguage.{facade.Id}"] = facade.Language ?? String.Empty;
208
- }
187
+ if (!fileInfo.Exists)
188
+ {
189
+ this.Messaging.Write(ErrorMessages.CannotFindFile(facade.SourceLineNumber, facade.Id, facade.FileName, facade.SourcePath));
190
+ return;
191
+ }
192
210
- // If there is an assembly for this file.
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)
193
+ using (var fileStream = this.FileSystem.OpenFile(facade.SourceLineNumber, fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read))
194
{
216
- try
195
+ if (Int32.MaxValue < fileStream.Length)
196
{
218
- var assemblyName = AssemblyNameReader.ReadAssembly(this.FileSystem, facade.SourceLineNumber, fileInfo.FullName, version);
197
+ throw new WixException(ErrorMessages.FileTooLarge(facade.SourceLineNumber, facade.SourcePath));
198
+ }
199
220
- this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "name", assemblyName.Name);
221
- this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "culture", assemblyName.Culture);
222
- this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "version", assemblyName.Version);
200
+ facade.FileSize = Convert.ToInt32(fileStream.Length, CultureInfo.InvariantCulture);
201
+ }
202
224
- if (!String.IsNullOrEmpty(assemblyName.Architecture))
225
- {
226
- this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "processorArchitecture", assemblyName.Architecture);
227
- }
228
- // TODO: WiX v3 seemed to do this but not clear it should actually be done.
229
- //else if (!String.IsNullOrEmpty(file.WixFile.ProcessorArchitecture))
230
- //{
231
- // this.SetMsiAssemblyName(assemblyNameSymbols, file, "processorArchitecture", file.WixFile.ProcessorArchitecture);
232
- //}
203
+ string version = null;
204
+ string language = null;
205
+ try
206
+ {
207
+ Installer.GetFileVersion(fileInfo.FullName, out version, out language);
208
+ }
209
+ catch (Win32Exception e)
210
+ {
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
+ }
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)))
238
{
236
- this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "publicKeyToken", assemblyName.PublicKeyToken);
239
+ this.Messaging.Write(WarningMessages.DefaultVersionUsedForUnversionedFile(facade.SourceLineNumber, facade.Version, facade.Id));
240
}
238
- else if (assemblySymbol.ApplicationFileRef == null)
241
+ }
242
+ else
243
+ {
244
+ if (null != facade.Language)
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));
247
}
248
243
- if (!String.IsNullOrEmpty(assemblyName.FileVersion))
249
+ int[] hash;
250
+ try
251
{
245
- this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "fileVersion", assemblyName.FileVersion);
252
+ Installer.GetFileHash(fileInfo.FullName, 0, out hash);
253
}
247
-
248
- // add the assembly name to the information cache
249
- if (null != this.VariableCache)
254
+ catch (Win32Exception e)
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
+ }
264
}
253
- }
254
- catch (WixException e)
255
- {
256
- this.Messaging.Write(e.Error);
265
+
266
+ // Remember the hash symbol for use later.
267
+ facade.MsiFileHashSymbol = new MsiFileHashSymbol(facade.SourceLineNumber, facade.Identifier)
268
+ {
269
+ Options = 0,
270
+ HashPart1 = hash[0],
271
+ HashPart2 = hash[1],
272
+ HashPart3 = hash[2],
273
+ HashPart4 = hash[3],
274
+ };
275
}
276
}
259
- else if (AssemblyType.Win32Assembly == assemblySymbol.Type)
277
+ else // update the file row with the version and language information.
278
{
261
- // TODO: Consider passing in the this.AllFileFacades as an indexed collection instead of searching through
262
- // all files like this. Even though this is a rare case it looks like we might be able to index the
263
- // file earlier.
264
- var fileManifest = this.AllFileFacades.FirstOrDefault(r => r.Id.Equals(assemblySymbol.ManifestFileRef, StringComparison.Ordinal));
265
- if (null == fileManifest)
279
+ // If no version was provided by the user, use the version from the file itself.
280
+ // This is the most common case.
281
+ if (String.IsNullOrEmpty(facade.Version))
282
+ {
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.
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;
297
}
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.
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
+ }
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
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
+ }
359
}
278
-
279
- if (!String.IsNullOrEmpty(assemblyName.Version))
360
+ catch (WixException e)
361
{
281
- this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "version", assemblyName.Version);
362
+ this.Messaging.Write(e.Error);
363
}
283
-
284
- if (!String.IsNullOrEmpty(assemblyName.Type))
364
+ }
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)
372
{
286
- this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "type", assemblyName.Type);
373
+ this.Messaging.Write(ErrorMessages.MissingManifestForWin32Assembly(facade.SourceLineNumber, facade.Id, assemblySymbol.ManifestFileRef));
374
}
375
289
- if (!String.IsNullOrEmpty(assemblyName.Architecture))
376
+ try
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
+ }
404
}
293
-
294
- if (!String.IsNullOrEmpty(assemblyName.PublicKeyToken))
405
+ catch (WixException e)
406
{
296
- this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "publicKeyToken", assemblyName.PublicKeyToken);
407
+ this.Messaging.Write(e.Error);
408
}
409
}
299
- catch (WixException e)
300
- {
301
- this.Messaging.Write(e.Error);
302
- }
410
}
411
}
305
- }
412
307
- private void SetMsiAssemblyName(Dictionary<string, MsiAssemblyNameSymbol> assemblyNameSymbols, 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))
413
+ private void SetMsiAssemblyName(IFileFacade facade, AssemblySymbol assemblySymbol, string name, string value)
414
{
312
- this.Messaging.Write(WarningMessages.NullMsiAssemblyNameValue(facade.SourceLineNumber, facade.ComponentRef, name));
313
- }
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))
415
+ // check for null value (this can occur when grabbing the file version from an assembly without one)
416
+ if (String.IsNullOrEmpty(value))
417
{
328
- assemblyNameSymbol.Value = value;
418
+ this.Messaging.Write(WarningMessages.NullMsiAssemblyNameValue(facade.SourceLineNumber, facade.ComponentRef, name));
419
}
420
else
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))
426
{
334
- ComponentRef = facade.ComponentRef,
335
- Name = name,
336
- Value = value,
337
- };
427
+ this.Messaging.Write(ErrorMessages.GACAssemblyIdentityWarning(facade.SourceLineNumber, Path.GetFileNameWithoutExtension(facade.FileName), value));
428
+ }
429
339
- facade.AssemblyNameSymbols.Add(assemblyNameSymbol);
340
- assemblyNameSymbols.Add(assemblyNameSymbol.Id.Id, assemblyNameSymbol);
341
- }
430
+ // Override directly authored value, otherwise remember the gathered information on the facade for use later.
431
+ var lookup = String.Concat(facade.ComponentRef, "/", name);
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
+ };
444
343
- if (this.VariableCache != null)
344
- {
345
- var key = String.Format(CultureInfo.InvariantCulture, "assembly{0}.{1}", name, facade.Id).ToLowerInvariant();
346
- this.VariableCache[key] = value;
445
+ facade.AssemblyNameSymbols.Add(assemblyNameSymbol);
446
+ this.AssemblyNameSymbols.Add(assemblyNameSymbol.Id.Id, assemblyNameSymbol);
447
+ }
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
+ }
454
}
455
}
456
}