private void UpdateFileFacade(FileFacade facade, Dictionary <string, MsiAssemblyNameSymbol> assemblyNameSymbols) { FileInfo fileInfo = null; try { fileInfo = new FileInfo(facade.SourcePath); } catch (ArgumentException) { this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath)); return; } catch (PathTooLongException) { this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath)); return; } catch (NotSupportedException) { this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath)); return; } if (!fileInfo.Exists) { this.Messaging.Write(ErrorMessages.CannotFindFile(facade.SourceLineNumber, facade.Id, facade.FileName, facade.SourcePath)); return; } using (var fileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read)) { if (Int32.MaxValue < fileStream.Length) { throw new WixException(ErrorMessages.FileTooLarge(facade.SourceLineNumber, facade.SourcePath)); } facade.FileSize = Convert.ToInt32(fileStream.Length, CultureInfo.InvariantCulture); } string version = null; string language = null; try { Installer.GetFileVersion(fileInfo.FullName, out version, out language); } catch (Win32Exception e) { if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND { throw new WixException(ErrorMessages.FileNotFound(facade.SourceLineNumber, fileInfo.FullName)); } else { throw new WixException(ErrorMessages.Win32Exception(e.NativeErrorCode, e.Message)); } } // If there is no version, it is assumed there is no language because it won't matter in the versioning of the install. if (String.IsNullOrEmpty(version)) // unversioned files have their hashes added to the MsiFileHash table { if (!this.OverwriteHash) { // not overwriting hash, so don't do the rest of these options. } else if (null != facade.Version) { // Search all of the file rows available to see if the specified version is actually a companion file. Yes, this looks // 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. // That's a reasonable thought but companion file usage is usually pretty rare so we'd be doing something expensive (indexing // all the file rows) for a relatively uncommon situation. Let's not do that. // // Also, if we do not find a matching file identifier then the user provided a default version and is providing a version // for unversioned file. That's allowed but generally a dangerous thing to do so let's point that out to the user. if (!this.FileFacades.Any(r => facade.Version.Equals(r.Id, StringComparison.Ordinal))) { this.Messaging.Write(WarningMessages.DefaultVersionUsedForUnversionedFile(facade.SourceLineNumber, facade.Version, facade.Id)); } } else { if (null != facade.Language) { this.Messaging.Write(WarningMessages.DefaultLanguageUsedForUnversionedFile(facade.SourceLineNumber, facade.Language, facade.Id)); } int[] hash; try { Installer.GetFileHash(fileInfo.FullName, 0, out hash); } catch (Win32Exception e) { if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND { throw new WixException(ErrorMessages.FileNotFound(facade.SourceLineNumber, fileInfo.FullName)); } else { throw new WixException(ErrorMessages.Win32Exception(e.NativeErrorCode, fileInfo.FullName, e.Message)); } } if (null == facade.Hash) { facade.Hash = this.Section.AddSymbol(new MsiFileHashSymbol(facade.SourceLineNumber, facade.Identifier)); } facade.Hash.Options = 0; facade.Hash.HashPart1 = hash[0]; facade.Hash.HashPart2 = hash[1]; facade.Hash.HashPart3 = hash[2]; facade.Hash.HashPart4 = hash[3]; } } else // update the file row with the version and language information. { // If no version was provided by the user, use the version from the file itself. // This is the most common case. if (String.IsNullOrEmpty(facade.Version)) { facade.Version = version; } else if (!this.FileFacades.Any(r => facade.Version.Equals(r.Id, StringComparison.Ordinal))) // this looks expensive, but see explanation below. { // The user provided a default version for the file row so we looked for a companion file (a file row with Id matching // the version value). We didn't find it so, we will override the default version they provided with the actual // version from the file itself. Now, I know it looks expensive to search through all the file rows trying to match // on the Id. However, the alternative is to build a big index of all file rows to do look ups. Since this case // where the file version is already present is rare (companion files are pretty uncommon), we'll do the more // CPU intensive search to save on the memory intensive index that wouldn't be used much. // // Also note this case can occur when the file is being updated using the WixBindUpdatedFiles extension mechanism. // That's typically even more rare than companion files so again, no index, just search. facade.Version = version; } if (!String.IsNullOrEmpty(facade.Language) && String.IsNullOrEmpty(language)) { this.Messaging.Write(WarningMessages.DefaultLanguageUsedForVersionedFile(facade.SourceLineNumber, facade.Language, facade.Id)); } else // override the default provided by the user (usually nothing) with the actual language from the file itself. { facade.Language = language; } // Populate the binder variables for this file information if requested. if (null != this.VariableCache) { if (!String.IsNullOrEmpty(facade.Version)) { var key = String.Format(CultureInfo.InvariantCulture, "fileversion.{0}", facade.Id); this.VariableCache[key] = facade.Version; } if (!String.IsNullOrEmpty(facade.Language)) { var key = String.Format(CultureInfo.InvariantCulture, "filelanguage.{0}", facade.Id); this.VariableCache[key] = facade.Language; } } } // If this is a CLR assembly, load the assembly and get the assembly name information if (AssemblyType.DotNetAssembly == facade.AssemblyType) { try { var assemblyName = AssemblyNameReader.ReadAssembly(facade.SourceLineNumber, fileInfo.FullName, version); this.SetMsiAssemblyName(assemblyNameSymbols, facade, "name", assemblyName.Name); this.SetMsiAssemblyName(assemblyNameSymbols, facade, "culture", assemblyName.Culture); this.SetMsiAssemblyName(assemblyNameSymbols, facade, "version", assemblyName.Version); if (!String.IsNullOrEmpty(assemblyName.Architecture)) { this.SetMsiAssemblyName(assemblyNameSymbols, facade, "processorArchitecture", assemblyName.Architecture); } // TODO: WiX v3 seemed to do this but not clear it should actually be done. //else if (!String.IsNullOrEmpty(file.WixFile.ProcessorArchitecture)) //{ // this.SetMsiAssemblyName(assemblyNameSymbols, file, "processorArchitecture", file.WixFile.ProcessorArchitecture); //} if (assemblyName.StrongNamedSigned) { this.SetMsiAssemblyName(assemblyNameSymbols, facade, "publicKeyToken", assemblyName.PublicKeyToken); } else if (facade.AssemblyApplicationFileRef == null) { throw new WixException(ErrorMessages.GacAssemblyNoStrongName(facade.SourceLineNumber, fileInfo.FullName, facade.ComponentRef)); } if (!String.IsNullOrEmpty(assemblyName.FileVersion)) { this.SetMsiAssemblyName(assemblyNameSymbols, facade, "fileVersion", assemblyName.FileVersion); } // add the assembly name to the information cache if (null != this.VariableCache) { this.VariableCache[$"assemblyfullname.{facade.Id}"] = assemblyName.GetFullName(); } } catch (WixException e) { this.Messaging.Write(e.Error); } } else if (AssemblyType.Win32Assembly == facade.AssemblyType) { // TODO: Consider passing in the this.FileFacades as an indexed collection instead of searching through // all files like this. Even though this is a rare case it looks like we might be able to index the // file earlier. var fileManifest = this.FileFacades.FirstOrDefault(r => r.Id.Equals(facade.AssemblyManifestFileRef, StringComparison.Ordinal)); if (null == fileManifest) { this.Messaging.Write(ErrorMessages.MissingManifestForWin32Assembly(facade.SourceLineNumber, facade.Id, facade.AssemblyManifestFileRef)); } try { var assemblyName = AssemblyNameReader.ReadAssemblyManifest(facade.SourceLineNumber, fileManifest.SourcePath); if (!String.IsNullOrEmpty(assemblyName.Name)) { this.SetMsiAssemblyName(assemblyNameSymbols, facade, "name", assemblyName.Name); } if (!String.IsNullOrEmpty(assemblyName.Version)) { this.SetMsiAssemblyName(assemblyNameSymbols, facade, "version", assemblyName.Version); } if (!String.IsNullOrEmpty(assemblyName.Type)) { this.SetMsiAssemblyName(assemblyNameSymbols, facade, "type", assemblyName.Type); } if (!String.IsNullOrEmpty(assemblyName.Architecture)) { this.SetMsiAssemblyName(assemblyNameSymbols, facade, "processorArchitecture", assemblyName.Architecture); } if (!String.IsNullOrEmpty(assemblyName.PublicKeyToken)) { this.SetMsiAssemblyName(assemblyNameSymbols, facade, "publicKeyToken", assemblyName.PublicKeyToken); } } catch (WixException e) { this.Messaging.Write(e.Error); } } }
private void UpdateFileFacade(FileFacade file) { var assemblyNameTuples = new Dictionary <string, MsiAssemblyNameTuple>(); foreach (var assemblyTuple in this.Section.Tuples.OfType <MsiAssemblyNameTuple>()) { assemblyNameTuples.Add(assemblyTuple.Component_ + "/" + assemblyTuple.Name, assemblyTuple); } FileInfo fileInfo = null; try { fileInfo = new FileInfo(file.WixFile.Source.Path); } catch (ArgumentException) { this.Messaging.Write(ErrorMessages.InvalidFileName(file.File.SourceLineNumbers, file.WixFile.Source.Path)); return; } catch (PathTooLongException) { this.Messaging.Write(ErrorMessages.InvalidFileName(file.File.SourceLineNumbers, file.WixFile.Source.Path)); return; } catch (NotSupportedException) { this.Messaging.Write(ErrorMessages.InvalidFileName(file.File.SourceLineNumbers, file.WixFile.Source.Path)); return; } if (!fileInfo.Exists) { this.Messaging.Write(ErrorMessages.CannotFindFile(file.File.SourceLineNumbers, file.File.File, file.File.LongFileName, file.WixFile.Source.Path)); return; } using (FileStream fileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read)) { if (Int32.MaxValue < fileStream.Length) { throw new WixException(ErrorMessages.FileTooLarge(file.File.SourceLineNumbers, file.WixFile.Source.Path)); } file.File.FileSize = Convert.ToInt32(fileStream.Length, CultureInfo.InvariantCulture); } string version = null; string language = null; try { Installer.GetFileVersion(fileInfo.FullName, out version, out language); } catch (Win32Exception e) { if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND { throw new WixException(ErrorMessages.FileNotFound(file.File.SourceLineNumbers, fileInfo.FullName)); } else { throw new WixException(ErrorMessages.Win32Exception(e.NativeErrorCode, e.Message)); } } // If there is no version, it is assumed there is no language because it won't matter in the versioning of the install. if (String.IsNullOrEmpty(version)) // unversioned files have their hashes added to the MsiFileHash table { if (!this.OverwriteHash) { // not overwriting hash, so don't do the rest of these options. } else if (null != file.File.Version) { // Search all of the file rows available to see if the specified version is actually a companion file. Yes, this looks // 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. // That's a reasonable thought but companion file usage is usually pretty rare so we'd be doing something expensive (indexing // all the file rows) for a relatively uncommon situation. Let's not do that. // // Also, if we do not find a matching file identifier then the user provided a default version and is providing a version // for unversioned file. That's allowed but generally a dangerous thing to do so let's point that out to the user. if (!this.FileFacades.Any(r => file.File.Version.Equals(r.File.File, StringComparison.Ordinal))) { this.Messaging.Write(WarningMessages.DefaultVersionUsedForUnversionedFile(file.File.SourceLineNumbers, file.File.Version, file.File.File)); } } else { if (null != file.File.Language) { this.Messaging.Write(WarningMessages.DefaultLanguageUsedForUnversionedFile(file.File.SourceLineNumbers, file.File.Language, file.File.File)); } int[] hash; try { Installer.GetFileHash(fileInfo.FullName, 0, out hash); } catch (Win32Exception e) { if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND { throw new WixException(ErrorMessages.FileNotFound(file.File.SourceLineNumbers, fileInfo.FullName)); } else { throw new WixException(ErrorMessages.Win32Exception(e.NativeErrorCode, fileInfo.FullName, e.Message)); } } if (null == file.Hash) { file.Hash = new MsiFileHashTuple(file.File.SourceLineNumbers, file.File.Id); this.Section.Tuples.Add(file.Hash); } file.Hash.File_ = file.File.File; file.Hash.Options = 0; file.Hash.HashPart1 = hash[0]; file.Hash.HashPart2 = hash[1]; file.Hash.HashPart3 = hash[2]; file.Hash.HashPart4 = hash[3]; } } else // update the file row with the version and language information. { // If no version was provided by the user, use the version from the file itself. // This is the most common case. if (String.IsNullOrEmpty(file.File.Version)) { file.File.Version = version; } else if (!this.FileFacades.Any(r => file.File.Version.Equals(r.File.File, StringComparison.Ordinal))) // this looks expensive, but see explanation below. { // The user provided a default version for the file row so we looked for a companion file (a file row with Id matching // the version value). We didn't find it so, we will override the default version they provided with the actual // version from the file itself. Now, I know it looks expensive to search through all the file rows trying to match // on the Id. However, the alternative is to build a big index of all file rows to do look ups. Since this case // where the file version is already present is rare (companion files are pretty uncommon), we'll do the more // CPU intensive search to save on the memory intensive index that wouldn't be used much. // // Also note this case can occur when the file is being updated using the WixBindUpdatedFiles extension mechanism. // That's typically even more rare than companion files so again, no index, just search. file.File.Version = version; } if (!String.IsNullOrEmpty(file.File.Language) && String.IsNullOrEmpty(language)) { this.Messaging.Write(WarningMessages.DefaultLanguageUsedForVersionedFile(file.File.SourceLineNumbers, file.File.Language, file.File.File)); } else // override the default provided by the user (usually nothing) with the actual language from the file itself. { file.File.Language = language; } // Populate the binder variables for this file information if requested. if (null != this.VariableCache) { if (!String.IsNullOrEmpty(file.File.Version)) { var key = String.Format(CultureInfo.InvariantCulture, "fileversion.{0}", file.File.File); this.VariableCache[key] = file.File.Version; } if (!String.IsNullOrEmpty(file.File.Language)) { var key = String.Format(CultureInfo.InvariantCulture, "filelanguage.{0}", file.File.File); this.VariableCache[key] = file.File.Language; } } } // If this is a CLR assembly, load the assembly and get the assembly name information if (FileAssemblyType.DotNetAssembly == file.WixFile.AssemblyType) { bool targetNetfx1 = false; var assemblyNameValues = new Dictionary <string, string>(); Guid referenceIdentityGuid = ClrInterop.ReferenceIdentityGuid; var result = ClrInterop.GetAssemblyIdentityFromFile(fileInfo.FullName, ref referenceIdentityGuid, out var referenceIdentity); if (0 == result && null != referenceIdentity) { var imageRuntimeVersion = referenceIdentity.GetAttribute(null, "ImageRuntimeVersion"); if (null != imageRuntimeVersion) { targetNetfx1 = imageRuntimeVersion.StartsWith("v1", StringComparison.OrdinalIgnoreCase); } string culture = referenceIdentity.GetAttribute(null, "Culture") ?? "neutral"; assemblyNameValues.Add("Culture", culture); string name = referenceIdentity.GetAttribute(null, "Name"); if (null != name) { assemblyNameValues.Add("Name", name); } string processorArchitecture = referenceIdentity.GetAttribute(null, "ProcessorArchitecture"); if (null != processorArchitecture) { assemblyNameValues.Add("ProcessorArchitecture", processorArchitecture); } string publicKeyToken = referenceIdentity.GetAttribute(null, "PublicKeyToken"); if (null != publicKeyToken) { bool publicKeyIsNeutral = (String.Equals(publicKeyToken, "neutral", StringComparison.OrdinalIgnoreCase)); // Managed code expects "null" instead of "neutral", and // this won't be installed to the GAC since it's not signed anyway. assemblyNameValues.Add("publicKeyToken", publicKeyIsNeutral ? "null" : publicKeyToken.ToUpperInvariant()); assemblyNameValues.Add("publicKeyTokenPreservedCase", publicKeyIsNeutral ? "null" : publicKeyToken); } else if (file.WixFile.File_AssemblyApplication == null) { throw new WixException(ErrorMessages.GacAssemblyNoStrongName(file.File.SourceLineNumbers, fileInfo.FullName, file.File.Component_)); } string assemblyVersion = referenceIdentity.GetAttribute(null, "Version"); if (null != version) { assemblyNameValues.Add("Version", assemblyVersion); } } else { this.Messaging.Write(ErrorMessages.InvalidAssemblyFile(file.File.SourceLineNumbers, fileInfo.FullName, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", result))); return; } if (assemblyNameValues.TryGetValue("name", out var value)) { this.SetMsiAssemblyName(assemblyNameTuples, file, "name", value); } if (!String.IsNullOrEmpty(version)) { this.SetMsiAssemblyName(assemblyNameTuples, file, "fileVersion", version); } if (assemblyNameValues.ContainsKey("version")) { string assemblyVersion = assemblyNameValues["version"]; if (!targetNetfx1) { // There is a bug in v1 fusion that requires the assembly's "version" attribute // to be equal to or longer than the "fileVersion" in length when its present; // the workaround is to prepend zeroes to the last version number in the assembly // version. if (null != version && version.Length > assemblyVersion.Length) { string padding = new string('0', version.Length - assemblyVersion.Length); string[] assemblyVersionNumbers = assemblyVersion.Split('.'); if (assemblyVersionNumbers.Length > 0) { assemblyVersionNumbers[assemblyVersionNumbers.Length - 1] = String.Concat(padding, assemblyVersionNumbers[assemblyVersionNumbers.Length - 1]); assemblyVersion = String.Join(".", assemblyVersionNumbers); } } } this.SetMsiAssemblyName(assemblyNameTuples, file, "version", assemblyVersion); } if (assemblyNameValues.ContainsKey("culture")) { this.SetMsiAssemblyName(assemblyNameTuples, file, "culture", assemblyNameValues["culture"]); } if (assemblyNameValues.ContainsKey("publicKeyToken")) { this.SetMsiAssemblyName(assemblyNameTuples, file, "publicKeyToken", assemblyNameValues["publicKeyToken"]); } if (!String.IsNullOrEmpty(file.WixFile.ProcessorArchitecture)) { this.SetMsiAssemblyName(assemblyNameTuples, file, "processorArchitecture", file.WixFile.ProcessorArchitecture); } if (assemblyNameValues.ContainsKey("processorArchitecture")) { this.SetMsiAssemblyName(assemblyNameTuples, file, "processorArchitecture", assemblyNameValues["processorArchitecture"]); } // add the assembly name to the information cache if (null != this.VariableCache) { string fileId = file.File.File; string key = String.Concat("assemblyfullname.", fileId); string assemblyName = String.Concat(assemblyNameValues["name"], ", version=", assemblyNameValues["version"], ", culture=", assemblyNameValues["culture"], ", publicKeyToken=", String.IsNullOrEmpty(assemblyNameValues["publicKeyToken"]) ? "null" : assemblyNameValues["publicKeyToken"]); if (assemblyNameValues.ContainsKey("processorArchitecture")) { assemblyName = String.Concat(assemblyName, ", processorArchitecture=", assemblyNameValues["processorArchitecture"]); } this.VariableCache[key] = assemblyName; // Add entries with the preserved case publicKeyToken string pcAssemblyNameKey = String.Concat("assemblyfullnamepreservedcase.", fileId); this.VariableCache[pcAssemblyNameKey] = (assemblyNameValues["publicKeyToken"] == assemblyNameValues["publicKeyTokenPreservedCase"]) ? assemblyName : assemblyName.Replace(assemblyNameValues["publicKeyToken"], assemblyNameValues["publicKeyTokenPreservedCase"]); string pcPublicKeyTokenKey = String.Concat("assemblypublickeytokenpreservedcase.", fileId); this.VariableCache[pcPublicKeyTokenKey] = assemblyNameValues["publicKeyTokenPreservedCase"]; } } else if (FileAssemblyType.Win32Assembly == file.WixFile.AssemblyType) { // TODO: Consider passing in the this.FileFacades as an indexed collection instead of searching through // all files like this. Even though this is a rare case it looks like we might be able to index the // file earlier. FileFacade fileManifest = this.FileFacades.SingleOrDefault(r => r.File.File.Equals(file.WixFile.File_AssemblyManifest, StringComparison.Ordinal)); if (null == fileManifest) { this.Messaging.Write(ErrorMessages.MissingManifestForWin32Assembly(file.File.SourceLineNumbers, file.File.File, file.WixFile.File_AssemblyManifest)); } string win32Type = null; string win32Name = null; string win32Version = null; string win32ProcessorArchitecture = null; string win32PublicKeyToken = null; // loading the dom is expensive we want more performant APIs than the DOM // Navigator is cheaper than dom. Perhaps there is a cheaper API still. try { XPathDocument doc = new XPathDocument(fileManifest.WixFile.Source.Path); XPathNavigator nav = doc.CreateNavigator(); nav.MoveToRoot(); // this assumes a particular schema for a win32 manifest and does not // provide error checking if the file does not conform to schema. // The fallback case here is that nothing is added to the MsiAssemblyName // table for an out of tolerance Win32 manifest. Perhaps warnings needed. if (nav.MoveToFirstChild()) { while (nav.NodeType != XPathNodeType.Element || nav.Name != "assembly") { nav.MoveToNext(); } if (nav.MoveToFirstChild()) { bool hasNextSibling = true; while (nav.NodeType != XPathNodeType.Element || nav.Name != "assemblyIdentity" && hasNextSibling) { hasNextSibling = nav.MoveToNext(); } if (!hasNextSibling) { this.Messaging.Write(ErrorMessages.InvalidManifestContent(file.File.SourceLineNumbers, fileManifest.WixFile.Source.Path)); return; } if (nav.MoveToAttribute("type", String.Empty)) { win32Type = nav.Value; nav.MoveToParent(); } if (nav.MoveToAttribute("name", String.Empty)) { win32Name = nav.Value; nav.MoveToParent(); } if (nav.MoveToAttribute("version", String.Empty)) { win32Version = nav.Value; nav.MoveToParent(); } if (nav.MoveToAttribute("processorArchitecture", String.Empty)) { win32ProcessorArchitecture = nav.Value; nav.MoveToParent(); } if (nav.MoveToAttribute("publicKeyToken", String.Empty)) { win32PublicKeyToken = nav.Value; nav.MoveToParent(); } } } } catch (FileNotFoundException fe) { this.Messaging.Write(ErrorMessages.FileNotFound(new SourceLineNumber(fileManifest.WixFile.Source.Path), fe.FileName, "AssemblyManifest")); } catch (XmlException xe) { this.Messaging.Write(ErrorMessages.InvalidXml(new SourceLineNumber(fileManifest.WixFile.Source.Path), "manifest", xe.Message)); } if (!String.IsNullOrEmpty(win32Name)) { this.SetMsiAssemblyName(assemblyNameTuples, file, "name", win32Name); } if (!String.IsNullOrEmpty(win32Version)) { this.SetMsiAssemblyName(assemblyNameTuples, file, "version", win32Version); } if (!String.IsNullOrEmpty(win32Type)) { this.SetMsiAssemblyName(assemblyNameTuples, file, "type", win32Type); } if (!String.IsNullOrEmpty(win32ProcessorArchitecture)) { this.SetMsiAssemblyName(assemblyNameTuples, file, "processorArchitecture", win32ProcessorArchitecture); } if (!String.IsNullOrEmpty(win32PublicKeyToken)) { this.SetMsiAssemblyName(assemblyNameTuples, file, "publicKeyToken", win32PublicKeyToken); } } }