ModuleIsolation.cs
using System.Reflection;
using System.Management.Automation; using System.Runtime.Loader; using System.IO; namespace PipeHow.Isol8; // Implement interfaces for interacting with loading logic of PowerShell public abstract class ModuleInitializer : IModuleAssemblyInitializer, IModuleAssemblyCleanup { // Create a new custom ALC and provide the directory private static Isol8AssemblyLoadContext alc; public ModuleInitializer(string assemblyName) { alc = new Isol8AssemblyLoadContext(dependencyDirectory, assemblyName); } // Runs when Import-Module is run on our module, but in this case also when referred to in NestedModules public void OnImport() => AssemblyLoadContext.Default.Resolving += ResolveAssembly; // Runs when user runs Remove-Module on our module public void OnRemove(PSModuleInfo psModuleInfo) => AssemblyLoadContext.Default.Resolving -= ResolveAssembly; // Name of initializer assembly public static string AssemblyName { get; set; } // Get directory of this assembly, and use that directory to load dependencies from private static readonly string dependencyDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); // Resolve assembly by name if it's the Isol8 dll being loaded by the default ALC // We know it's the default ALC because of OnImport above public static Assembly ResolveAssembly(AssemblyLoadContext defaultAlc, AssemblyName assemblyName) => alc.LoadFromAssemblyName(assemblyName); } // We create our own ALC by inheriting from AssemblyLoadContext and overriding the Load() method // We can also change the constructor to take a path which we load from, which we do here public class Isol8AssemblyLoadContext : AssemblyLoadContext { // The path which we try to load the assemblies from private readonly string dependencyDirectory; // We can call the base constructor to set a name for the ALC // There are more options such as marking our ALC as collectible to enable unloading it, but that doesn't work with PowerShell public Isol8AssemblyLoadContext(string path, string moduleName) : base(moduleName) { dependencyDirectory = path; } // Override the Load() method and try to load the module as a DLL file in the provided directory if it exists protected override Assembly Load(AssemblyName assemblyName) { var assemblyPath = Path.Join(dependencyDirectory, $"{assemblyName.Name}.dll"); // If it exists we can load it from the path if (File.Exists(assemblyPath)) { return LoadFromAssemblyPath(assemblyPath); } // Returning null once more lets the loader know that we didn't load the module, and lets it try something else return null; } } |