aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core/Bind/Databases/UpdateFileFacadesCommand.cs
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2017-10-14 16:12:07 -0700
committerRob Mensching <rob@firegiant.com>2017-10-14 16:12:07 -0700
commitdbde9e7104b907bbbaea17e21247d8cafc8b3a4c (patch)
tree0f5fbbb6fe12c6b2e5e622a0e18ce4c5b4eb2b96 /src/WixToolset.Core/Bind/Databases/UpdateFileFacadesCommand.cs
parentfbf986eb97f68396797a89fc7d40dec07b775440 (diff)
downloadwix-dbde9e7104b907bbbaea17e21247d8cafc8b3a4c.tar.gz
wix-dbde9e7104b907bbbaea17e21247d8cafc8b3a4c.tar.bz2
wix-dbde9e7104b907bbbaea17e21247d8cafc8b3a4c.zip
Massive refactoring to introduce the concept of IBackend
Diffstat (limited to 'src/WixToolset.Core/Bind/Databases/UpdateFileFacadesCommand.cs')
-rw-r--r--src/WixToolset.Core/Bind/Databases/UpdateFileFacadesCommand.cs532
1 files changed, 0 insertions, 532 deletions
diff --git a/src/WixToolset.Core/Bind/Databases/UpdateFileFacadesCommand.cs b/src/WixToolset.Core/Bind/Databases/UpdateFileFacadesCommand.cs
deleted file mode 100644
index 36818afa..00000000
--- a/src/WixToolset.Core/Bind/Databases/UpdateFileFacadesCommand.cs
+++ /dev/null
@@ -1,532 +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
3namespace WixToolset.Bind.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Collections.Specialized;
8 using System.ComponentModel;
9 using System.Globalization;
10 using System.IO;
11 using System.Linq;
12 using System.Xml;
13 using System.Xml.XPath;
14 using WixToolset.Clr.Interop;
15 using WixToolset.Data;
16 using WixToolset.Data.Rows;
17 using WixToolset.Msi;
18
19 /// <summary>
20 /// Update file information.
21 /// </summary>
22 internal class UpdateFileFacadesCommand : ICommand
23 {
24 public IEnumerable<FileFacade> FileFacades { private get; set; }
25
26 public IEnumerable<FileFacade> UpdateFileFacades { private get; set; }
27
28 public string ModularizationGuid { private get; set; }
29
30 public Output Output { private get; set; }
31
32 public bool OverwriteHash { private get; set; }
33
34 public TableDefinitionCollection TableDefinitions { private get; set; }
35
36 public IDictionary<string, string> VariableCache { private get; set; }
37
38 public void Execute()
39 {
40 foreach (FileFacade file in this.UpdateFileFacades)
41 {
42 this.UpdateFileFacade(file);
43 }
44 }
45
46 private void UpdateFileFacade(FileFacade file)
47 {
48 FileInfo fileInfo = null;
49 try
50 {
51 fileInfo = new FileInfo(file.WixFile.Source);
52 }
53 catch (ArgumentException)
54 {
55 Messaging.Instance.OnMessage(WixErrors.InvalidFileName(file.File.SourceLineNumbers, file.WixFile.Source));
56 return;
57 }
58 catch (PathTooLongException)
59 {
60 Messaging.Instance.OnMessage(WixErrors.InvalidFileName(file.File.SourceLineNumbers, file.WixFile.Source));
61 return;
62 }
63 catch (NotSupportedException)
64 {
65 Messaging.Instance.OnMessage(WixErrors.InvalidFileName(file.File.SourceLineNumbers, file.WixFile.Source));
66 return;
67 }
68
69 if (!fileInfo.Exists)
70 {
71 Messaging.Instance.OnMessage(WixErrors.CannotFindFile(file.File.SourceLineNumbers, file.File.File, file.File.FileName, file.WixFile.Source));
72 return;
73 }
74
75 using (FileStream fileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read))
76 {
77 if (Int32.MaxValue < fileStream.Length)
78 {
79 throw new WixException(WixErrors.FileTooLarge(file.File.SourceLineNumbers, file.WixFile.Source));
80 }
81
82 file.File.FileSize = Convert.ToInt32(fileStream.Length, CultureInfo.InvariantCulture);
83 }
84
85 string version = null;
86 string language = null;
87 try
88 {
89 Installer.GetFileVersion(fileInfo.FullName, out version, out language);
90 }
91 catch (Win32Exception e)
92 {
93 if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND
94 {
95 throw new WixException(WixErrors.FileNotFound(file.File.SourceLineNumbers, fileInfo.FullName));
96 }
97 else
98 {
99 throw new WixException(WixErrors.Win32Exception(e.NativeErrorCode, e.Message));
100 }
101 }
102
103 // If there is no version, it is assumed there is no language because it won't matter in the versioning of the install.
104 if (String.IsNullOrEmpty(version)) // unversioned files have their hashes added to the MsiFileHash table
105 {
106 if (!this.OverwriteHash)
107 {
108 // not overwriting hash, so don't do the rest of these options.
109 }
110 else if (null != file.File.Version)
111 {
112 // Search all of the file rows available to see if the specified version is actually a companion file. Yes, this looks
113 // 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.
114 // That's a reasonable thought but companion file usage is usually pretty rare so we'd be doing something expensive (indexing
115 // all the file rows) for a relatively uncommon situation. Let's not do that.
116 //
117 // Also, if we do not find a matching file identifier then the user provided a default version and is providing a version
118 // for unversioned file. That's allowed but generally a dangerous thing to do so let's point that out to the user.
119 if (!this.FileFacades.Any(r => file.File.Version.Equals(r.File.File, StringComparison.Ordinal)))
120 {
121 Messaging.Instance.OnMessage(WixWarnings.DefaultVersionUsedForUnversionedFile(file.File.SourceLineNumbers, file.File.Version, file.File.File));
122 }
123 }
124 else
125 {
126 if (null != file.File.Language)
127 {
128 Messaging.Instance.OnMessage(WixWarnings.DefaultLanguageUsedForUnversionedFile(file.File.SourceLineNumbers, file.File.Language, file.File.File));
129 }
130
131 int[] hash;
132 try
133 {
134 Installer.GetFileHash(fileInfo.FullName, 0, out hash);
135 }
136 catch (Win32Exception e)
137 {
138 if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND
139 {
140 throw new WixException(WixErrors.FileNotFound(file.File.SourceLineNumbers, fileInfo.FullName));
141 }
142 else
143 {
144 throw new WixException(WixErrors.Win32Exception(e.NativeErrorCode, fileInfo.FullName, e.Message));
145 }
146 }
147
148 if (null == file.Hash)
149 {
150 Table msiFileHashTable = this.Output.EnsureTable(this.TableDefinitions["MsiFileHash"]);
151 file.Hash = msiFileHashTable.CreateRow(file.File.SourceLineNumbers);
152 }
153
154 file.Hash[0] = file.File.File;
155 file.Hash[1] = 0;
156 file.Hash[2] = hash[0];
157 file.Hash[3] = hash[1];
158 file.Hash[4] = hash[2];
159 file.Hash[5] = hash[3];
160 }
161 }
162 else // update the file row with the version and language information.
163 {
164 // If no version was provided by the user, use the version from the file itself.
165 // This is the most common case.
166 if (String.IsNullOrEmpty(file.File.Version))
167 {
168 file.File.Version = version;
169 }
170 else if (!this.FileFacades.Any(r => file.File.Version.Equals(r.File.File, StringComparison.Ordinal))) // this looks expensive, but see explanation below.
171 {
172 // The user provided a default version for the file row so we looked for a companion file (a file row with Id matching
173 // the version value). We didn't find it so, we will override the default version they provided with the actual
174 // version from the file itself. Now, I know it looks expensive to search through all the file rows trying to match
175 // on the Id. However, the alternative is to build a big index of all file rows to do look ups. Since this case
176 // where the file version is already present is rare (companion files are pretty uncommon), we'll do the more
177 // CPU intensive search to save on the memory intensive index that wouldn't be used much.
178 //
179 // Also note this case can occur when the file is being updated using the WixBindUpdatedFiles extension mechanism.
180 // That's typically even more rare than companion files so again, no index, just search.
181 file.File.Version = version;
182 }
183
184 if (!String.IsNullOrEmpty(file.File.Language) && String.IsNullOrEmpty(language))
185 {
186 Messaging.Instance.OnMessage(WixWarnings.DefaultLanguageUsedForVersionedFile(file.File.SourceLineNumbers, file.File.Language, file.File.File));
187 }
188 else // override the default provided by the user (usually nothing) with the actual language from the file itself.
189 {
190 file.File.Language = language;
191 }
192
193 // Populate the binder variables for this file information if requested.
194 if (null != this.VariableCache)
195 {
196 if (!String.IsNullOrEmpty(file.File.Version))
197 {
198 string key = String.Format(CultureInfo.InvariantCulture, "fileversion.{0}", BindDatabaseCommand.Demodularize(this.Output.Type, this.ModularizationGuid, file.File.File));
199 this.VariableCache[key] = file.File.Version;
200 }
201
202 if (!String.IsNullOrEmpty(file.File.Language))
203 {
204 string key = String.Format(CultureInfo.InvariantCulture, "filelanguage.{0}", BindDatabaseCommand.Demodularize(this.Output.Type, ModularizationGuid, file.File.File));
205 this.VariableCache[key] = file.File.Language;
206 }
207 }
208 }
209
210 // If this is a CLR assembly, load the assembly and get the assembly name information
211 if (FileAssemblyType.DotNetAssembly == file.WixFile.AssemblyType)
212 {
213 bool targetNetfx1 = false;
214 StringDictionary assemblyNameValues = new StringDictionary();
215
216 ClrInterop.IReferenceIdentity referenceIdentity = null;
217 Guid referenceIdentityGuid = ClrInterop.ReferenceIdentityGuid;
218 uint result = ClrInterop.GetAssemblyIdentityFromFile(fileInfo.FullName, ref referenceIdentityGuid, out referenceIdentity);
219 if (0 == result && null != referenceIdentity)
220 {
221 string imageRuntimeVersion = referenceIdentity.GetAttribute(null, "ImageRuntimeVersion");
222 if (null != imageRuntimeVersion)
223 {
224 targetNetfx1 = imageRuntimeVersion.StartsWith("v1", StringComparison.OrdinalIgnoreCase);
225 }
226
227 string culture = referenceIdentity.GetAttribute(null, "Culture") ?? "neutral";
228 assemblyNameValues.Add("Culture", culture);
229
230 string name = referenceIdentity.GetAttribute(null, "Name");
231 if (null != name)
232 {
233 assemblyNameValues.Add("Name", name);
234 }
235
236 string processorArchitecture = referenceIdentity.GetAttribute(null, "ProcessorArchitecture");
237 if (null != processorArchitecture)
238 {
239 assemblyNameValues.Add("ProcessorArchitecture", processorArchitecture);
240 }
241
242 string publicKeyToken = referenceIdentity.GetAttribute(null, "PublicKeyToken");
243 if (null != publicKeyToken)
244 {
245 bool publicKeyIsNeutral = (String.Equals(publicKeyToken, "neutral", StringComparison.OrdinalIgnoreCase));
246
247 // Managed code expects "null" instead of "neutral", and
248 // this won't be installed to the GAC since it's not signed anyway.
249 assemblyNameValues.Add("publicKeyToken", publicKeyIsNeutral ? "null" : publicKeyToken.ToUpperInvariant());
250 assemblyNameValues.Add("publicKeyTokenPreservedCase", publicKeyIsNeutral ? "null" : publicKeyToken);
251 }
252 else if (file.WixFile.AssemblyApplication == null)
253 {
254 throw new WixException(WixErrors.GacAssemblyNoStrongName(file.File.SourceLineNumbers, fileInfo.FullName, file.File.Component));
255 }
256
257 string assemblyVersion = referenceIdentity.GetAttribute(null, "Version");
258 if (null != version)
259 {
260 assemblyNameValues.Add("Version", assemblyVersion);
261 }
262 }
263 else
264 {
265 Messaging.Instance.OnMessage(WixErrors.InvalidAssemblyFile(file.File.SourceLineNumbers, fileInfo.FullName, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", result)));
266 return;
267 }
268
269 Table assemblyNameTable = this.Output.EnsureTable(this.TableDefinitions["MsiAssemblyName"]);
270 if (assemblyNameValues.ContainsKey("name"))
271 {
272 this.SetMsiAssemblyName(assemblyNameTable, file, "name", assemblyNameValues["name"]);
273 }
274
275 if (!String.IsNullOrEmpty(version))
276 {
277 this.SetMsiAssemblyName(assemblyNameTable, file, "fileVersion", version);
278 }
279
280 if (assemblyNameValues.ContainsKey("version"))
281 {
282 string assemblyVersion = assemblyNameValues["version"];
283
284 if (!targetNetfx1)
285 {
286 // There is a bug in v1 fusion that requires the assembly's "version" attribute
287 // to be equal to or longer than the "fileVersion" in length when its present;
288 // the workaround is to prepend zeroes to the last version number in the assembly
289 // version.
290 if (null != version && version.Length > assemblyVersion.Length)
291 {
292 string padding = new string('0', version.Length - assemblyVersion.Length);
293 string[] assemblyVersionNumbers = assemblyVersion.Split('.');
294
295 if (assemblyVersionNumbers.Length > 0)
296 {
297 assemblyVersionNumbers[assemblyVersionNumbers.Length - 1] = String.Concat(padding, assemblyVersionNumbers[assemblyVersionNumbers.Length - 1]);
298 assemblyVersion = String.Join(".", assemblyVersionNumbers);
299 }
300 }
301 }
302
303 this.SetMsiAssemblyName(assemblyNameTable, file, "version", assemblyVersion);
304 }
305
306 if (assemblyNameValues.ContainsKey("culture"))
307 {
308 this.SetMsiAssemblyName(assemblyNameTable, file, "culture", assemblyNameValues["culture"]);
309 }
310
311 if (assemblyNameValues.ContainsKey("publicKeyToken"))
312 {
313 this.SetMsiAssemblyName(assemblyNameTable, file, "publicKeyToken", assemblyNameValues["publicKeyToken"]);
314 }
315
316 if (!String.IsNullOrEmpty(file.WixFile.ProcessorArchitecture))
317 {
318 this.SetMsiAssemblyName(assemblyNameTable, file, "processorArchitecture", file.WixFile.ProcessorArchitecture);
319 }
320
321 if (assemblyNameValues.ContainsKey("processorArchitecture"))
322 {
323 this.SetMsiAssemblyName(assemblyNameTable, file, "processorArchitecture", assemblyNameValues["processorArchitecture"]);
324 }
325
326 // add the assembly name to the information cache
327 if (null != this.VariableCache)
328 {
329 string fileId = BindDatabaseCommand.Demodularize(this.Output.Type, this.ModularizationGuid, file.File.File);
330 string key = String.Concat("assemblyfullname.", fileId);
331 string assemblyName = String.Concat(assemblyNameValues["name"], ", version=", assemblyNameValues["version"], ", culture=", assemblyNameValues["culture"], ", publicKeyToken=", String.IsNullOrEmpty(assemblyNameValues["publicKeyToken"]) ? "null" : assemblyNameValues["publicKeyToken"]);
332 if (assemblyNameValues.ContainsKey("processorArchitecture"))
333 {
334 assemblyName = String.Concat(assemblyName, ", processorArchitecture=", assemblyNameValues["processorArchitecture"]);
335 }
336
337 this.VariableCache[key] = assemblyName;
338
339 // Add entries with the preserved case publicKeyToken
340 string pcAssemblyNameKey = String.Concat("assemblyfullnamepreservedcase.", fileId);
341 this.VariableCache[pcAssemblyNameKey] = (assemblyNameValues["publicKeyToken"] == assemblyNameValues["publicKeyTokenPreservedCase"]) ? assemblyName : assemblyName.Replace(assemblyNameValues["publicKeyToken"], assemblyNameValues["publicKeyTokenPreservedCase"]);
342
343 string pcPublicKeyTokenKey = String.Concat("assemblypublickeytokenpreservedcase.", fileId);
344 this.VariableCache[pcPublicKeyTokenKey] = assemblyNameValues["publicKeyTokenPreservedCase"];
345 }
346 }
347 else if (FileAssemblyType.Win32Assembly == file.WixFile.AssemblyType)
348 {
349 // TODO: Consider passing in the this.FileFacades as an indexed collection instead of searching through
350 // all files like this. Even though this is a rare case it looks like we might be able to index the
351 // file earlier.
352 FileFacade fileManifest = this.FileFacades.SingleOrDefault(r => r.File.File.Equals(file.WixFile.AssemblyManifest, StringComparison.Ordinal));
353 if (null == fileManifest)
354 {
355 Messaging.Instance.OnMessage(WixErrors.MissingManifestForWin32Assembly(file.File.SourceLineNumbers, file.File.File, file.WixFile.AssemblyManifest));
356 }
357
358 string win32Type = null;
359 string win32Name = null;
360 string win32Version = null;
361 string win32ProcessorArchitecture = null;
362 string win32PublicKeyToken = null;
363
364 // loading the dom is expensive we want more performant APIs than the DOM
365 // Navigator is cheaper than dom. Perhaps there is a cheaper API still.
366 try
367 {
368 XPathDocument doc = new XPathDocument(fileManifest.WixFile.Source);
369 XPathNavigator nav = doc.CreateNavigator();
370 nav.MoveToRoot();
371
372 // this assumes a particular schema for a win32 manifest and does not
373 // provide error checking if the file does not conform to schema.
374 // The fallback case here is that nothing is added to the MsiAssemblyName
375 // table for an out of tolerance Win32 manifest. Perhaps warnings needed.
376 if (nav.MoveToFirstChild())
377 {
378 while (nav.NodeType != XPathNodeType.Element || nav.Name != "assembly")
379 {
380 nav.MoveToNext();
381 }
382
383 if (nav.MoveToFirstChild())
384 {
385 bool hasNextSibling = true;
386 while (nav.NodeType != XPathNodeType.Element || nav.Name != "assemblyIdentity" && hasNextSibling)
387 {
388 hasNextSibling = nav.MoveToNext();
389 }
390 if (!hasNextSibling)
391 {
392 Messaging.Instance.OnMessage(WixErrors.InvalidManifestContent(file.File.SourceLineNumbers, fileManifest.WixFile.Source));
393 return;
394 }
395
396 if (nav.MoveToAttribute("type", String.Empty))
397 {
398 win32Type = nav.Value;
399 nav.MoveToParent();
400 }
401
402 if (nav.MoveToAttribute("name", String.Empty))
403 {
404 win32Name = nav.Value;
405 nav.MoveToParent();
406 }
407
408 if (nav.MoveToAttribute("version", String.Empty))
409 {
410 win32Version = nav.Value;
411 nav.MoveToParent();
412 }
413
414 if (nav.MoveToAttribute("processorArchitecture", String.Empty))
415 {
416 win32ProcessorArchitecture = nav.Value;
417 nav.MoveToParent();
418 }
419
420 if (nav.MoveToAttribute("publicKeyToken", String.Empty))
421 {
422 win32PublicKeyToken = nav.Value;
423 nav.MoveToParent();
424 }
425 }
426 }
427 }
428 catch (FileNotFoundException fe)
429 {
430 Messaging.Instance.OnMessage(WixErrors.FileNotFound(new SourceLineNumber(fileManifest.WixFile.Source), fe.FileName, "AssemblyManifest"));
431 }
432 catch (XmlException xe)
433 {
434 Messaging.Instance.OnMessage(WixErrors.InvalidXml(new SourceLineNumber(fileManifest.WixFile.Source), "manifest", xe.Message));
435 }
436
437 Table assemblyNameTable = this.Output.EnsureTable(this.TableDefinitions["MsiAssemblyName"]);
438 if (!String.IsNullOrEmpty(win32Name))
439 {
440 this.SetMsiAssemblyName(assemblyNameTable, file, "name", win32Name);
441 }
442
443 if (!String.IsNullOrEmpty(win32Version))
444 {
445 this.SetMsiAssemblyName(assemblyNameTable, file, "version", win32Version);
446 }
447
448 if (!String.IsNullOrEmpty(win32Type))
449 {
450 this.SetMsiAssemblyName(assemblyNameTable, file, "type", win32Type);
451 }
452
453 if (!String.IsNullOrEmpty(win32ProcessorArchitecture))
454 {
455 this.SetMsiAssemblyName(assemblyNameTable, file, "processorArchitecture", win32ProcessorArchitecture);
456 }
457
458 if (!String.IsNullOrEmpty(win32PublicKeyToken))
459 {
460 this.SetMsiAssemblyName(assemblyNameTable, file, "publicKeyToken", win32PublicKeyToken);
461 }
462 }
463 }
464
465 /// <summary>
466 /// Set an MsiAssemblyName row. If it was directly authored, override the value, otherwise
467 /// create a new row.
468 /// </summary>
469 /// <param name="assemblyNameTable">MsiAssemblyName table.</param>
470 /// <param name="file">FileFacade containing the assembly read for the MsiAssemblyName row.</param>
471 /// <param name="name">MsiAssemblyName name.</param>
472 /// <param name="value">MsiAssemblyName value.</param>
473 private void SetMsiAssemblyName(Table assemblyNameTable, FileFacade file, string name, string value)
474 {
475 // check for null value (this can occur when grabbing the file version from an assembly without one)
476 if (String.IsNullOrEmpty(value))
477 {
478 Messaging.Instance.OnMessage(WixWarnings.NullMsiAssemblyNameValue(file.File.SourceLineNumbers, file.File.Component, name));
479 }
480 else
481 {
482 Row assemblyNameRow = null;
483
484 // override directly authored value
485 foreach (Row row in assemblyNameTable.Rows)
486 {
487 if ((string)row[0] == file.File.Component && (string)row[1] == name)
488 {
489 assemblyNameRow = row;
490 break;
491 }
492 }
493
494 // 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.
495 if ("name" == name && FileAssemblyType.DotNetAssembly == file.WixFile.AssemblyType &&
496 String.IsNullOrEmpty(file.WixFile.AssemblyApplication) &&
497 !String.Equals(Path.GetFileNameWithoutExtension(file.File.LongFileName), value, StringComparison.OrdinalIgnoreCase))
498 {
499 Messaging.Instance.OnMessage(WixErrors.GACAssemblyIdentityWarning(file.File.SourceLineNumbers, Path.GetFileNameWithoutExtension(file.File.LongFileName), value));
500 }
501
502 if (null == assemblyNameRow)
503 {
504 assemblyNameRow = assemblyNameTable.CreateRow(file.File.SourceLineNumbers);
505 assemblyNameRow[0] = file.File.Component;
506 assemblyNameRow[1] = name;
507 assemblyNameRow[2] = value;
508
509 // put the MsiAssemblyName row in the same section as the related File row
510 assemblyNameRow.SectionId = file.File.SectionId;
511
512 if (null == file.AssemblyNames)
513 {
514 file.AssemblyNames = new List<Row>();
515 }
516
517 file.AssemblyNames.Add(assemblyNameRow);
518 }
519 else
520 {
521 assemblyNameRow[2] = value;
522 }
523
524 if (this.VariableCache != null)
525 {
526 string key = String.Format(CultureInfo.InvariantCulture, "assembly{0}.{1}", name, BindDatabaseCommand.Demodularize(this.Output.Type, this.ModularizationGuid, file.File.File)).ToLowerInvariant();
527 this.VariableCache[key] = (string)assemblyNameRow[2];
528 }
529 }
530 }
531 }
532}