{"id":18432,"date":"2020-06-16T13:51:23","date_gmt":"2020-06-16T21:51:23","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/powershell\/?p=18432"},"modified":"2024-01-25T03:32:52","modified_gmt":"2024-01-25T11:32:52","slug":"resolving-powershell-module-assembly-dependency-conflicts","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/powershell\/resolving-powershell-module-assembly-dependency-conflicts\/","title":{"rendered":"Resolving PowerShell Module Assembly Dependency Conflicts"},"content":{"rendered":"<p>When writing a PowerShell module, especially a binary module (i.e. one written in a language like C# and loaded into PowerShell as an assembly\/DLL), it&#8217;s natural to take dependencies on other packages or libraries to provide functionality.<\/p>\n<p>Taking dependencies on other libraries is usually desirable for code reuse. However, PowerShell always loads assemblies into the same context, and this can present issues when a module&#8217;s dependencies conflict with already-loaded DLLs, preventing two otherwise unrelated modules from being used together in the same PowerShell session. If you&#8217;ve been hit by this yourself, you would have seen an error message like this:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/powershell\/wp-content\/uploads\/sites\/30\/2020\/06\/moduleconflict.png\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"><img decoding=\"async\" style=\"max-width: 100%;\" src=\"https:\/\/devblogs.microsoft.com\/powershell\/wp-content\/uploads\/sites\/30\/2020\/06\/moduleconflict.png\" alt=\"Assembly load conflict error message.\" data-canonical-src=\"https:\/\/devblogs.microsoft.com\/powershell\/wp-content\/uploads\/sites\/30\/2020\/06\/moduleconflict.png\" \/><\/a><\/p>\n<p>In this blog post, we&#8217;ll look at some of the ways dependency conflicts can arise in PowerShell, and some of the ways to mitigate dependency conflict issues. Even if you&#8217;re not a module author, there are some tricks in here that might help you with dependency conflicts arising in modules that you use.<\/p>\n<h2><a id=\"user-content-contents\" class=\"anchor\" href=\"#contents\" aria-hidden=\"true\"><\/a>Contents<\/h2>\n<p>This is a fairly long blog post, so here&#8217;s a table of contents:<\/p>\n<ul>\n<li><a href=\"#why-do-dependency-conflicts-occur\">Why do dependency conflicts occur?<\/a><\/li>\n<li><a href=\"#powershell-and-net\">PowerShell and .NET<\/a><\/li>\n<li><a href=\"#quick-fixes-and-their-limitations\">Quick fixes and their limitations<\/a><\/li>\n<li><a href=\"#more-robust-solutions\">More robust solutions<\/a><\/li>\n<li><a href=\"#solutions-for-dependency-conflicts-that-dont-work-for-powershell\">Solutions for dependency conflicts that don&#8217;t work for PowerShell<\/a><\/li>\n<li><a href=\"#solving-the-issue-in-powershell-itself\">Solving the issue in PowerShell itself&#8230;<\/a><\/li>\n<li><a href=\"#further-reading\">Further reading<\/a><\/li>\n<li><a href=\"#final-notes\">Final notes<\/a><\/li>\n<\/ul>\n<h2><a id=\"user-content-why-do-dependency-conflicts-occur\" class=\"anchor\" href=\"#why-do-dependency-conflicts-occur\" aria-hidden=\"true\"><\/a>Why do dependency conflicts occur?<\/h2>\n<p>In .NET, dependency conflicts arise when two versions of the same assembly are loaded into the same Assembly Load Context (this term means slightly different things on different .NET platforms, which we&#8217;ll cover later). This conflict issue is a common problem that occurs essentially everywhere in software where versioned dependencies are used. Because there are no simple solutions (and because many dependency resolution frameworks try to solve the problem in different ways), this situation is often called &#8220;dependency hell&#8221;.<\/p>\n<p>Conflict issues are compounded by the fact that a project almost never deliberately or directly depends on two versions of the same dependency, but instead depends on two different dependencies that each require a different version of the same dependency.<\/p>\n<p>For example, say your .NET application, <code>DuckBuilder<\/code>, brings in two dependencies, to perform parts of its functionality and looks like this:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/powershell\/wp-content\/uploads\/sites\/30\/2020\/06\/dep-conflict.jpg\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"><img decoding=\"async\" style=\"max-width: 100%;\" src=\"https:\/\/devblogs.microsoft.com\/powershell\/wp-content\/uploads\/sites\/30\/2020\/06\/dep-conflict.jpg\" alt=\"Two dependencies of DuckBuilder rely on different versions of Newtonsoft.Json\" data-canonical-src=\"https:\/\/devblogs.microsoft.com\/powershell\/wp-content\/uploads\/sites\/30\/2020\/06\/dep-conflict.jpg\" \/><\/a><\/p>\n<p>Because <code>Contoso.ZipTools<\/code> and <code>Fabrikam.FileHelpers<\/code> both depend on <code>Newtonsoft.Json<\/code>, but different versions, there may be a dependency conflict, depending on how each dependency is loaded.<\/p>\n<h3><a id=\"user-content-conflicting-with-powershells-dependencies\" class=\"anchor\" href=\"#conflicting-with-powershells-dependencies\" aria-hidden=\"true\"><\/a>Conflicting with PowerShell&#8217;s dependencies<\/h3>\n<p>In PowerShell, the dependency conflict issue is exacerbated because PowerShell modules, and PowerShell&#8217;s own dependencies, are all loaded into the same shared context. This means the PowerShell engine and all loaded PowerShell modules must not have conflicting dependencies.<\/p>\n<p>One scenario in which this can cause issues is where a module has a dependency that conflicts with one of PowerShell&#8217;s own dependencies. A classic example of this is Newtonsoft.Json:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/powershell\/wp-content\/uploads\/sites\/30\/2020\/06\/engine-conflict.jpg\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"><img decoding=\"async\" style=\"max-width: 100%;\" src=\"https:\/\/devblogs.microsoft.com\/powershell\/wp-content\/uploads\/sites\/30\/2020\/06\/engine-conflict.jpg\" alt=\"FictionalTools module depends on newer version of Newtonsoft.Json than PowerShell\" data-canonical-src=\"https:\/\/devblogs.microsoft.com\/powershell\/wp-content\/uploads\/sites\/30\/2020\/06\/engine-conflict.jpg\" \/><\/a><\/p>\n<p>In this example, the module <code>FictionalTools<\/code> depends on <code>Newtonsoft.Json<\/code> version <code>12.0.3<\/code>, which is a newer version of Newtonsoft.Json than 11.0.2 that ships in the example PowerShell. (To be clear, this is an example and PowerShell 7 currently ships with Newtonsoft.Json 12.0.3.)<\/p>\n<p>Because the module depends on a newer version of the assembly, it won&#8217;t accept the version that PowerShell already has loaded, but because PowerShell has already loaded a version of the assembly, the module can&#8217;t load its own using the conventional load mechanism.<\/p>\n<h3><a id=\"user-content-conflicting-with-another-modules-dependencies\" class=\"anchor\" href=\"#conflicting-with-another-modules-dependencies\" aria-hidden=\"true\"><\/a>Conflicting with another module&#8217;s dependencies<\/h3>\n<p>Another common scenario in PowerShell is that a module is loaded that depends on one version of an assembly, and then another module is loaded later that depends on a different version of that assembly.<\/p>\n<p>This often looks like the following:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/powershell\/wp-content\/uploads\/sites\/30\/2020\/06\/mod-conflict.jpg\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"><img decoding=\"async\" style=\"max-width: 100%;\" src=\"https:\/\/devblogs.microsoft.com\/powershell\/wp-content\/uploads\/sites\/30\/2020\/06\/mod-conflict.jpg\" alt=\"Two PowerShell modules require different versions of the Microsoft.Extensions.Logging dependency\" data-canonical-src=\"https:\/\/devblogs.microsoft.com\/powershell\/wp-content\/uploads\/sites\/30\/2020\/06\/mod-conflict.jpg\" \/><\/a><\/p>\n<p>In the above case, the <code>FictionalTools<\/code> module requires a newer version of <code>Microsoft.Extensions.Logging<\/code> than the <code>FilesystemManager<\/code> module.<\/p>\n<p>Let&#8217;s imagine these modules load their dependencies by placing the dependency assemblies in the same directory as the root module assembly and allowing .NET to implicitly load them by name. If we are running PowerShell 7 (on top of .NET Core 3.1), we can load and run <code>FictionalTools<\/code> and then load and run <code>FilesystemManager<\/code> without issue. However, if in a new session we load and run <code>FilesystemManager<\/code> and then <code>FictionalTools<\/code>, we will encounter a <code>FileLoadException<\/code> from the <code>FictionalTools<\/code> command, because it requires a newer version of <code>Microsoft.Extensions.Logging<\/code> than the one loaded, but cannot load it because an assembly of the same name has already been loaded.<\/p>\n<h2><a id=\"user-content-powershell-and-net\" class=\"anchor\" href=\"#powershell-and-net\" aria-hidden=\"true\"><\/a>PowerShell and .NET<\/h2>\n<p>PowerShell runs on the .NET platform, and since we&#8217;re discussing assembly dependency conflicts, we must discuss .NET. .NET is ultimately responsible for resolving and loading assembly dependencies, so we must understand how .NET operates here to understand dependency conflicts.<\/p>\n<p>We must also confront the fact that different versions of PowerShell run on different .NET implementations, which respectively have their own mechanisms for assembly resolution.<\/p>\n<p>In general, PowerShell 5.1 and below run on .NET Framework, while PowerShell 6 and above run on .NET Core. These two flavours of .NET load and handle assemblies somewhat differently, meaning resolving dependency conflicts can vary depending on the underlying .NET platform.<\/p>\n<h3><a id=\"user-content-assembly-load-contexts\" class=\"anchor\" href=\"#assembly-load-contexts\" aria-hidden=\"true\"><\/a>Assembly Load Contexts<\/h3>\n<p>In this post, we&#8217;ll use the term &#8220;Assembly Load Context&#8221; (ALC) frequently. An Assembly Load Context is a .NET concept that essentially means a runtime namespace into which assemblies are loaded and within which assemblies&#8217; names are unique. This concept allows assemblies to be uniquely resolved by name in each ALC.<\/p>\n<h3><a id=\"user-content-assembly-reference-loading-in-net\" class=\"anchor\" href=\"#assembly-reference-loading-in-net\" aria-hidden=\"true\"><\/a>Assembly reference loading in .NET<\/h3>\n<p>The semantics of assembly loading (whether versions clash, whether that assembly&#8217;s dependencies are handled, etc.) depend on both the .NET implementation (.NET Core vs .NET Framework) and the API or .NET mechanism used to load a particular assembly.<\/p>\n<p>Rather than go into deep detail describing these here, there is a list of links in the <a href=\"#further-reading\">Further reading<\/a> section that go into great detail on how .NET assembly loading works in each .NET implementation.<\/p>\n<p>In this blog post, we&#8217;ll refer to the following mechanisms:<\/p>\n<ul>\n<li>Implicit assembly loading (effectively <code>Assembly.Load(AssemblyName)<\/code>), when .NET implicitly tries to load an assembly by name from a static assembly reference in .NET code.<\/li>\n<li><code>Assembly.LoadFrom()<\/code>, a plugin-oriented loading API that will add handlers to resolve dependencies of the loaded DLL (but which may not do so the way we want).<\/li>\n<li><code>Assembly.LoadFile()<\/code>, a bare-bones loading API intended to load only the assembly asked for with no handling of its dependencies.<\/li>\n<\/ul>\n<p>The way these APIs work has changed in subtle ways between .NET Core and .NET Framework, so it&#8217;s worth reading through the further reading links.<\/p>\n<h3><a id=\"user-content-differences-in-net-framework-vs-net-core\" class=\"anchor\" href=\"#differences-in-net-framework-vs-net-core\" aria-hidden=\"true\"><\/a>Differences in .NET Framework vs .NET Core<\/h3>\n<p>Importantly, Assembly Load Contexts and other assembly resolution mechanisms have changed between .NET Framework and .NET Core.<\/p>\n<p>In particular .NET Framework has the following features:<\/p>\n<ul>\n<li>The Global Assembly Cache, for machine-wide assembly resolution<\/li>\n<li>Application Domains, which work like in-process sandboxes for assembly isolation, but also present a serialization layer to contend with<\/li>\n<li>A limited assembly load context model, explained in depth <a href=\"https:\/\/docs.microsoft.com\/dotnet\/framework\/deployment\/best-practices-for-assembly-loading\" rel=\"nofollow\">here<\/a>, that has a fixed set of assembly load contexts, each with their own behaviour:\n<ul>\n<li>The default load context, where assemblies are loaded by default<\/li>\n<li>The load-from context, for loading assemblies manually at runtime<\/li>\n<li>The reflection-only context, for safely loading assemblies to read their metadata without running them<\/li>\n<li>The mysterious void that assemblies loaded with <code>Assembly.LoadFile(string path)<\/code> and <code>Assembly.Load(byte[] asmBytes)<\/code> live in<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>.NET Core (and .NET 5+) has eschewed this complexity for a simpler model:<\/p>\n<ul>\n<li>No Global Assembly Cache; applications bring all their own dependencies (PowerShell, as the plugin host, complicates this slightly for modules ??? its dependencies in <code>$PSHOME<\/code> are shared with all modules). This removes an exogenous factor for dependency resolution in applications, making dependency resolution more reproducible.<\/li>\n<li>Only one Application Domain, and no ability to create new ones. The Application Domain concept lives on purely to be the global state of the .NET process.<\/li>\n<li>A new, extensible Assembly Load Context model, where assembly resolution can be effectively namespaced by putting it in a new ALC. .NET Core processes begin with a single default ALC into which all assemblies are loaded (except for those loaded with <code>Assembly.LoadFile(string)<\/code> and <code>Assembly.Load(byte[])<\/code>), but are free to create and define their own custom ALCs with their own loading logic. When an assembly is loaded, the first ALC it is loaded into is responsible for resolving its dependencies, creating opportunities to create powerful .NET plugin loading mechanisms.<\/li>\n<\/ul>\n<p>In both implementations, assemblies are loaded lazily when a method requiring their type is run for the first time.<\/p>\n<p>For example here are two versions of the same code that load a dependency at different times.<\/p>\n<p>The first will always load its dependency when <code>Program.GetRange()<\/code> is called, because the dependency reference is lexically present within the method:<\/p>\n<div class=\"highlight highlight-source-cs\">\n<pre><span class=\"pl-k\">using<\/span> <span class=\"pl-en\">Dependency<\/span>.<span class=\"pl-en\">Library<\/span>;\r\n\r\n<span class=\"pl-k\">public<\/span> <span class=\"pl-k\">static<\/span> <span class=\"pl-k\">class<\/span> <span class=\"pl-en\">Program<\/span>\r\n{\r\n    <span class=\"pl-k\">public<\/span> <span class=\"pl-k\">static<\/span> <span class=\"pl-en\">List<\/span>&lt;<span class=\"pl-k\">int<\/span>&gt; <span class=\"pl-en\">GetRange<\/span>(<span class=\"pl-k\">int<\/span> <span class=\"pl-smi\">limit<\/span>)\r\n    {\r\n        <span class=\"pl-k\">var<\/span> <span class=\"pl-smi\">list<\/span> <span class=\"pl-k\">=<\/span> <span class=\"pl-k\">new<\/span> <span class=\"pl-en\">List<\/span>&lt;<span class=\"pl-k\">int<\/span>&gt;();\r\n        <span class=\"pl-k\">for<\/span> (<span class=\"pl-k\">int<\/span> <span class=\"pl-smi\">i<\/span> <span class=\"pl-k\">=<\/span> <span class=\"pl-c1\">0<\/span>; <span class=\"pl-smi\">i<\/span> <span class=\"pl-k\">&lt;<\/span> <span class=\"pl-smi\">limit<\/span>; <span class=\"pl-smi\">i<\/span><span class=\"pl-k\">++<\/span>)\r\n        {\r\n            <span class=\"pl-k\">if<\/span> (<span class=\"pl-smi\">i<\/span> <span class=\"pl-k\">&gt;=<\/span> <span class=\"pl-c1\">20<\/span>)\r\n            {\r\n                <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> Dependency.Library will be loaded when GetNumbers is run<\/span>\r\n                <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> because the dependency call occurs directly within the method<\/span>\r\n                <span class=\"pl-smi\">DependencyApi<\/span>.<span class=\"pl-en\">Use<\/span>();\r\n            }\r\n\r\n            <span class=\"pl-smi\">list<\/span>.<span class=\"pl-en\">Add<\/span>(<span class=\"pl-smi\">i<\/span>);\r\n        }\r\n        <span class=\"pl-k\">return<\/span> <span class=\"pl-smi\">list<\/span>;\r\n    }\r\n}<\/pre>\n<\/div>\n<p>The second will load its dependency only if the <code>limit<\/code> parameter is 20 or more, because of the internal indirection through a method:<\/p>\n<div class=\"highlight highlight-source-cs\">\n<pre><span class=\"pl-k\">using<\/span> <span class=\"pl-en\">Dependency<\/span>.<span class=\"pl-en\">Library<\/span>;\r\n\r\n<span class=\"pl-k\">public<\/span> <span class=\"pl-k\">static<\/span> <span class=\"pl-k\">class<\/span> <span class=\"pl-en\">Program<\/span>\r\n{\r\n    <span class=\"pl-k\">public<\/span> <span class=\"pl-k\">static<\/span> <span class=\"pl-en\">List<\/span>&lt;<span class=\"pl-k\">int<\/span>&gt; <span class=\"pl-en\">GetNumbers<\/span>(<span class=\"pl-k\">int<\/span> <span class=\"pl-smi\">limit<\/span>)\r\n    {\r\n        <span class=\"pl-k\">var<\/span> <span class=\"pl-smi\">list<\/span> <span class=\"pl-k\">=<\/span> <span class=\"pl-k\">new<\/span> <span class=\"pl-en\">List<\/span>&lt;<span class=\"pl-k\">int<\/span>&gt;();\r\n        <span class=\"pl-k\">for<\/span> (<span class=\"pl-k\">int<\/span> <span class=\"pl-smi\">i<\/span> <span class=\"pl-k\">=<\/span> <span class=\"pl-c1\">0<\/span>; <span class=\"pl-smi\">i<\/span> <span class=\"pl-k\">&lt;<\/span> <span class=\"pl-smi\">limit<\/span>; <span class=\"pl-smi\">i<\/span><span class=\"pl-k\">++<\/span>)\r\n        {\r\n            <span class=\"pl-k\">if<\/span> (<span class=\"pl-smi\">i<\/span> <span class=\"pl-k\">&gt;=<\/span> <span class=\"pl-c1\">20<\/span>)\r\n            {\r\n                <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> Dependency.Library is only referenced within<\/span>\r\n                <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> the UseDependencyApi() method,<\/span>\r\n                <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> so will only be loaded when limit &gt;= 20<\/span>\r\n                <span class=\"pl-en\">UseDependencyApi<\/span>();\r\n            }\r\n\r\n            <span class=\"pl-smi\">list<\/span>.<span class=\"pl-en\">Add<\/span>(<span class=\"pl-smi\">i<\/span>);\r\n        }\r\n        <span class=\"pl-k\">return<\/span> <span class=\"pl-smi\">list<\/span>;\r\n    }\r\n\r\n    <span class=\"pl-k\">private<\/span> <span class=\"pl-k\">static<\/span> <span class=\"pl-k\">void<\/span> <span class=\"pl-en\">UseDependencyApi<\/span>()\r\n    {\r\n        <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> Once UseDependencyApi() is called, Dependency.Library is loaded<\/span>\r\n        <span class=\"pl-smi\">DependencyApi<\/span>.<span class=\"pl-en\">Use<\/span>();\r\n    }\r\n}<\/pre>\n<\/div>\n<p>This is a good thing for .NET to do, since it minimizes the memory and filesystem reads it needs to use, making it more resource efficient.<\/p>\n<p>Unfortunately a side effect of this is that if an assembly load will fail, we won&#8217;t necessarily know when the program is first loaded, only when the code path that tries to load the assembly is run.<\/p>\n<p>It can also set up timing conditions for assembly load conflicts; if two parts of the same program will try to load different versions of the same assembly, which one is loaded depends on which code path is run first.<\/p>\n<p>For PowerShell this means that the following factors can affect an assembly load conflict:<\/p>\n<ul>\n<li>Which module was loaded first?<\/li>\n<li>Was the cmdlet\/code path that uses the dependency library run?<\/li>\n<li>Does PowerShell load a conflicting dependency at startup or only under certain code paths?<\/li>\n<\/ul>\n<h2><a id=\"user-content-quick-fixes-and-their-limitations\" class=\"anchor\" href=\"#quick-fixes-and-their-limitations\" aria-hidden=\"true\"><\/a>Quick fixes and their limitations<\/h2>\n<p>In some cases it&#8217;s possible to make small adjustments to your module and fix things with a minimum of fuss. But these solutions tend to come with caveats, so that while they may apply to your module, they won&#8217;t work for every module.<\/p>\n<h3><a id=\"user-content-change-your-dependency-version\" class=\"anchor\" href=\"#change-your-dependency-version\" aria-hidden=\"true\"><\/a>Change your dependency version<\/h3>\n<p>The simplest way to avoid dependency conflicts is to agree on a dependency. This may be possible when:<\/p>\n<ul>\n<li>Your conflict is with a direct dependency of your module and you control the version<\/li>\n<li>Your conflict is with an indirect dependency, but you can configure your direct dependencies to use a workable indirect dependency version<\/li>\n<li>You know the conflicting version and can rely on it not changing<\/li>\n<\/ul>\n<p>A good example of this last scenario is with the <code>Newtonsoft.Json<\/code> package. This is a dependency of PowerShell 6 and above, and isn&#8217;t used in Windows PowerShell, meaning a simple way to resolve versioning conflicts is to target the lowest version of <code>Newtonsoft.Json<\/code> across the PowerShell versions you wish to target.<\/p>\n<p>For example, PowerShell 6.2.6 and PowerShell 7.0.2 both currently use Newtonsoft.Json version 12.0.3. So, to create a module targeting Windows PowerShell, PowerShell 6 and PowerShell 7, you would target Newtonsoft.Json 12.0.3 as a dependency and include it in your built module. When the module is loaded in PowerShell 6 or 7, PowerShell&#8217;s own Newtonsoft.Json assembly will already be loaded, but the version will be at least the required one for your module, meaning resolution will succeed. In Windows PowerShell, the assembly will not be already present in PowerShell, and so will be loaded from your module instead.<\/p>\n<p>Generally, when targeting a concrete PowerShell package, like Microsoft.PowerShell.Sdk or System.Management.Automation, NuGet should be able to resolve the right dependency versions required. It&#8217;s only in the case of targeting both Windows PowerShell and PowerShell 6+ that dependency versioning becomes more difficult, either because of needing to target multiple frameworks, or due to targeting PowerShellStandard.Library.<\/p>\n<p>Circumstances where pinning to a common dependency version won&#8217;t work include:<\/p>\n<ul>\n<li>The conflict is with an indirect dependency, and there&#8217;s no configuration of your dependencies that can use the common version<\/li>\n<li>The other dependency version is likely to change often, meaning settling on a common version will only be a short-term fix<\/li>\n<\/ul>\n<h3><a id=\"user-content-add-an-assemblyresolve-event-handler-to-create-a-dynamic-binding-redirect\" class=\"anchor\" href=\"#add-an-assemblyresolve-event-handler-to-create-a-dynamic-binding-redirect\" aria-hidden=\"true\"><\/a>Add an AssemblyResolve event handler to create a dynamic binding redirect<\/h3>\n<p>When changing your own dependency version isn&#8217;t possible, or is too hard, another way to make your module play nicely with other dependencies is to set up a dynamic binding redirect by registering an event handler for the <code>AssemblyResolve<\/code> event.<\/p>\n<p>As with a static binding redirect, the point here to is force all consumers of a dependency to use the same actual assembly. This means we need to intercept calls to load the dependency and always redirect them to the version we want.<\/p>\n<p>This looks something like this:<\/p>\n<div class=\"highlight highlight-source-cs\">\n<pre><span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> Register the event handler as early as you can in your code.<\/span>\r\n<span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> A good option is to use the IModuleAssemblyInitializer interface<\/span>\r\n<span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> that PowerShell provides to run code early on when your module is loaded.<\/span>\r\n\r\n<span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> This class will be instantiated on module import and the OnImport() method run.<\/span>\r\n<span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> Make sure that:<\/span>\r\n<span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span>  - the class is public<\/span>\r\n<span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span>  - the class has a public, parameterless constructor<\/span>\r\n<span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span>  - the class implements IModuleAssemblyInitializer<\/span>\r\n<span class=\"pl-k\">public<\/span> <span class=\"pl-k\">class<\/span> <span class=\"pl-en\">MyModuleInitializer<\/span> : <span class=\"pl-en\">IModuleAssemblyInitializer<\/span>\r\n{\r\n    <span class=\"pl-k\">public<\/span> <span class=\"pl-k\">void<\/span> <span class=\"pl-en\">OnImport<\/span>()\r\n    {\r\n        <span class=\"pl-smi\">AppDomain<\/span>.<span class=\"pl-smi\">CurrentDomain<\/span>.<span class=\"pl-smi\">AssemblyResolve<\/span> <span class=\"pl-k\">+=<\/span> <span class=\"pl-smi\">DependencyResolution<\/span>.<span class=\"pl-smi\">ResolveNewtonsoftJson<\/span>;\r\n    }\r\n}\r\n\r\n<span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> Clean up the event handler when the the module is removed<\/span>\r\n<span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> to prevent memory leaks.<\/span>\r\n<span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span><\/span>\r\n<span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> Like IModuleAssemblyInitializer, IModuleAssemblyCleanup allows<\/span>\r\n<span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> you to register code to run when a module is removed (with Remove-Module).<\/span>\r\n<span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> Make sure it is also public with a public parameterless contructor<\/span>\r\n<span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> and implements IModuleAssemblyCleanup.<\/span>\r\n<span class=\"pl-k\">public<\/span> <span class=\"pl-k\">class<\/span> <span class=\"pl-en\">MyModuleCleanup<\/span> : <span class=\"pl-en\">IModuleAssemblyCleanup<\/span>\r\n{\r\n    <span class=\"pl-k\">public<\/span> <span class=\"pl-k\">void<\/span> <span class=\"pl-en\">OnRemove<\/span>()\r\n    {\r\n        <span class=\"pl-smi\">AppDomain<\/span>.<span class=\"pl-smi\">CurrentDomain<\/span>.<span class=\"pl-smi\">AssemblyResolve<\/span> <span class=\"pl-k\">-=<\/span> <span class=\"pl-smi\">DependencyResolution<\/span>.<span class=\"pl-smi\">ResolveNewtonsoftJson<\/span>;\r\n    }\r\n}\r\n\r\n<span class=\"pl-k\">internal<\/span> <span class=\"pl-k\">static<\/span> <span class=\"pl-k\">class<\/span> <span class=\"pl-en\">DependencyResolution<\/span>\r\n{\r\n    <span class=\"pl-k\">private<\/span> <span class=\"pl-k\">static<\/span> <span class=\"pl-k\">readonly<\/span> <span class=\"pl-k\">string<\/span> <span class=\"pl-smi\">s_modulePath<\/span> <span class=\"pl-k\">=<\/span> <span class=\"pl-smi\">Path<\/span>.<span class=\"pl-en\">GetDirectoryName<\/span>(\r\n        <span class=\"pl-smi\">Assembly<\/span>.<span class=\"pl-en\">GetExecutingAssembly<\/span>().<span class=\"pl-smi\">Location<\/span>);\r\n\r\n    <span class=\"pl-k\">public<\/span> <span class=\"pl-k\">static<\/span> <span class=\"pl-en\">Assembly<\/span> <span class=\"pl-en\">ResolveNewtonsoftJson<\/span>(<span class=\"pl-k\">object<\/span> <span class=\"pl-smi\">sender<\/span>, <span class=\"pl-en\">ResolveEventArgs<\/span> <span class=\"pl-smi\">args<\/span>)\r\n    {\r\n        <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> Parse the assembly name<\/span>\r\n        <span class=\"pl-k\">var<\/span> <span class=\"pl-smi\">assemblyName<\/span> <span class=\"pl-k\">=<\/span> <span class=\"pl-k\">new<\/span> <span class=\"pl-en\">AssemblyName<\/span>(<span class=\"pl-smi\">args<\/span>.<span class=\"pl-smi\">Name<\/span>);\r\n\r\n        <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> We only want to handle the dependency we care about.<\/span>\r\n        <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> In this example it's Newtonsoft.Json.<\/span>\r\n        <span class=\"pl-k\">if<\/span> (<span class=\"pl-k\">!<\/span><span class=\"pl-smi\">assemblyName<\/span>.<span class=\"pl-smi\">Name<\/span>.<span class=\"pl-en\">Equals<\/span>(<span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span>Newtonsoft.Json<span class=\"pl-pds\">\"<\/span><\/span>))\r\n        {\r\n            <span class=\"pl-k\">return<\/span> <span class=\"pl-c1\">null<\/span>;\r\n        }\r\n\r\n        <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> Generally the version of the dependency you want to load is the higher one,<\/span>\r\n        <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> since it's the most likely to be compatible with all dependent assemblies.<\/span>\r\n        <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> The logic here assumes our module always has the version we want to load.<\/span>\r\n        <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> Also note the use of Assembly.LoadFrom() here rather than Assembly.LoadFile().<\/span>\r\n        <span class=\"pl-k\">return<\/span> <span class=\"pl-smi\">Assembly<\/span>.<span class=\"pl-en\">LoadFrom<\/span>(<span class=\"pl-smi\">Path<\/span>.<span class=\"pl-en\">Combine<\/span>(<span class=\"pl-smi\">s_modulePath<\/span>, <span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span>Newtonsoft.Json.dll<span class=\"pl-pds\">\"<\/span><\/span>));\r\n    }\r\n}<\/pre>\n<\/div>\n<p>Note that you can technically register a scriptblock within PowerShell to handle an <code>AssemblyResolve<\/code> event, but:<\/p>\n<ul>\n<li>An <code>AssemblyResolve<\/code> event may be triggered on any thread, something that PowerShell will be unable to handle.<\/li>\n<li>Even when events are handled on the right thread, PowerShell&#8217;s scoping concepts mean that the event handler scriptblock must be written carefully to not depend on variables defined outside it.<\/li>\n<\/ul>\n<p>There are situations where redirecting to a common version will not work:<\/p>\n<ul>\n<li>When the dependency has made breaking changes between versions, or when dependent code relies on a functionality otherwise not available in the version you redirect to<\/li>\n<li>When your module isn&#8217;t loaded before the conflicting dependency. If you register an <code>AssemblyResolve<\/code> event handler after the dependency has been loaded, it will never be fired. If you&#8217;re trying to prevent dependency conflicts with another module, this may be an issue, since the other module may be loaded first.<\/li>\n<\/ul>\n<h3><a id=\"user-content-use-the-dependency-out-of-process\" class=\"anchor\" href=\"#use-the-dependency-out-of-process\" aria-hidden=\"true\"><\/a>Use the dependency out of process<\/h3>\n<p>This solution is more for module users than module authors, but is possible a solution to use when confronted with a module that won&#8217;t work due to an existing dependency conflict.<\/p>\n<p>Dependency conflicts occur because two versions of the same assembly are loaded <em>into the same .NET process<\/em>. So a simple solution is to load them into different processes, as long as you can still use the functionality from both together.<\/p>\n<p>In PowerShell, there are several ways to achieve this.<\/p>\n<h4><a id=\"user-content-invoke-powershell-as-a-subprocess\" class=\"anchor\" href=\"#invoke-powershell-as-a-subprocess\" aria-hidden=\"true\"><\/a>Invoke PowerShell as a subprocess<\/h4>\n<p>A simple way to run a PowerShell command out of the current process is to just start a new PowerShell process directly with the command call:<\/p>\n<div class=\"highlight highlight-source-powershell\">\n<pre>pwsh <span class=\"pl-k\">-<\/span>c <span class=\"pl-s\"><span class=\"pl-pds\">'<\/span>Invoke-ConflictingCommand<span class=\"pl-pds\">'<\/span><\/span><\/pre>\n<\/div>\n<p>The main limitation here is that restructuring the result can be trickier or more error prone than other options.<\/p>\n<h4><a id=\"user-content-the-powershell-job-system\" class=\"anchor\" href=\"#the-powershell-job-system\" aria-hidden=\"true\"><\/a>The PowerShell job system<\/h4>\n<p>The PowerShell job system also runs commands out of process, by sending commands to a new PowerShell process and returning the results:<\/p>\n<div class=\"highlight highlight-source-powershell\">\n<pre><span class=\"pl-smi\">$result<\/span> <span class=\"pl-k\">=<\/span> <span class=\"pl-c1\">Start-Job<\/span> { <span class=\"pl-c1\">Invoke-ConflictingCommand<\/span> } <span class=\"pl-k\">|<\/span> <span class=\"pl-c1\">Receive-Job<\/span> <span class=\"pl-k\">-<\/span>Wait<\/pre>\n<\/div>\n<p>In this case, you just need to be sure that any variables and state are passed in correctly.<\/p>\n<p>The job system can also be slightly cumbersome when running small commands.<\/p>\n<h4><a id=\"user-content-powershell-remoting\" class=\"anchor\" href=\"#powershell-remoting\" aria-hidden=\"true\"><\/a>PowerShell remoting<\/h4>\n<p>When it&#8217;s available, PowerShell remoting can be a very ergonomic way to run commands out of process. With remoting you can create a fresh PSSession in a new process, call its commands over PowerShell remoting and then use the results locally with, for example, the other module with the conflicting dependencies.<\/p>\n<p>An example might look like this:<\/p>\n<div class=\"highlight highlight-source-powershell\">\n<pre><span class=\"pl-c\"><span class=\"pl-c\">#<\/span> Create a local PowerShell session<\/span>\r\n<span class=\"pl-c\"><span class=\"pl-c\">#<\/span> where the module with conflicting assemblies will be loaded<\/span>\r\n<span class=\"pl-smi\">$s<\/span> <span class=\"pl-k\">=<\/span> <span class=\"pl-c1\">New-PSSession<\/span>\r\n\r\n<span class=\"pl-c\"><span class=\"pl-c\">#<\/span> Import the module with the conflicting dependency via remoting,<\/span>\r\n<span class=\"pl-c\"><span class=\"pl-c\">#<\/span> exposing the commands locally<\/span>\r\n<span class=\"pl-c1\">Import-Module<\/span> <span class=\"pl-k\">-<\/span>PSSession <span class=\"pl-smi\">$s<\/span> <span class=\"pl-k\">-<\/span>Name ConflictingModule\r\n\r\n<span class=\"pl-c\"><span class=\"pl-c\">#<\/span> Run a command from the module with the conflicting dependencies<\/span>\r\n<span class=\"pl-c1\">Invoke-ConflictingCommand<\/span><\/pre>\n<\/div>\n<h4><a id=\"user-content-implicit-remoting-to-windows-powershell\" class=\"anchor\" href=\"#implicit-remoting-to-windows-powershell\" aria-hidden=\"true\"><\/a>Implicit remoting to Windows PowerShell<\/h4>\n<p>Another option in PowerShell 7 is to use the <code>-UseWindowsPowerShell<\/code> flag on <code>Import-Module<\/code>. This will import the module through a local remoting session into Windows PowerShell:<\/p>\n<div class=\"highlight highlight-source-powershell\">\n<pre><span class=\"pl-c1\">Import-Module<\/span> <span class=\"pl-k\">-<\/span>Name ConflictingModule <span class=\"pl-k\">-<\/span>UseWindowsPowerShell<\/pre>\n<\/div>\n<p>Be aware of course that modules may not work with or work differently with Windows PowerShell.<\/p>\n<h4><a id=\"user-content-when-not-to-use-out-of-process-invocation\" class=\"anchor\" href=\"#when-not-to-use-out-of-process-invocation\" aria-hidden=\"true\"><\/a>When not to use out-of-process invocation<\/h4>\n<p>As a module author, out-of-process command invocation is harder to bake into a module, and may have edge cases that cause issues. In particular, remoting and jobs may not be available in all environments where your module needs to work. However, the general principle of moving the implementation out of process and allowing the PowerShell module to be a thinner client, may still be applicable.<\/p>\n<p>Of course, for module users too there will be cases where out-of-process invocation won&#8217;t work:<\/p>\n<ul>\n<li>When PowerShell remoting is unavailable, for example if you don&#8217;t have privileges to use it or it is not enabled.<\/li>\n<li>When a particular .NET type is needed from output, for example to run methods on, or as input to another command. Commands run over PowerShell remoting emit deserialized output, meaning they return <code>psobject<\/code>s rather than strongly-typed .NET objects. This means that method calls and strongly typed APIs will not work with the output of commands imported over remoting.<\/li>\n<\/ul>\n<h2><a id=\"user-content-more-robust-solutions\" class=\"anchor\" href=\"#more-robust-solutions\" aria-hidden=\"true\"><\/a>More robust solutions<\/h2>\n<p>The solutions above have all had the issue that there are scenarios and modules for which they won&#8217;t work. However, they also have the virtue of being relatively simple to implement correctly.<\/p>\n<p>These next solutions we discuss are generally more robust, but also take somewhat more work to implement correctly and can introduce subtle bugs if not written carefully.<\/p>\n<h3><a id=\"user-content-loading-through-net-core-assembly-load-contexts\" class=\"anchor\" href=\"#loading-through-net-core-assembly-load-contexts\" aria-hidden=\"true\"><\/a>Loading through .NET Core Assembly Load Contexts<\/h3>\n<p><a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.runtime.loader.assemblyloadcontext\" rel=\"nofollow\">Assembly Load Contexts<\/a> (ALCs) as an implementable API were introduced in .NET Core 1.0 to specifically address the need to load multiple versions of the same assembly into the same runtime.<\/p>\n<p>Within .NET Core and .NET 5, they offer what is far and away the most robust solution to the problem of needing to load conflicting versions of an assembly. However, custom ALCs are not available in .NET Framework. This means that this solution will only work in PowerShell 6 and above.<\/p>\n<p>Currently, the best example of using an ALC for dependency isolation in PowerShell is in PowerShell Editor Services (the language server for the PowerShell extension for Visual Studio Code), where <a href=\"https:\/\/github.com\/PowerShell\/PowerShellEditorServices\/blob\/master\/src\/PowerShellEditorServices.Hosting\/Internal\/PsesLoadContext.cs\">an ALC is used<\/a> to prevent PowerShell Editor Services&#8217; own dependencies from clashing with those in PowerShell modules.<\/p>\n<p>Implementing module dependency isolation with an ALC is conceptually difficult, but we will work through a minimal example here.<\/p>\n<p>Imagine we have a simple module, only intended to work in PowerShell 7, whose source is laid out like this:<\/p>\n<pre lang=\"none\"><code>+ AlcModule.psd1\r\n+ src\/\r\n    + TestAlcModuleCommand.cs\r\n    + AlcModule.csproj\r\n<\/code><\/pre>\n<p>The cmdlet implementation looks like this:<\/p>\n<div class=\"highlight highlight-source-cs\">\n<pre><span class=\"pl-k\">using<\/span> <span class=\"pl-en\">Shared<\/span>.<span class=\"pl-en\">Dependency<\/span>;\r\n\r\n<span class=\"pl-k\">namespace<\/span> <span class=\"pl-en\">AlcModule<\/span>\r\n{\r\n    [<span class=\"pl-en\">Cmdlet<\/span>(<span class=\"pl-smi\">VerbsDiagnostic<\/span>.<span class=\"pl-smi\">Test<\/span>, <span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span>AlcModule<span class=\"pl-pds\">\"<\/span><\/span>)]\r\n    <span class=\"pl-k\">public<\/span> <span class=\"pl-k\">class<\/span> <span class=\"pl-en\">TestAlcModuleCommand<\/span> : <span class=\"pl-en\">Cmdlet<\/span>\r\n    {\r\n        <span class=\"pl-k\">protected<\/span> <span class=\"pl-k\">override<\/span> <span class=\"pl-k\">void<\/span> <span class=\"pl-en\">EndProcessing<\/span>()\r\n        {\r\n            <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> Here's where our dependency gets used<\/span>\r\n            <span class=\"pl-smi\">Dependency<\/span>.<span class=\"pl-en\">Use<\/span>();\r\n            <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> Something trivial to make our cmdlet do *something*<\/span>\r\n            <span class=\"pl-en\">WriteObject<\/span>(<span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span>done!<span class=\"pl-pds\">\"<\/span><\/span>);\r\n        }\r\n    }\r\n}<\/pre>\n<\/div>\n<p>The (heavily simplified) manifest, looks like this:<\/p>\n<div class=\"highlight highlight-source-powershell\">\n<pre><span class=\"pl-k\">@<\/span>{\r\n    <span class=\"pl-smi\">Author<\/span> <span class=\"pl-k\">=<\/span> <span class=\"pl-s\"><span class=\"pl-pds\">'<\/span>Me<span class=\"pl-pds\">'<\/span><\/span>\r\n    <span class=\"pl-smi\">ModuleVersion<\/span> <span class=\"pl-k\">=<\/span> <span class=\"pl-s\"><span class=\"pl-pds\">'<\/span>0.0.1<span class=\"pl-pds\">'<\/span><\/span>\r\n    <span class=\"pl-smi\">RootModule<\/span> <span class=\"pl-k\">=<\/span> <span class=\"pl-s\"><span class=\"pl-pds\">'<\/span>AlcModule.dll<span class=\"pl-pds\">'<\/span><\/span>\r\n    <span class=\"pl-smi\">CmdletsToExport<\/span> <span class=\"pl-k\">=<\/span> <span class=\"pl-k\">@<\/span>(<span class=\"pl-s\"><span class=\"pl-pds\">'<\/span>Test-AlcModule<span class=\"pl-pds\">'<\/span><\/span>)\r\n    <span class=\"pl-smi\">PowerShellVersion<\/span> <span class=\"pl-k\">=<\/span> <span class=\"pl-s\"><span class=\"pl-pds\">'<\/span>7.0<span class=\"pl-pds\">'<\/span><\/span>\r\n}<\/pre>\n<\/div>\n<p>And the csproj looks like this:<\/p>\n<div class=\"highlight highlight-text-xml\">\n<pre>&lt;<span class=\"pl-ent\">Project<\/span> <span class=\"pl-e\">Sdk<\/span>=<span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span>Microsoft.NET.Sdk<span class=\"pl-pds\">\"<\/span><\/span>&gt;\r\n  &lt;<span class=\"pl-ent\">PropertyGroup<\/span>&gt;\r\n    &lt;<span class=\"pl-ent\">TargetFramework<\/span>&gt;netcoreapp3.1&lt;\/<span class=\"pl-ent\">TargetFramework<\/span>&gt;\r\n  &lt;\/<span class=\"pl-ent\">PropertyGroup<\/span>&gt;\r\n  &lt;<span class=\"pl-ent\">ItemGroup<\/span>&gt;\r\n    &lt;<span class=\"pl-ent\">PackageReference<\/span> <span class=\"pl-e\">Include<\/span>=<span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span>Shared.Dependency<span class=\"pl-pds\">\"<\/span><\/span> <span class=\"pl-e\">Version<\/span>=<span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span>1.0.0<span class=\"pl-pds\">\"<\/span><\/span> \/&gt;\r\n    &lt;<span class=\"pl-ent\">PackageReference<\/span> <span class=\"pl-e\">Include<\/span>=<span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span>Microsoft.PowerShell.Sdk<span class=\"pl-pds\">\"<\/span><\/span> <span class=\"pl-e\">Version<\/span>=<span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span>7.0.1<span class=\"pl-pds\">\"<\/span><\/span> <span class=\"pl-e\">PrivateAssets<\/span>=<span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span>all<span class=\"pl-pds\">\"<\/span><\/span> \/&gt;\r\n  &lt;\/<span class=\"pl-ent\">ItemGroup<\/span>&gt;\r\n&lt;\/<span class=\"pl-ent\">Project<\/span>&gt;<\/pre>\n<\/div>\n<p>When we build this module, the generated output has the following layout:<\/p>\n<pre lang=\"none\"><code>AlcModule\/\r\n  + AlcModule.psd1\r\n  + AlcModule.dll\r\n  + Shared.Dependency.dll\r\n<\/code><\/pre>\n<p>In this example, our problem lies in the Shared.Dependency.dll assembly, which is our imaginary conflicting dependency. This is the dependency that we need to put behind an ALC so that we can use the module specific one.<\/p>\n<p>We seek to re-engineer the module so that:<\/p>\n<ul>\n<li>Module dependencies are only ever loaded into our custom ALC, and not into PowerShell&#8217;s ALC, so there can be no conflict. Moreover, as we add more dependencies to our project, we don&#8217;t want to continuously add more code to keep loading working; instead we want reusable, generic dependency resolution logic.<\/li>\n<li>Loading the module will still work as normal in PowerShell, meaning cmdlets and other types the PowerShell module system needs to see will be defined within PowerShell&#8217;s own ALC.<\/li>\n<\/ul>\n<p>To mediate these two requirements, we must break our module up into two assemblies:<\/p>\n<ul>\n<li>A cmdlets assembly, AlcModule.Cmdlets.dll, which will contain definitions of all the types that PowerShell&#8217;s module system needs to load the module correctly. Namely any implementations of the <code>Cmdlet<\/code> base class and our <code>IModuleAssemblyInitializer<\/code>-implementing class, which will set up the event handler for <code>AssemblyLoadContext.Default.Resolving<\/code> to properly load AlcModule.Engine.dll through our custom ALC. Any types that are meant to be publicly exposed to PowerShell must also be defined here, since PowerShell 7 deliberately hides types defined in assemblies loaded in other ALCs. Finally, this assembly will also need to be where our custom ALC definition lives. Beyond that, as little code should live in this as possible.<\/li>\n<li>An engine assembly, AlcModule.Engine.dll, which handles all the actual implementation of the module. Types from this will still be available in the PowerShell ALC, but it will initially be loaded through our custom ALC, and its dependencies will only ever be loaded into the custom ALC. Effectively, this becomes a &#8220;bridge&#8221; between the two ALCs.<\/li>\n<\/ul>\n<p>Using this bridge concept, our new assembly situation effectively will look like this:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/powershell\/wp-content\/uploads\/sites\/30\/2020\/06\/alc-diagram.jpg\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"><img decoding=\"async\" style=\"max-width: 100%;\" src=\"https:\/\/devblogs.microsoft.com\/powershell\/wp-content\/uploads\/sites\/30\/2020\/06\/alc-diagram.jpg\" alt=\"Diagram representing AlcModule.Engine.dll bridging the two ALCs\" data-canonical-src=\"https:\/\/devblogs.microsoft.com\/powershell\/wp-content\/uploads\/sites\/30\/2020\/06\/alc-diagram.jpg\" \/><\/a><\/p>\n<p>To make sure the default ALC&#8217;s dependency probing logic will not resolve the dependencies to be loaded into the custom ALC, we will need to separate these two parts of the module in different directories. So our new module layout will look like this:<\/p>\n<pre lang=\"none\"><code>AlcModule\/\r\n  AlcModule.Cmdlets.dll\r\n  AlcModule.psd1\r\n  Dependencies\/\r\n  | + AlcModule.Engine.dll\r\n  | + Shared.Dependency.dll\r\n<\/code><\/pre>\n<p>To see how our implementation now changes, we&#8217;ll start with the implementation of AlcModule.Engine.dll:<\/p>\n<div class=\"highlight highlight-source-cs\">\n<pre><span class=\"pl-k\">using<\/span> <span class=\"pl-en\">Shared<\/span>.<span class=\"pl-en\">Dependency<\/span>;\r\n\r\n<span class=\"pl-k\">namespace<\/span> <span class=\"pl-en\">AlcModule<\/span>.<span class=\"pl-en\">Engine<\/span>\r\n{\r\n    <span class=\"pl-k\">public<\/span> <span class=\"pl-k\">class<\/span> <span class=\"pl-en\">AlcEngine<\/span>\r\n    {\r\n        <span class=\"pl-k\">public<\/span> <span class=\"pl-k\">static<\/span> <span class=\"pl-k\">void<\/span> <span class=\"pl-en\">Use<\/span>()\r\n        {\r\n            <span class=\"pl-smi\">Dependency<\/span>.<span class=\"pl-en\">Use<\/span>();\r\n        }\r\n    }\r\n}<\/pre>\n<\/div>\n<p>This is a fairly straightforward container for the dependency, Shared.Dependency.dll, but you should think of it as the .NET API for your functionality, which the cmdlets living in the other assembly will wrap for PowerShell.<\/p>\n<p>The cmdlet in AlcModule.Cmdlets.dll now looks like this:<\/p>\n<div class=\"highlight highlight-source-cs\">\n<pre><span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> Reference our module's Engine implementation here<\/span>\r\n<span class=\"pl-k\">using<\/span> <span class=\"pl-en\">AlcModule<\/span>.<span class=\"pl-en\">Engine<\/span>;\r\n\r\n<span class=\"pl-k\">namespace<\/span> <span class=\"pl-en\">AlcModule<\/span>.<span class=\"pl-en\">Cmdlets<\/span>\r\n{\r\n    [<span class=\"pl-en\">Cmdlet<\/span>(<span class=\"pl-smi\">VerbsDiagnostic<\/span>.<span class=\"pl-smi\">Test<\/span>, <span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span>AlcModule<span class=\"pl-pds\">\"<\/span><\/span>)]\r\n    <span class=\"pl-k\">public<\/span> <span class=\"pl-k\">class<\/span> <span class=\"pl-en\">TestAlcModuleCommand<\/span> : <span class=\"pl-en\">Cmdlet<\/span>\r\n    {\r\n        <span class=\"pl-k\">protected<\/span> <span class=\"pl-k\">override<\/span> <span class=\"pl-k\">void<\/span> <span class=\"pl-en\">EndProcessing<\/span>()\r\n        {\r\n            <span class=\"pl-smi\">AlcEngine<\/span>.<span class=\"pl-en\">Use<\/span>();\r\n            <span class=\"pl-en\">WriteObject<\/span>(<span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span>done!<span class=\"pl-pds\">\"<\/span><\/span>);\r\n        }\r\n    }\r\n}<\/pre>\n<\/div>\n<p>At this point, if we were to load AlcModule and run Test-AlcModule, we would hit a <code>FileNotFoundException<\/code> when the default ALC tries to load Alc.Engine.dll to run <code>EndProcessing()<\/code>. This is good, since it means the default ALC can&#8217;t find the dependencies we want to hide.<\/p>\n<p>So now we need to add the magic to AlcModule.Cmdlets.dll that helps it know how to resolve AlcModule.Engine.dll. First we must define our custom ALC that will resolve assemblies from our module&#8217;s Dependencies directory:<\/p>\n<div class=\"highlight highlight-source-cs\">\n<pre><span class=\"pl-k\">namespace<\/span> <span class=\"pl-en\">AlcModule<\/span>.<span class=\"pl-en\">Cmdlets<\/span>\r\n{\r\n    <span class=\"pl-k\">internal<\/span> <span class=\"pl-k\">class<\/span> <span class=\"pl-en\">AlcModuleAssemblyLoadContext<\/span> : <span class=\"pl-en\">AssemblyLoadContext<\/span>\r\n    {\r\n        <span class=\"pl-k\">private<\/span> <span class=\"pl-k\">readonly<\/span> <span class=\"pl-k\">string<\/span> <span class=\"pl-smi\">_dependencyDirPath<\/span>;\r\n\r\n        <span class=\"pl-k\">public<\/span> <span class=\"pl-en\">AlcModuleAssemblyLoadContext<\/span>(<span class=\"pl-k\">string<\/span> <span class=\"pl-smi\">dependencyDirPath<\/span>)\r\n        {\r\n            <span class=\"pl-smi\">_depdendencyDirPath<\/span> <span class=\"pl-k\">=<\/span> <span class=\"pl-smi\">dependencyDirPath<\/span>;\r\n        }\r\n\r\n        <span class=\"pl-k\">protected<\/span> <span class=\"pl-k\">override<\/span> <span class=\"pl-en\">Assembly<\/span> <span class=\"pl-en\">Load<\/span>(<span class=\"pl-en\">AssemblyName<\/span> <span class=\"pl-smi\">assemblyName<\/span>)\r\n        {\r\n            <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> We do the simple logic here of<\/span>\r\n            <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> looking for an assembly of the given name<\/span>\r\n            <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> in the configured dependency directory<\/span>\r\n            <span class=\"pl-k\">string<\/span> <span class=\"pl-smi\">assemblyPath<\/span> <span class=\"pl-k\">=<\/span> <span class=\"pl-smi\">Path<\/span>.<span class=\"pl-en\">Combine<\/span>(\r\n                <span class=\"pl-smi\">s_dependencyDirPath<\/span>,\r\n                <span class=\"pl-s\"><span class=\"pl-pds\">$\"<\/span>{<span class=\"pl-smi\">assemblyName<\/span>.<span class=\"pl-smi\">Name<\/span>}.dll<span class=\"pl-pds\">\"<\/span><\/span>);\r\n\r\n            <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> The ALC must use inherited methods to load assemblies<\/span>\r\n            <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> Assembly.Load*() won't work here<\/span>\r\n            <span class=\"pl-k\">return<\/span> <span class=\"pl-en\">LoadFromAssemblyPath<\/span>(<span class=\"pl-smi\">assemblyPath<\/span>);\r\n        }\r\n    }\r\n}<\/pre>\n<\/div>\n<p>Then, we need to hook our custom ALC up to the default ALC&#8217;s <code>Resolving<\/code> event (which is the ALC version of the <code>AssemblyResolve<\/code> event on Application Domains) that will be fired when <code>EndProcessing()<\/code> is called to try and find AlcModule.Engine.dll:<\/p>\n<div class=\"highlight highlight-source-cs\">\n<pre><span class=\"pl-k\">namespace<\/span> <span class=\"pl-en\">AlcModule<\/span>.<span class=\"pl-en\">Cmdlets<\/span>\r\n{\r\n    <span class=\"pl-k\">public<\/span> <span class=\"pl-k\">class<\/span> <span class=\"pl-en\">AlcModuleResolveEventHandler<\/span> : <span class=\"pl-en\">IModuleAssemblyInitializer<\/span>, <span class=\"pl-en\">IModuleAssemblyCleanup<\/span>\r\n    {\r\n        <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> Get the path of the dependency directory.<\/span>\r\n        <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> In this case we find it relative to the AlcModule.Cmdlets.dll location<\/span>\r\n        <span class=\"pl-k\">private<\/span> <span class=\"pl-k\">static<\/span> <span class=\"pl-k\">readonly<\/span> <span class=\"pl-k\">string<\/span> <span class=\"pl-smi\">s_dependencyDirPath<\/span> <span class=\"pl-k\">=<\/span> <span class=\"pl-smi\">Path<\/span>.<span class=\"pl-en\">GetFullPath<\/span>(\r\n            <span class=\"pl-smi\">Path<\/span>.<span class=\"pl-en\">Combine<\/span>(\r\n                <span class=\"pl-smi\">Path<\/span>.<span class=\"pl-en\">GetDirectoryName<\/span>(<span class=\"pl-smi\">Assembly<\/span>.<span class=\"pl-en\">GetExecutingAssembly<\/span>().<span class=\"pl-smi\">Location<\/span>),\r\n                <span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span>Dependencies<span class=\"pl-pds\">\"<\/span><\/span>));\r\n\r\n        <span class=\"pl-k\">private<\/span> <span class=\"pl-k\">static<\/span> <span class=\"pl-k\">readonly<\/span> <span class=\"pl-en\">AlcModuleAssemblyLoadContext<\/span> <span class=\"pl-smi\">s_dependencyAlc<\/span> <span class=\"pl-k\">=<\/span> <span class=\"pl-k\">new<\/span> <span class=\"pl-en\">AlcModuleAssemblyLoadContext<\/span>(<span class=\"pl-smi\">s_dependencyDirPath<\/span>);\r\n\r\n        <span class=\"pl-k\">public<\/span> <span class=\"pl-k\">void<\/span> <span class=\"pl-en\">OnImport<\/span>()\r\n        {\r\n            <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> Add the Resolving event handler here<\/span>\r\n            <span class=\"pl-smi\">AssemblyLoadContext<\/span>.<span class=\"pl-smi\">Default<\/span>.<span class=\"pl-smi\">Resolving<\/span> <span class=\"pl-k\">+=<\/span> <span class=\"pl-smi\">ResolveAlcEngine<\/span>;\r\n        }\r\n\r\n        <span class=\"pl-k\">public<\/span> <span class=\"pl-k\">void<\/span> <span class=\"pl-en\">OnRemove<\/span>()\r\n        {\r\n          <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> Remove the Resolving event handler here<\/span>\r\n          <span class=\"pl-smi\">AssemblyLoadContext<\/span>.<span class=\"pl-smi\">Default<\/span>.<span class=\"pl-smi\">Resolving<\/span> <span class=\"pl-k\">-=<\/span> <span class=\"pl-smi\">ResolveAlcEngine<\/span>;\r\n        }\r\n\r\n        <span class=\"pl-k\">private<\/span> <span class=\"pl-k\">static<\/span> <span class=\"pl-en\">Assembly<\/span> <span class=\"pl-en\">ResolveAlcEngine<\/span>(\r\n            <span class=\"pl-en\">AssemblyLoadContext<\/span> <span class=\"pl-smi\">defaultAlc<\/span>,\r\n            <span class=\"pl-en\">AssemblyName<\/span> <span class=\"pl-smi\">assemblyToResolve<\/span>)\r\n        {\r\n            <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> We only want to resolve the Alc.Engine.dll assembly here.<\/span>\r\n            <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> Because this will be loaded into the custom ALC,<\/span>\r\n            <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> all of *its* dependencies will be resolved<\/span>\r\n            <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> by the logic we defined for that ALC's implementation.<\/span>\r\n            <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span><\/span>\r\n            <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> Note that we are safe in our assumption that the name is enough<\/span>\r\n            <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> to distinguish our assembly here,<\/span>\r\n            <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> since it's unique to our module.<\/span>\r\n            <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> There should be no other AlcModule.Engine.dll on the system.<\/span>\r\n            <span class=\"pl-k\">if<\/span> (<span class=\"pl-k\">!<\/span><span class=\"pl-smi\">assemblyToResolve<\/span>.<span class=\"pl-smi\">Name<\/span>.<span class=\"pl-en\">Equals<\/span>(<span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span>AlcModule.Engine<span class=\"pl-pds\">\"<\/span><\/span>))\r\n            {\r\n                <span class=\"pl-k\">return<\/span> <span class=\"pl-c1\">null<\/span>;\r\n            }\r\n\r\n            <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> Allow our ALC to handle the directory discovery concept<\/span>\r\n            <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span><\/span>\r\n            <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> This is where Alc.Engine.dll is loaded into our custom ALC<\/span>\r\n            <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> and then passed through into PowerShell's ALC,<\/span>\r\n            <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> becoming the bridge between both<\/span>\r\n            <span class=\"pl-k\">return<\/span> <span class=\"pl-smi\">s_dependencyAlc<\/span>.<span class=\"pl-en\">LoadFromAssemblyName<\/span>(<span class=\"pl-smi\">assemblyToResolve<\/span>);\r\n        }\r\n    }\r\n}<\/pre>\n<\/div>\n<p>With the new implementation laid out, we can now take a look at the sequence of calls that occurs when the module is loaded and <code>Test-AlcModule<\/code> is run:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/powershell\/wp-content\/uploads\/sites\/30\/2020\/06\/alc-sequence.png\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"><img decoding=\"async\" style=\"max-width: 100%;\" src=\"https:\/\/devblogs.microsoft.com\/powershell\/wp-content\/uploads\/sites\/30\/2020\/06\/alc-sequence.png\" alt=\"Sequence diagram of calls using the custom ALC to load dependencies\" data-canonical-src=\"https:\/\/devblogs.microsoft.com\/powershell\/wp-content\/uploads\/sites\/30\/2020\/06\/alc-sequence.png\" \/><\/a><\/p>\n<p>Some points of interest are:<\/p>\n<ul>\n<li>The <code>IModuleAssemblyInitializer<\/code> is run first when the module loads and sets the <code>Resolving<\/code> event<\/li>\n<li>We don&#8217;t even load the dependencies until <code>Test-AlcModule<\/code> is run and its <code>EndProcessing()<\/code> method is called<\/li>\n<li>When <code>EndProcessing()<\/code> is called, the default ALC does not find <code>AlcModule.Engine.dll<\/code> and fires the <code>Resolving<\/code> event<\/li>\n<li>Our event handler here is what does the magic of hooking up the custom ALC to the default ALC, and only loads AlcModule.Engine.dll<\/li>\n<li>Then, within AlcModule.Engine.dll, when <code>AlcEngine.Use()<\/code> is called, the custom ALC again kicks in to resolve Shared.Dependency.dll. Specifically it always loads our Shared.Dependency.dll, since it never conflicts with anything in the default ALC and only looks in our <code>Dependencies<\/code> directory.<\/li>\n<\/ul>\n<p>Assembling the implementation, our new source code layout looks like this:<\/p>\n<pre lang=\"none\"><code>+ AlcModule.psd1\r\n+ src\/\r\n  + AlcModule.Cmdlets\/\r\n  | + AlcModule.Cmdlets.csproj\r\n  | + TestAlcModuleCommand.cs\r\n  | + AlcModuleAssemblyLoadContext.cs\r\n  | + AlcModuleInitializer.cs\r\n  |\r\n  + AlcModule.Engine\/\r\n  | + AlcModule.Engine.csproj\r\n  | + AlcEngine.cs\r\n<\/code><\/pre>\n<p>AlcModule.Cmdlets.csproj looks like:<\/p>\n<div class=\"highlight highlight-text-xml\">\n<pre>&lt;<span class=\"pl-ent\">Project<\/span> <span class=\"pl-e\">Sdk<\/span>=<span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span>Microsoft.NET.Sdk<span class=\"pl-pds\">\"<\/span><\/span>&gt;\r\n  &lt;<span class=\"pl-ent\">PropertyGroup<\/span>&gt;\r\n    &lt;<span class=\"pl-ent\">TargetFramework<\/span>&gt;netcoreapp3.1&lt;\/<span class=\"pl-ent\">TargetFramework<\/span>&gt;\r\n  &lt;\/<span class=\"pl-ent\">PropertyGroup<\/span>&gt;\r\n  &lt;<span class=\"pl-ent\">ItemGroup<\/span>&gt;\r\n    &lt;<span class=\"pl-ent\">ProjectReference<\/span> <span class=\"pl-e\">Include<\/span>=<span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span>..\\AlcModule.Engine\\AlcModule.Engine.csproj<span class=\"pl-pds\">\"<\/span><\/span> \/&gt;\r\n    &lt;<span class=\"pl-ent\">PackageReference<\/span> <span class=\"pl-e\">Include<\/span>=<span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span>Microsoft.PowerShell.Sdk<span class=\"pl-pds\">\"<\/span><\/span> <span class=\"pl-e\">Version<\/span>=<span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span>7.0.1<span class=\"pl-pds\">\"<\/span><\/span> <span class=\"pl-e\">PrivateAssets<\/span>=<span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span>all<span class=\"pl-pds\">\"<\/span><\/span> \/&gt;\r\n  &lt;\/<span class=\"pl-ent\">ItemGroup<\/span>&gt;\r\n&lt;\/<span class=\"pl-ent\">Project<\/span>&gt;<\/pre>\n<\/div>\n<p>AlcModule.Engine.csproj looks like this:<\/p>\n<div class=\"highlight highlight-text-xml\">\n<pre>&lt;<span class=\"pl-ent\">Project<\/span> <span class=\"pl-e\">Sdk<\/span>=<span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span>Microsoft.NET.Sdk<span class=\"pl-pds\">\"<\/span><\/span>&gt;\r\n  &lt;<span class=\"pl-ent\">PropertyGroup<\/span>&gt;\r\n    &lt;<span class=\"pl-ent\">TargetFramework<\/span>&gt;netcoreapp3.1&lt;\/<span class=\"pl-ent\">TargetFramework<\/span>&gt;\r\n  &lt;\/<span class=\"pl-ent\">PropertyGroup<\/span>&gt;\r\n  &lt;<span class=\"pl-ent\">ItemGroup<\/span>&gt;\r\n    &lt;<span class=\"pl-ent\">PackageReference<\/span> <span class=\"pl-e\">Include<\/span>=<span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span>Shared.Dependency<span class=\"pl-pds\">\"<\/span><\/span> <span class=\"pl-e\">Version<\/span>=<span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span>1.0.0<span class=\"pl-pds\">\"<\/span><\/span> \/&gt;\r\n  &lt;\/<span class=\"pl-ent\">ItemGroup<\/span>&gt;\r\n&lt;\/<span class=\"pl-ent\">Project<\/span>&gt;<\/pre>\n<\/div>\n<p>And so when we build the module, our strategy is:<\/p>\n<ul>\n<li>Build AlcModule.Engine<\/li>\n<li>Build AlcModule.Cmdlets<\/li>\n<li>Copy everything from AlcModule.Engine into the Dependencies dir, and remember what we copied<\/li>\n<li>Copy everything from AlcModule.Cmdlets that wasn&#8217;t in AlcModule.Engine into the base module dir<\/li>\n<\/ul>\n<p>Since the module layout here is so crucial to dependency separation, here&#8217;s a build script to use from the source root:<\/p>\n<div class=\"highlight highlight-source-powershell\">\n<pre><span class=\"pl-k\">param<\/span>(\r\n    <span class=\"pl-c\"><span class=\"pl-c\">#<\/span> The .NET build configuration<\/span>\r\n    [<span class=\"pl-c1\">ValidateSet<\/span>(<span class=\"pl-s\"><span class=\"pl-pds\">'<\/span>Debug<span class=\"pl-pds\">'<\/span><\/span><span class=\"pl-k\">,<\/span> <span class=\"pl-s\"><span class=\"pl-pds\">'<\/span>Release<span class=\"pl-pds\">'<\/span><\/span>)]\r\n    [<span class=\"pl-k\">string<\/span>]\r\n    <span class=\"pl-smi\">$Configuration<\/span> <span class=\"pl-k\">=<\/span> <span class=\"pl-s\"><span class=\"pl-pds\">'<\/span>Debug<span class=\"pl-pds\">'<\/span><\/span>\r\n)\r\n\r\n<span class=\"pl-c\"><span class=\"pl-c\">#<\/span> Convenient reusable constants<\/span>\r\n<span class=\"pl-smi\">$mod<\/span> <span class=\"pl-k\">=<\/span> <span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span>AlcModule<span class=\"pl-pds\">\"<\/span><\/span>\r\n<span class=\"pl-smi\">$netcore<\/span> <span class=\"pl-k\">=<\/span> <span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span>netcoreapp3.1<span class=\"pl-pds\">\"<\/span><\/span>\r\n<span class=\"pl-smi\">$copyExtensions<\/span> <span class=\"pl-k\">=<\/span> <span class=\"pl-k\">@<\/span>(<span class=\"pl-s\"><span class=\"pl-pds\">'<\/span>.dll<span class=\"pl-pds\">'<\/span><\/span><span class=\"pl-k\">,<\/span> <span class=\"pl-s\"><span class=\"pl-pds\">'<\/span>.pdb<span class=\"pl-pds\">'<\/span><\/span>)\r\n\r\n<span class=\"pl-c\"><span class=\"pl-c\">#<\/span> Source code locations<\/span>\r\n<span class=\"pl-smi\">$src<\/span> <span class=\"pl-k\">=<\/span> <span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span><span class=\"pl-c1\">$PSScriptRoot<\/span>\/src<span class=\"pl-pds\">\"<\/span><\/span>\r\n<span class=\"pl-smi\">$engineSrc<\/span> <span class=\"pl-k\">=<\/span> <span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span><span class=\"pl-smi\">$src<\/span>\/<span class=\"pl-smi\">$mod<\/span>.Engine<span class=\"pl-pds\">\"<\/span><\/span>\r\n<span class=\"pl-smi\">$cmdletsSrc<\/span> <span class=\"pl-k\">=<\/span> <span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span><span class=\"pl-smi\">$src<\/span>\/<span class=\"pl-smi\">$mod<\/span>.Cmdlets<span class=\"pl-pds\">\"<\/span><\/span>\r\n\r\n<span class=\"pl-c\"><span class=\"pl-c\">#<\/span> Generated output locations<\/span>\r\n<span class=\"pl-smi\">$outDir<\/span> <span class=\"pl-k\">=<\/span> <span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span><span class=\"pl-c1\">$PSScriptRoot<\/span>\/out\/<span class=\"pl-smi\">$mod<\/span><span class=\"pl-pds\">\"<\/span><\/span>\r\n<span class=\"pl-smi\">$outDeps<\/span> <span class=\"pl-k\">=<\/span> <span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span><span class=\"pl-smi\">$outDir<\/span>\/Dependencies<span class=\"pl-pds\">\"<\/span><\/span>\r\n\r\n<span class=\"pl-c\"><span class=\"pl-c\">#<\/span> Build AlcModule.Engine<\/span>\r\n<span class=\"pl-c1\">Push-Location<\/span> <span class=\"pl-smi\">$engineSrc<\/span>\r\ndotnet publish <span class=\"pl-k\">-<\/span>c <span class=\"pl-smi\">$Configuration<\/span>\r\n<span class=\"pl-c1\">Pop-Location<\/span>\r\n\r\n<span class=\"pl-c\"><span class=\"pl-c\">#<\/span> Build AlcModule.Cmdlets<\/span>\r\n<span class=\"pl-c1\">Push-Location<\/span> <span class=\"pl-smi\">$cmdletsSrc<\/span>\r\ndotnet publish <span class=\"pl-k\">-<\/span>c <span class=\"pl-smi\">$Configuration<\/span>\r\n<span class=\"pl-c1\">Pop-Location<\/span>\r\n\r\n<span class=\"pl-c\"><span class=\"pl-c\">#<\/span> Ensure out directory exists and is clean<\/span>\r\n<span class=\"pl-c1\">Remove-Item<\/span> <span class=\"pl-k\">-<\/span>Path <span class=\"pl-smi\">$outDir<\/span> <span class=\"pl-k\">-<\/span>Recurse <span class=\"pl-k\">-<\/span>ErrorAction Ignore\r\n<span class=\"pl-c1\">New-Item<\/span> <span class=\"pl-k\">-<\/span>Path <span class=\"pl-smi\">$outDir<\/span> <span class=\"pl-k\">-<\/span>ItemType Directory\r\n<span class=\"pl-c1\">New-Item<\/span> <span class=\"pl-k\">-<\/span>Path <span class=\"pl-smi\">$outDeps<\/span> <span class=\"pl-k\">-<\/span>ItemType Directory\r\n\r\n<span class=\"pl-c\"><span class=\"pl-c\">#<\/span> Copy manifest<\/span>\r\n<span class=\"pl-c1\">Copy-Item<\/span> <span class=\"pl-k\">-<\/span>Path <span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span><span class=\"pl-c1\">$PSScriptRoot<\/span>\/<span class=\"pl-smi\">$mod<\/span>.psd1<span class=\"pl-pds\">\"<\/span><\/span>\r\n\r\n<span class=\"pl-c\"><span class=\"pl-c\">#<\/span> Copy each Engine asset and remember it<\/span>\r\n<span class=\"pl-smi\">$deps<\/span> <span class=\"pl-k\">=<\/span> [<span class=\"pl-k\">System.Collections.Generic.Hashtable<\/span>[<span class=\"pl-k\">string<\/span>]]::new()\r\n<span class=\"pl-c1\">Get-ChildItem<\/span> <span class=\"pl-k\">-<\/span>Path <span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span><span class=\"pl-smi\">$engineSrc<\/span>\/bin\/<span class=\"pl-smi\">$Configuration<\/span>\/<span class=\"pl-smi\">$netcore<\/span>\/publish\/<span class=\"pl-pds\">\"<\/span><\/span> <span class=\"pl-k\">|<\/span>\r\n    <span class=\"pl-c1\">Where-Object<\/span> { <span class=\"pl-c1\">$_<span class=\"pl-smi\">.Extension<\/span><\/span> <span class=\"pl-k\">-in<\/span> <span class=\"pl-smi\">$copyExtensions<\/span> } <span class=\"pl-k\">|<\/span>\r\n    <span class=\"pl-c1\">ForEach-Object<\/span> { [<span class=\"pl-k\">void<\/span>]<span class=\"pl-smi\">$deps<span class=\"pl-smi\">.Add<\/span><\/span>(<span class=\"pl-c1\">$_<span class=\"pl-smi\">.Name<\/span><\/span>); <span class=\"pl-c1\">Copy-Item<\/span> <span class=\"pl-k\">-<\/span>Path <span class=\"pl-c1\">$_<span class=\"pl-smi\">.FullName<\/span><\/span> <span class=\"pl-k\">-<\/span>Destination <span class=\"pl-smi\">$outDeps<\/span> }\r\n\r\n<span class=\"pl-c\"><span class=\"pl-c\">#<\/span> Now copy each Cmdlets asset, not taking any found in Engine<\/span>\r\n<span class=\"pl-c1\">Get-ChildItem<\/span> <span class=\"pl-k\">-<\/span>Path <span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span><span class=\"pl-smi\">$cmdletsSrc<\/span>\/bin\/<span class=\"pl-smi\">$Configuration<\/span>\/<span class=\"pl-smi\">$netcore<\/span>\/publish\/<span class=\"pl-pds\">\"<\/span><\/span> <span class=\"pl-k\">|<\/span>\r\n    <span class=\"pl-c1\">Where-Object<\/span> { <span class=\"pl-k\">-not<\/span> <span class=\"pl-smi\">$deps<span class=\"pl-smi\">.Contains<\/span><\/span>(<span class=\"pl-c1\">$_<span class=\"pl-smi\">.Name<\/span><\/span>) <span class=\"pl-k\">-and<\/span> <span class=\"pl-c1\">$_<span class=\"pl-smi\">.Extension<\/span><\/span> <span class=\"pl-k\">-in<\/span> <span class=\"pl-smi\">$copyExtensions<\/span> } <span class=\"pl-k\">|<\/span>\r\n    <span class=\"pl-c1\">ForEach-Object<\/span> { <span class=\"pl-c1\">Copy-Item<\/span> <span class=\"pl-k\">-<\/span>Path <span class=\"pl-c1\">$_<span class=\"pl-smi\">.FullName<\/span><\/span> <span class=\"pl-k\">-<\/span>Destination <span class=\"pl-smi\">$outDir<\/span> }<\/pre>\n<\/div>\n<p>So finally, we have a general way to use an Assembly Load Context to isolate our module&#8217;s dependencies that remains robust over time and as more dependencies are added.<\/p>\n<p>Hopefully this example is informative, but naturally a fuller example would be more helpful. For that, go to <a href=\"https:\/\/github.com\/rjmholt\/ModuleDependencyIsolationExample\">this GitHub repository I created<\/a> to give a full demonstration of how to migrate a module to use an ALC, while keeping that module working in .NET Framework and also using .NET Standard and PowerShell Standard to simply the core implementation.<\/p>\n<h3><a id=\"user-content-assembly-resolve-handler-for-side-by-side-loading-with-assemblyloadfile\" class=\"anchor\" href=\"#assembly-resolve-handler-for-side-by-side-loading-with-assemblyloadfile\" aria-hidden=\"true\"><\/a>Assembly resolve handler for side-by-side loading with <code>Assembly.LoadFile()<\/code><\/h3>\n<p>Another, possibly simpler, way to achieve side-by-side assembly loading is to hook up an <code>AssemblyResolve<\/code> event to <code>Assembly.LoadFile()<\/code>. Using <code>Assembly.LoadFile()<\/code> has the advantage of being the simplest solution to implement and working with both .NET Core and .NET Framework.<\/p>\n<p>To show this, let&#8217;s take a look at a quick example of an implementation that combines ideas from our dynamic binding redirect example, and from the Assembly Load Context example above.<\/p>\n<p>We&#8217;ll call this module <code>LoadFileModule<\/code>, which has a trival command <code>Test-LoadFile [-Path] &lt;path&gt;<\/code>. This module takes a dependency on the <code>CsvHelper<\/code> assembly (version 15.0.5).<\/p>\n<p>As with the ALC module, we must first split up the module into two pieces. First the part that does the actual implementation, <code>LoadFileModule.Engine<\/code>:<\/p>\n<div class=\"highlight highlight-source-cs\">\n<pre><span class=\"pl-k\">using<\/span> <span class=\"pl-en\">CsvHelper<\/span>;\r\n\r\n<span class=\"pl-k\">namespace<\/span> <span class=\"pl-en\">LoadFileModule<\/span>.<span class=\"pl-en\">Engine<\/span>\r\n{\r\n    <span class=\"pl-k\">public<\/span> <span class=\"pl-k\">class<\/span> <span class=\"pl-en\">Runner<\/span>\r\n    {\r\n        <span class=\"pl-k\">public<\/span> <span class=\"pl-k\">void<\/span> <span class=\"pl-en\">Use<\/span>(<span class=\"pl-k\">string<\/span> <span class=\"pl-smi\">path<\/span>)\r\n        {\r\n            <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> Here's where we use the CsvHelper dependency<\/span>\r\n            <span class=\"pl-k\">using<\/span> (<span class=\"pl-k\">var<\/span> <span class=\"pl-smi\">reader<\/span> <span class=\"pl-k\">=<\/span> <span class=\"pl-k\">new<\/span> <span class=\"pl-en\">StreamReader<\/span>(<span class=\"pl-smi\">path<\/span>))\r\n            <span class=\"pl-k\">using<\/span> (<span class=\"pl-k\">var<\/span> <span class=\"pl-smi\">csvReader<\/span> <span class=\"pl-k\">=<\/span> <span class=\"pl-k\">new<\/span> <span class=\"pl-en\">CsvReader<\/span>(<span class=\"pl-smi\">reader<\/span>, <span class=\"pl-smi\">CultureInfo<\/span>.<span class=\"pl-smi\">InvariantCulture<\/span>))\r\n            {\r\n                <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> Imagine we do something useful here...<\/span>\r\n            }\r\n        }\r\n    }\r\n}<\/pre>\n<\/div>\n<p>And then the part that PowerShell will load directly, <code>LoadFileModule.Cmdlets<\/code>. We use a very similar strategy as with the ALC module, where we isolate dependencies in a separate directory. But this time we must load all assemblies in with a resolve event, rather than just LoadFileModule.Engine.dll.<\/p>\n<div class=\"highlight highlight-source-cs\">\n<pre><span class=\"pl-k\">using<\/span> <span class=\"pl-en\">LoadFileModule<\/span>.<span class=\"pl-en\">Engine<\/span>;\r\n\r\n<span class=\"pl-k\">namespace<\/span> <span class=\"pl-en\">LoadFileModule<\/span>.<span class=\"pl-en\">Cmdlets<\/span>\r\n{\r\n    <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> Actual cmdlet definition<\/span>\r\n    [<span class=\"pl-en\">Cmdlet<\/span>(<span class=\"pl-smi\">VerbsDiagnostic<\/span>.<span class=\"pl-smi\">Test<\/span>, <span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span>LoadFile<span class=\"pl-pds\">\"<\/span><\/span>)]\r\n    <span class=\"pl-k\">public<\/span> <span class=\"pl-k\">class<\/span> <span class=\"pl-en\">TestLoadFileCommand<\/span> : <span class=\"pl-en\">Cmdlet<\/span>\r\n    {\r\n        [<span class=\"pl-en\">Parameter<\/span>(<span class=\"pl-en\">Mandatory<\/span> <span class=\"pl-k\">=<\/span> <span class=\"pl-c1\">true<\/span>)]\r\n        <span class=\"pl-k\">public<\/span> <span class=\"pl-k\">string<\/span> <span class=\"pl-smi\">Path<\/span> { <span class=\"pl-k\">get<\/span>; <span class=\"pl-k\">set<\/span>; }\r\n\r\n        <span class=\"pl-k\">protected<\/span> <span class=\"pl-k\">override<\/span> <span class=\"pl-k\">void<\/span> <span class=\"pl-en\">EndProcessing<\/span>()\r\n        {\r\n            <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> Here's where we use LoadFileModule.Engine<\/span>\r\n            <span class=\"pl-k\">new<\/span> <span class=\"pl-en\">Runner<\/span>().<span class=\"pl-en\">Use<\/span>(<span class=\"pl-smi\">Path<\/span>);\r\n        }\r\n    }\r\n\r\n    <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> This class controls our dependency resolution<\/span>\r\n    <span class=\"pl-k\">public<\/span> <span class=\"pl-k\">class<\/span> <span class=\"pl-en\">ModuleContextHandler<\/span> : <span class=\"pl-en\">IModuleAssemblyInitializer<\/span>, <span class=\"pl-en\">IModuleAssemblyCleanup<\/span>\r\n    {\r\n        <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> We catalog our dependencies here to ensure we don't load anything else<\/span>\r\n        <span class=\"pl-k\">private<\/span> <span class=\"pl-k\">static<\/span> <span class=\"pl-en\">IReadOnlyDictionary<\/span>&lt;<span class=\"pl-k\">string<\/span>, <span class=\"pl-k\">int<\/span>&gt; <span class=\"pl-smi\">s_dependencies<\/span> <span class=\"pl-k\">=<\/span> <span class=\"pl-k\">new<\/span> <span class=\"pl-en\">Dictionary<\/span>&lt;<span class=\"pl-k\">string<\/span>, <span class=\"pl-k\">int<\/span>&gt;\r\n        {\r\n            { <span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span>CsvHelper<span class=\"pl-pds\">\"<\/span><\/span>, <span class=\"pl-c1\">15<\/span> },\r\n        };\r\n\r\n        <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> Set up the path to our dependency directory within the module<\/span>\r\n        <span class=\"pl-k\">private<\/span> <span class=\"pl-k\">static<\/span> <span class=\"pl-k\">string<\/span> <span class=\"pl-smi\">s_dependenciesDirPath<\/span> <span class=\"pl-k\">=<\/span> <span class=\"pl-smi\">Path<\/span>.<span class=\"pl-en\">GetFullPath<\/span>(\r\n            <span class=\"pl-smi\">Path<\/span>.<span class=\"pl-en\">Combine<\/span>(\r\n                <span class=\"pl-smi\">Path<\/span>.<span class=\"pl-en\">GetDirectoryName<\/span>(<span class=\"pl-smi\">Assembly<\/span>.<span class=\"pl-en\">GetExecutingAssembly<\/span>().<span class=\"pl-smi\">Location<\/span>),\r\n                <span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span>Dependencies<span class=\"pl-pds\">\"<\/span><\/span>));\r\n\r\n        <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> This makes sure we only try to resolve dependencies when the module is loaded<\/span>\r\n        <span class=\"pl-k\">private<\/span> <span class=\"pl-k\">static<\/span> <span class=\"pl-k\">bool<\/span> <span class=\"pl-smi\">s_engineLoaded<\/span> <span class=\"pl-k\">=<\/span> <span class=\"pl-c1\">false<\/span>;\r\n\r\n        <span class=\"pl-k\">public<\/span> <span class=\"pl-k\">void<\/span> <span class=\"pl-en\">OnImport<\/span>()\r\n          {\r\n            <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> Set up our event when the module is loaded<\/span>\r\n            <span class=\"pl-smi\">AppDomain<\/span>.<span class=\"pl-smi\">CurrentDomain<\/span>.<span class=\"pl-smi\">AssemblyResolve<\/span> <span class=\"pl-k\">+=<\/span> <span class=\"pl-smi\">HandleResolveEvent<\/span>;\r\n        }\r\n\r\n        <span class=\"pl-k\">public<\/span> <span class=\"pl-k\">void<\/span> <span class=\"pl-en\">OnRemove<\/span>(<span class=\"pl-en\">PSModuleInfo<\/span> <span class=\"pl-smi\">psModuleInfo<\/span>)\r\n        {\r\n            <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> Unset the event when the module is unloaded<\/span>\r\n            <span class=\"pl-smi\">AppDomain<\/span>.<span class=\"pl-smi\">CurrentDomain<\/span>.<span class=\"pl-smi\">AssemblyResolve<\/span> <span class=\"pl-k\">-=<\/span> <span class=\"pl-smi\">HandleResolveEvent<\/span>;\r\n        }\r\n\r\n        <span class=\"pl-k\">private<\/span> <span class=\"pl-k\">static<\/span> <span class=\"pl-en\">Assembly<\/span> <span class=\"pl-en\">HandleResolveEvent<\/span>(<span class=\"pl-k\">object<\/span> <span class=\"pl-smi\">sender<\/span>, <span class=\"pl-en\">ResolveEventArgs<\/span> <span class=\"pl-smi\">args<\/span>)\r\n        {\r\n            <span class=\"pl-k\">var<\/span> <span class=\"pl-smi\">asmName<\/span> <span class=\"pl-k\">=<\/span> <span class=\"pl-k\">new<\/span> <span class=\"pl-en\">AssemblyName<\/span>(<span class=\"pl-smi\">args<\/span>.<span class=\"pl-smi\">Name<\/span>);\r\n\r\n            <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> First we want to know if this is the top-level assembly<\/span>\r\n            <span class=\"pl-k\">if<\/span> (<span class=\"pl-smi\">asmName<\/span>.<span class=\"pl-smi\">Name<\/span>.<span class=\"pl-en\">Equals<\/span>(<span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span>LoadFileModule.Engine<span class=\"pl-pds\">\"<\/span><\/span>))\r\n            {\r\n                <span class=\"pl-smi\">s_engineLoaded<\/span> <span class=\"pl-k\">=<\/span> <span class=\"pl-c1\">true<\/span>;\r\n                <span class=\"pl-k\">return<\/span> <span class=\"pl-smi\">Assembly<\/span>.<span class=\"pl-en\">LoadFile<\/span>(<span class=\"pl-smi\">Path<\/span>.<span class=\"pl-en\">Combine<\/span>(<span class=\"pl-smi\">s_dependenciesDirPath<\/span>, <span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span>Unrelated.Engine.dll<span class=\"pl-pds\">\"<\/span><\/span>));\r\n            }\r\n\r\n            <span class=\"pl-c\"><span class=\"pl-c\">\/\/<\/span> Otherwise, if that assembly has been loaded, we must try to resolve its dependencies too<\/span>\r\n            <span class=\"pl-k\">if<\/span> (<span class=\"pl-smi\">s_engineLoaded<\/span>\r\n                <span class=\"pl-k\">&amp;&amp;<\/span> <span class=\"pl-smi\">s_dependencies<\/span>.<span class=\"pl-en\">TryGetValue<\/span>(<span class=\"pl-smi\">asmName<\/span>.<span class=\"pl-smi\">Name<\/span>, <span class=\"pl-k\">out<\/span> <span class=\"pl-k\">int<\/span> <span class=\"pl-smi\">requiredMajorVersion<\/span>)\r\n                <span class=\"pl-k\">&amp;&amp;<\/span> <span class=\"pl-smi\">asmName<\/span>.<span class=\"pl-smi\">Version<\/span>.<span class=\"pl-smi\">Major<\/span> <span class=\"pl-k\">==<\/span> <span class=\"pl-smi\">requiredMajorVersion<\/span>)\r\n            {\r\n                <span class=\"pl-k\">string<\/span> <span class=\"pl-smi\">asmPath<\/span> <span class=\"pl-k\">=<\/span> <span class=\"pl-smi\">Path<\/span>.<span class=\"pl-en\">Combine<\/span>(<span class=\"pl-smi\">s_dependenciesDirPath<\/span>, <span class=\"pl-s\"><span class=\"pl-pds\">$\"<\/span>{<span class=\"pl-smi\">asmName<\/span>.<span class=\"pl-smi\">Name<\/span>}.dll<span class=\"pl-pds\">\"<\/span><\/span>);\r\n                <span class=\"pl-k\">return<\/span> <span class=\"pl-smi\">Assembly<\/span>.<span class=\"pl-en\">LoadFile<\/span>(<span class=\"pl-smi\">asmPath<\/span>);\r\n            }\r\n\r\n            <span class=\"pl-k\">return<\/span> <span class=\"pl-c1\">null<\/span>;\r\n        }\r\n    }\r\n}<\/pre>\n<\/div>\n<p>Finally, the layout of the module is also similar to the ALC module:<\/p>\n<pre lang=\"none\"><code>LoadFileModule\/\r\n  + LoadFileModule.Cmdlets.dll\r\n  + LoadFileModule.psd1\r\n  + Dependencies\/\r\n  |  + LoadFileModule.Engine.dll\r\n  |  + CsvHelper.dll\r\n<\/code><\/pre>\n<p>With this structure in place, LoadFileModule now supports being loaded alongside other modules with a dependency on CsvHelper.<\/p>\n<p>Because our handler will apply to <strong>all<\/strong> <code>AssemblyResolve<\/code> events across the Application Domain, we&#8217;ve needed to make some specific design choices here:<\/p>\n<ul>\n<li>We only start handling general dependency loading after LoadFileModule.Engine.dll has been loaded, to narrow the window in which our event may interfere with other loading.<\/li>\n<li>We push LoadFileModule.Engine.dll out into the Dependencies directory, so that it&#8217;s loaded by a <code>LoadFile()<\/code> call rather than by PowerShell. This means its own dependency loads will always raise an <code>AssemblyResolve<\/code> event, even if another CsvHelper.dll (for example) is loaded in PowerShell, meaning we have an opportunity to find the correct dependency.<\/li>\n<li>We are forced to code a dictionary of dependency names and versions into our handler, since we must try only to resolve our specific dependencies for our module. In our particular case, it would be possible to use the <code>ResolveEventArgs.RequestingAssembly<\/code> property to work out whether <code>CsvHelper<\/code> is being requested by our module, but this wouldn&#8217;t work for dependencies of dependencies (for example if <code>CsvHelper<\/code> itself raised an <code>AssemblyResolve<\/code> event). There are other, harder ways to do this, such as comparing requested assemblies to the metadata of assemblies in the Dependencies directory, but here we&#8217;ve made this checking more explicit both to highlight the issue and to simplify the example.<\/li>\n<\/ul>\n<p>Essentially this is the key difference between the <code>LoadFile()<\/code> strategy and the ALC strategy: the <code>AssemblyResolve<\/code> event must service all loads in the Application Domain, making it much harder to keep our dependency resolution from being tangled with other modules. However, the lack of ALCs in .NET Framework makes this option one worth understanding (even just for .NET Framework, while using an ALC in .NET Core).<\/p>\n<h3><a id=\"user-content-custom-application-domains\" class=\"anchor\" href=\"#custom-application-domains\" aria-hidden=\"true\"><\/a>Custom Application Domains<\/h3>\n<p>A final (and by some measures, extreme) option for assembly isolation is custom Application Domain use. Application Domains (used in the plural), are only available in .NET Framework, and are used to provide in-process isolation between parts of a .NET application. One of the uses of this is to isolate assembly loads from each other within the same process.<\/p>\n<p>However, Application Domains are serialization boundaries, meaning that objects in one Application Domain cannot be referenced and used directly by objects in another Application Domain. This can be worked around by implementing <code>MarshalByRefObject<\/code>, but when you don&#8217;t control the types (as is often the case with dependencies), it&#8217;s not possible to force an implementation here, meaning that the only solution is to make large architectural changes. The serialization boundary also has serious performance implications.<\/p>\n<p>Because Application Domains have this serious limitation, are complicated to implement, and only work in .NET Framework, I won&#8217;t give an example of how you might use them here. While they&#8217;re worth mentioning as a possibility, they aren&#8217;t an investment I would recommend.<\/p>\n<p>If you&#8217;re interested in trying to use a custom Application Domain, the following links might help:<\/p>\n<ul>\n<li><a href=\"https:\/\/docs.microsoft.com\/dotnet\/framework\/app-domains\/application-domains\" rel=\"nofollow\">Conceptual documentation on Appliation Domains<\/a><\/li>\n<li><a href=\"https:\/\/docs.microsoft.com\/dotnet\/framework\/app-domains\/use\" rel=\"nofollow\">Examples for using Application Domains<\/a><\/li>\n<\/ul>\n<h2><a id=\"user-content-solutions-for-dependency-conflicts-that-dont-work-for-powershell\" class=\"anchor\" href=\"#solutions-for-dependency-conflicts-that-dont-work-for-powershell\" aria-hidden=\"true\"><\/a>Solutions for dependency conflicts that don&#8217;t work for PowerShell<\/h2>\n<p>Finally, I should address some possibilities that come up when researching .NET dependency conflicts in .NET that can look promising but generally won&#8217;t work for PowerShell.<\/p>\n<p>These solutions have the common theme that they are changes in deployment configurations in an environment where you control the application and possibly the entire machine. This is because they are oriented toward scenarios like web servers and other applications deployed to server environments, where the environment is intended to host the application and is free to be configured by the deploying user.<\/p>\n<p>They also tend to be very much .NET Framework focused, meaning they will not work with PowerShell 6 or above.<\/p>\n<p>Note that if you know that your module will only be used in environments you have total control over, and only with Windows PowerShell 5.1 and below, some of these may be options.<\/p>\n<p>In general however, <strong>modules should not modify global machine state like this<\/strong>, as it can break configurations, causing powershell.exe, other modules or other dependent applications not to work, or simply fail and cause your module to fail in unexpected ways.<\/p>\n<h3><a id=\"user-content-static-binding-redirect-with-appconfig-to-force-using-the-same-dependency-version\" class=\"anchor\" href=\"#static-binding-redirect-with-appconfig-to-force-using-the-same-dependency-version\" aria-hidden=\"true\"><\/a>Static binding redirect with app.config to force using the same dependency version<\/h3>\n<p>.NET Framework applications can take advantage of an <code>app.config<\/code> file to configure some of the application&#8217;s behaviours declaratively. It&#8217;s possible to write an <code>app.config<\/code> entry that configures assembly binding to redirect assembly loading to a particular version.<\/p>\n<p>Two issues with this for PowerShell are:<\/p>\n<ul>\n<li>.NET Core does not support <code>app.config<\/code>, so this is a <code>powershell.exe<\/code>-only solution.<\/li>\n<li><code>powershell.exe<\/code> is a shared application that lives under the <code>System32<\/code> directory. This means its likely your module won&#8217;t be able to modify its contents on many systems, and even if it can, modifying the <code>app.config<\/code> could break an existing configuration or affect the loading of other modules.<\/li>\n<\/ul>\n<h3><a id=\"user-content-setting-codebase-with-appconfig\" class=\"anchor\" href=\"#setting-codebase-with-appconfig\" aria-hidden=\"true\"><\/a>Setting <code>codebase<\/code> with app.config<\/h3>\n<p>For the same reasons as with setting binding redirects in <code>app.config<\/code>, trying to configure the <code>app.config<\/code> <code>codebase<\/code> setting is generally not going to work in PowerShell modules.<\/p>\n<h3><a id=\"user-content-installing-your-dependencies-to-the-global-assembly-cache-gac\" class=\"anchor\" href=\"#installing-your-dependencies-to-the-global-assembly-cache-gac\" aria-hidden=\"true\"><\/a>Installing your dependencies to the Global Assembly Cache (GAC)<\/h3>\n<p>Another way to resolve dependency version conflicts in .NET Framework is to install dependencies to the GAC, so that different versions can be loaded side-by-side from the GAC.<\/p>\n<p>Again for PowerShell modules, the chief issues here are:<\/p>\n<ul>\n<li>The GAC only applies to .NET Framework, so this will not help in PowerShell 6 and above.<\/li>\n<li>Installing assemblies to the GAC is a modification of global machine state and may cause side-effects in other applications or to other modules. It may also be difficult to do correctly, even when your module has the required access privileges (and getting it wrong could cause serious issues in other .NET applications machine-wide).<\/li>\n<\/ul>\n<h2><a id=\"user-content-solving-the-issue-in-powershell-itself\" class=\"anchor\" href=\"#solving-the-issue-in-powershell-itself\" aria-hidden=\"true\"><\/a>Solving the issue in PowerShell itself&#8230;<\/h2>\n<p>After reading through all of this and seeing the complexity not just of implementing an isolation solution, but making it work with the PowerShell module system, you may wonder why PowerShell hasn&#8217;t put a solution to this problem into the module system yet.<\/p>\n<p>While it&#8217;s not something we&#8217;re planning to implement imminently, the facility of Assembly Load Contexts available in .NET 5 makes this something worth considering long term.<\/p>\n<p>However, this would represent a large change in the module system, which is a very critical (and already complex) component of PowerShell. In addition, the diversity of PowerShell modules and module scenarios presents a serious challenge in terms of PowerShell correctly isolating modules consistently and without creating edge cases.<\/p>\n<p>In particular, dependencies will be exposed if they are part of a module&#8217;s API. For example, if a PowerShell module converts YAML strings to objects and uses YamlDotNet to return objects of type <code>YamlDotNet.YamlDocument<\/code>, then its dependencies are exposed to the global context.<\/p>\n<p>This can lead to type identity issues when instances of the exposed type are used with APIs expecting the same type (but from a different assembly); specifically you may see confusing messages like &#8220;Type <code>YamlDotNet.YamlDocument<\/code> cannot be cast to type <code>YamlDotNet.YamlDocument<\/code>&#8220;, because even though the two types have the same name, .NET regards them as coming from different assemblies and therefore being different.<\/p>\n<p>In order to safely isolate a dependency used in the module API, API types need to either be a type already found in PowerShell and its public dependencies, like <code>PSObject<\/code> or <code>System.Xml.XmlDocument<\/code>, or a new type defined by the module to be an intermediary between the PowerShell context and the dependency context.<\/p>\n<p>It&#8217;s likely that any module isolation strategy could break some module, and that module authors will need, to some extent, to understand the implications of dependency isolation and be responsible for deciding whether their module can have its dependencies isolated.<\/p>\n<p>So with that in mind, I encourage you <a href=\"https:\/\/github.com\/PowerShell\/PowerShell\/issues\/12920\">to contribute to the discussion on GitHub<\/a>. Also take a look at <a href=\"https:\/\/github.com\/PowerShell\/PowerShell-RFC\/blob\/master\/X-Withdrawn\/RFC0043-Loading-Module-Into-Isolated-AssemblyLoadContext.md\">the RFC<\/a> proposed to address the issue.<\/p>\n<h2><a id=\"user-content-further-reading\" class=\"anchor\" href=\"#further-reading\" aria-hidden=\"true\"><\/a>Further reading<\/h2>\n<p>There&#8217;s plenty more to read on the topic of .NET assembly version dependency conflicts. Here are some nice jumping off points:<\/p>\n<ul>\n<li><a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/standard\/assembly\/\" rel=\"nofollow\">.NET: Assemblies in .NET<\/a><\/li>\n<li><a href=\"https:\/\/docs.microsoft.com\/dotnet\/core\/dependency-loading\/loading-managed\" rel=\"nofollow\">.NET Core: The managed assembly loading algorithm<\/a><\/li>\n<li><a href=\"https:\/\/docs.microsoft.com\/dotnet\/core\/dependency-loading\/understanding-assemblyloadcontext\" rel=\"nofollow\">.NET Core: Understanding System.Runtime.Loader.AssemblyLoadContext<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/dotnet\/runtime\/issues\/13471\">.NET Core: Discussion about side-by-side assembly loading solutions<\/a><\/li>\n<li><a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/framework\/configure-apps\/redirect-assembly-versions\" rel=\"nofollow\">.NET Framework: Redirecting assembly versions<\/a><\/li>\n<li><a href=\"https:\/\/docs.microsoft.com\/dotnet\/framework\/deployment\/best-practices-for-assembly-loading\" rel=\"nofollow\">.NET Framework: Best practices for assembly loading<\/a><\/li>\n<li><a href=\"https:\/\/docs.microsoft.com\/dotnet\/framework\/deployment\/how-the-runtime-locates-assemblies\" rel=\"nofollow\">.NET Framework: How the runtime locates assemblies<\/a><\/li>\n<li><a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/standard\/assembly\/resolve-loads\" rel=\"nofollow\">.NET Framework: Resolve assembly loads<\/a><\/li>\n<li><a href=\"https:\/\/stackoverflow.com\/questions\/43365736\/assembly-binding-redirect-how-and-why\" rel=\"nofollow\">StackOverflow: Assembly binding redirect, how and why?<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/PowerShell\/PowerShell\/issues\/11571\">PowerShell: Discussion about implementing AssemblyLoadContexts<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/PowerShell\/PowerShell\/issues\/12052\">PowerShell: <code>Assembly.LoadFile()<\/code> doesn&#8217;t load into default AssemblyLoadContext<\/a><\/li>\n<li><a href=\"https:\/\/weblog.west-wind.com\/posts\/2012\/Nov\/03\/Back-to-Basics-When-does-a-NET-Assembly-Dependency-get-loaded\" rel=\"nofollow\">Rick Strahl: When does a .NET assembly dependency get loaded?<\/a><\/li>\n<li><a href=\"https:\/\/codeblog.jonskeet.uk\/2019\/06\/30\/versioning-limitations-in-net\/\" rel=\"nofollow\">Jon Skeet: Summary of versioning in .NET<\/a><\/li>\n<li><a href=\"https:\/\/natemcmaster.com\/blog\/2017\/12\/21\/netcore-primitives\/\" rel=\"nofollow\">Nate McMaster: Deep dive into .NET Core primitives<\/a><\/li>\n<\/ul>\n<h2><a id=\"user-content-final-notes\" class=\"anchor\" href=\"#final-notes\" aria-hidden=\"true\"><\/a>Final notes<\/h2>\n<p>In this blog post, we looked over various ways to solve the issue of having module dependency conflicts in PowerShell, identifying strategies that won&#8217;t work, simple strategies that sometimes work, and more complex strategies that are more robust.<\/p>\n<p>In particular we looked at how to implement an Assembly Load Context in .NET Core to make module dependency isolation much easier in PowerShell 6 and up.<\/p>\n<p>This is fairly complicated subject matter, so don&#8217;t worry if it doesn&#8217;t click immediately. Feel free to read the material here and linked, experiment with implementations (try stepping through it in a debugger), and also get in touch with us on GitHub or Twitter.<\/p>\n<p>Cheers!<\/p>\n<p>Rob Holt<\/p>\n<p>Software Engineer<\/p>\n<p>PowerShell Team<\/p>\n","protected":false},"excerpt":{"rendered":"<p>When writing a PowerShell module, especially a binary module (i.e. one written in a language like C# and loaded into PowerShell as an assembly\/DLL), it&#8217;s natural to take dependencies on other packages or libraries to provide functionality. Taking dependencies on other libraries is usually desirable for code reuse. However, PowerShell always loads assemblies into the [&hellip;]<\/p>\n","protected":false},"author":3098,"featured_media":13641,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-18432","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-powershell"],"acf":[],"blog_post_summary":"<p>When writing a PowerShell module, especially a binary module (i.e. one written in a language like C# and loaded into PowerShell as an assembly\/DLL), it&#8217;s natural to take dependencies on other packages or libraries to provide functionality. Taking dependencies on other libraries is usually desirable for code reuse. However, PowerShell always loads assemblies into the [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/posts\/18432","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/users\/3098"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/comments?post=18432"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/posts\/18432\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/media\/13641"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/media?parent=18432"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/categories?post=18432"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/tags?post=18432"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}