Skip to content

Commit

Permalink
AssembyContexts are no fun
Browse files Browse the repository at this point in the history
  • Loading branch information
svrooij committed Dec 27, 2024
1 parent 89c2a3c commit 2fcdd6e
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 13 deletions.
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
push:
branches:
- main
- feature/assembly-context
tags:
- v*
pull_request:
Expand Down
33 changes: 33 additions & 0 deletions src/TokenMagician/ModuleAssemblyLoadContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
namespace TokenMagician;
using System.Reflection;
using System.Runtime.Loader;

internal class ModuleAssemblyLoadContext : AssemblyLoadContext
{
private readonly string _dependencyDirPath;

public ModuleAssemblyLoadContext(string dependencyDirPath)
{
_dependencyDirPath = dependencyDirPath;
}

protected override Assembly Load(AssemblyName assemblyName)
{
// We do the simple logic here of looking for an assembly of the given name
// in the configured dependency directory.
string assemblyPath = Path.Combine(
_dependencyDirPath,
$"{assemblyName.Name}.dll");

if (File.Exists(assemblyPath))
{
Console.WriteLine($"Loading assembly {assemblyName.Name} from {assemblyPath}");
// The ALC must use inherited methods to load assemblies.
// Assembly.Load*() won't work here.
return LoadFromAssemblyPath(assemblyPath);
}

// For other assemblies, return null to allow other resolutions to continue.
return null;

Check warning on line 31 in src/TokenMagician/ModuleAssemblyLoadContext.cs

View workflow job for this annotation

GitHub Actions / 🛠️ Test PowerShell Module

Possible null reference return. [/home/runner/work/TokenMagician/TokenMagician/src/TokenMagician/TokenMagician.csproj]

Check warning on line 31 in src/TokenMagician/ModuleAssemblyLoadContext.cs

View workflow job for this annotation

GitHub Actions / 🛠️ Test PowerShell Module

Possible null reference return. [/home/runner/work/TokenMagician/TokenMagician/src/TokenMagician/TokenMagician.csproj]
}
}
58 changes: 58 additions & 0 deletions src/TokenMagician/ModuleResolveEventHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System.Management.Automation;
using System.Reflection;
using System.Runtime.Loader;

namespace TokenMagician;

/// <summary>
/// A class that initializes the module when it is imported into the session.
/// </summary>
public class ModuleResolveEventHandler : IModuleAssemblyInitializer, IModuleAssemblyCleanup
{
// Get the path of the dependency directory.
// In this case we find it relative to the AlcModule.Cmdlets.dll location
private static readonly string s_dependencyDirPath = Path.GetFullPath(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!);

private static readonly ModuleAssemblyLoadContext s_dependencyAlc =
new ModuleAssemblyLoadContext(s_dependencyDirPath);
public void OnImport()
{
// This is called when the module is imported into the session
// and can be used to initialize the module.
Console.WriteLine("Module imported");
AssemblyLoadContext.Default.Resolving += ResolveAssembly;
}

public void OnRemove(PSModuleInfo module)
{
// This is called when the module is removed from the session
// and can be used to cleanup the module.
Console.WriteLine("Module removed");
AssemblyLoadContext.Default.Resolving -= ResolveAssembly;
}

private static Assembly ResolveAssembly(AssemblyLoadContext defaultAlc, AssemblyName assemblyToResolve)
{
// We only want to resolve the Alc.Engine.dll assembly here.
// Because this will be loaded into the custom ALC,
// all of *its* dependencies will be resolved
// by the logic we defined for that ALC's implementation.
//
// Note that we are safe in our assumption that the name is enough
// to distinguish our assembly here,
// since it's unique to our module.
// There should be no other AlcModule.Engine.dll on the system.
if (!File.Exists(Path.Combine(s_dependencyDirPath, $"{assemblyToResolve.Name}.dll")))
{
Console.WriteLine($"Assembly {assemblyToResolve.Name} not found in {s_dependencyDirPath}");
return null;

Check warning on line 48 in src/TokenMagician/ModuleResolveEventHandler.cs

View workflow job for this annotation

GitHub Actions / 🛠️ Test PowerShell Module

Possible null reference return. [/home/runner/work/TokenMagician/TokenMagician/src/TokenMagician/TokenMagician.csproj]

Check warning on line 48 in src/TokenMagician/ModuleResolveEventHandler.cs

View workflow job for this annotation

GitHub Actions / 🛠️ Test PowerShell Module

Possible null reference return. [/home/runner/work/TokenMagician/TokenMagician/src/TokenMagician/TokenMagician.csproj]
}

// Allow our ALC to handle the directory discovery concept
//
// This is where Alc.Engine.dll is loaded into our custom ALC
// and then passed through into PowerShell's ALC,
// becoming the bridge between both
return s_dependencyAlc.LoadFromAssemblyName(assemblyToResolve);
}
}
26 changes: 13 additions & 13 deletions src/TokenMagician/TokenMagician.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Script module or binary module file associated with this manifest.
# This loads the psm1 file, which in turn loads the TokenMagician.Loader.dll which will then load the actual module.
# This is to setup the Assembly Load Context for the module and its dependencies.
RootModule = 'TokenMagician.psm1'
RootModule = 'TokenMagician.dll'

# Version number of this module.
ModuleVersion = '0.1.0'
Expand Down Expand Up @@ -59,25 +59,25 @@
# FunctionsToExport = @()

# Cmdlets to export from this module.
CmdletsToExport = @(
"Get-TmMsiToken"
)
# CmdletsToExport = @(
# "Get-TmMsiToken"
# )

# Variables to export from this module.
# VariablesToExport = @()

# Aliases to export from this module.
AliasesToExport = @(
"Get-MsiToken"
)
# AliasesToExport = @(
# "Get-MsiToken"
# )

# List of all files included in this module.
FileList = @(
"TokenMagician.psd1",
"TokenMagician.psm1",
"TokenMagician.dll-Help.xml",
"TokenMagician.dll"
)
# FileList = @(
# "TokenMagician.psd1",
# "TokenMagician.psm1",
# "TokenMagician.dll-Help.xml",
# "TokenMagician.dll"
# )

# Private data to pass to the module specified in RootModule/ModuleToProcess.
PrivateData = @{
Expand Down

0 comments on commit 2fcdd6e

Please sign in to comment.