/// <summary> /// Test if the source should be written to the destination (true) or if the destination is newer/same (false) /// </summary> /// <param name="source">AssemblyLocationInformation</param> /// <param name="destination">string with destination path</param> /// <returns>bool</returns> private bool ShouldWrite(AssemblyLocationInformation source, string destination) { if (source.Filename.Equals(destination)) { return(false); } string path = Path.GetDirectoryName(destination); if (!Directory.Exists(path)) { Directory.CreateDirectory(FileLocations.AddonsLocation); } if (!File.Exists(destination)) { return(true); } if (File.GetLastWriteTime(destination) >= source.FileDate) { return(false); } Log.Warn().WriteLine("Overwriting {0} with {1}, as the later is newer.", destination, source.Filename); File.Delete(destination); return(true); }
/// <summary> /// Logic to load an embedded assembly /// </summary> /// <param name="assemblyLocationInformation"></param> /// <returns>Assembly</returns> private Assembly LoadEmbeddedAssembly(AssemblyLocationInformation assemblyLocationInformation) { if (!assemblyLocationInformation.IsEmbedded) { return(null); } // Check if we can work with temporary files if (_applicationConfig.CopyEmbeddedAssembliesToFileSystem) { var assembly = LoadEmbeddedAssemblyViaTmpFile(assemblyLocationInformation); if (assembly != null) { return(assembly); } } if (Log.IsVerboseEnabled()) { Log.Verbose().WriteLine("Loading {0} internally, this COULD cause assembly load context issues...", assemblyLocationInformation.Name); } using (var stream = Resources.AbsoluteResourceAsStream(assemblyLocationInformation.ContainingAssembly, assemblyLocationInformation.Filename)) { return(Assembly.Load(stream.ToByteArray())); } }
/// <summary> /// This is a workaround where an embedded assembly is written to a tmp file, which solves some issues /// </summary> /// <param name="assemblyLocationInformation">AssemblyLocationInformation</param> /// <returns>Assembly</returns> private Assembly LoadEmbeddedAssemblyViaTmpFile(AssemblyLocationInformation assemblyLocationInformation) { var assemblyFileName = $@"{FileLocations.AddonsLocation}\{assemblyLocationInformation.Name}.dll"; using (var stream = Resources.AbsoluteResourceAsStream(assemblyLocationInformation.ContainingAssembly, assemblyLocationInformation.Filename)) { try { if (ShouldWrite(assemblyLocationInformation, assemblyFileName)) { using (var fileStream = new FileStream(assemblyFileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None)) { stream.CopyTo(fileStream); } // Register delete on exit, this is done by calling a command _assembliesToDeleteAtExit.Add(assemblyFileName); } // Get the assembly name from the file var assemblyName = AssemblyName.GetAssemblyName(assemblyFileName); if (Log.IsVerboseEnabled()) { Log.Verbose().WriteLine("Loading {0} from {1}", assemblyLocationInformation.Name, assemblyFileName); } // Use load, as it's now in the probing path return(Assembly.Load(assemblyName)); } catch (Exception) { stream.Seek(0, SeekOrigin.Begin); var appdataDirectory = $@"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}\{_applicationConfig.ApplicationName}"; assemblyFileName = $@"{appdataDirectory}\{assemblyLocationInformation.Name}.dll"; if (ShouldWrite(assemblyLocationInformation, assemblyFileName)) { using (var fileStream = new FileStream(assemblyFileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None)) { stream.CopyTo(fileStream); } // Register delete on exit, this is done by calling a command _assembliesToDeleteAtExit.Add(assemblyFileName); } if (Log.IsVerboseEnabled()) { Log.Verbose().WriteLine("Loading {0} from {1}", assemblyLocationInformation.Name, assemblyFileName); } // Use load-from, as it's on a different place return(Assembly.LoadFrom(assemblyFileName)); } } }
/// <summary> /// Load an assembly from the specified location /// </summary> /// <param name="assemblyLocationInformation">AssemblyLocationInformation</param> /// <returns>Assembly</returns> public Assembly LoadAssembly(AssemblyLocationInformation assemblyLocationInformation) { // Check if the simple name can be found in the cache if (LoadedAssemblies.TryGetValue(assemblyLocationInformation.Name, out var assembly)) { if (Log.IsInfoEnabled()) { Log.Info().WriteLine("Returned {0} from cache.", assemblyLocationInformation.Name); } return(assembly); } if (assemblyLocationInformation.IsEmbedded) { return(LoadEmbeddedAssembly(assemblyLocationInformation)); } // Load from file return(LoadFromFile(assemblyLocationInformation)); }
/// <summary> /// This is called when a new assembly is loaded, we need to know this /// </summary> /// <param name="sender">object</param> /// <param name="args">AssemblyLoadEventArgs</param> private void AssemblyLoad(object sender, AssemblyLoadEventArgs args) { var loadedAssembly = args.LoadedAssembly; var assemblyName = loadedAssembly.GetName().Name; if (Log.IsVerboseEnabled()) { Log.Verbose().WriteLine("Loaded {0}", assemblyName); } LoadedAssemblies[assemblyName] = loadedAssembly; if (!_applicationConfig.ScanForEmbeddedAssemblies) { return; } // Ignore resource checking on certain assemblies if (AssembliesToIgnore.IsMatch(assemblyName)) { return; } string[] resources; try { resources = Resources.GetCachedManifestResourceNames(loadedAssembly); } catch (Exception ex) { Log.Warn().WriteLine(ex, "Couldn't retrieve resources from {0}", loadedAssembly.GetName().Name); return; } foreach (var resource in resources) { var resourceMatch = _assemblyResourceNameRegex.Match(resource); if (!resourceMatch.Success) { continue; } var embeddedAssemblyName = resourceMatch.Groups["assembly"].Value; if (LoadedAssemblies.ContainsKey(embeddedAssemblyName)) { // Ignoring already loaded assembly, as we cannot unload. continue; } var newAssemblyLocation = new AssemblyLocationInformation(embeddedAssemblyName, loadedAssembly, resource); if (AvailableAssemblies.TryGetValue(embeddedAssemblyName, out var availableAssemblyLocationInformation)) { if (availableAssemblyLocationInformation.FileDate > newAssemblyLocation.FileDate) { continue; } } if (Log.IsVerboseEnabled()) { Log.Verbose().WriteLine("Detected additional assembly {0} in {1}", embeddedAssemblyName, loadedAssembly.GetName().Name); } AvailableAssemblies[embeddedAssemblyName] = newAssemblyLocation; } }
/// <summary> /// Load an assembly via a file, this used via Assembly.Load or Assembly.LoadFrom depending on where the file is or can be stored /// </summary> /// <param name="additionalInformation">AssemblyLocationInformation used for some decisions</param> /// <returns>Assembly</returns> private Assembly LoadFromFile(AssemblyLocationInformation additionalInformation) { // Get the assembly name from the file var assemblyName = AssemblyName.GetAssemblyName(additionalInformation.Filename); // Check the cache again, this time with the "real" name if (LoadedAssemblies.TryGetValue(assemblyName.Name, out var assembly)) { if (Log.IsInfoEnabled()) { Log.Info().WriteLine("Returned {0} from cache.", assemblyName.Name); } return(assembly); } var destination = $@"{FileLocations.AddonsLocation}\{assemblyName.Name}.dll"; var destinationIsNotSource = !destination.Equals(additionalInformation.Filename, StringComparison.OrdinalIgnoreCase); if (Log.IsDebugEnabled() && !destinationIsNotSource) { Log.Debug().WriteLine("Skipping copy, as destination and source would be the same."); } if (_applicationConfig.CopyAssembliesToProbingPath && FileLocations.AddonsLocation != null && destinationIsNotSource) { try { if (ShouldWrite(additionalInformation, destination)) { if (Log.IsVerboseEnabled()) { Log.Verbose().WriteLine("Creating a copy of {0} to {1}, solving potential context loading issues.", additionalInformation.Filename, destination); } File.Copy(additionalInformation.Filename, destination); // Register delete on exit, this is done by calling a command _assembliesToDeleteAtExit.Add(destination); } // Load via the assembly name, it's not inside the probing path assembly = Assembly.Load(assemblyName); if (assembly != null) { return(assembly); } } catch (Exception ex) { Log.Warn().WriteLine(ex, "Couldn't create a copy of {0} to {1}.", additionalInformation.Filename, destination); } } if (Log.IsVerboseEnabled()) { Log.Verbose().WriteLine("Loading {0} from {1}.", additionalInformation.Name, additionalInformation.Filename); } try { return(Assembly.LoadFrom(additionalInformation.Filename)); } catch (Exception ex) { Log.Error().WriteLine(ex, "Couldn't load assembly from file {0}", additionalInformation.Filename); } return(null); }