Skip to content

ALC Approach is Leaky #1945

@jborean93

Description

@jborean93

Prerequisites

  • Write a descriptive title.
  • Make sure you are able to repro it on the latest released version
  • Search the existing issues.

Steps to reproduce

# Get existing assemblies that are loaded
$existing = [AppDomain]::CurrentDomain.GetAssemblies() | Select-Object @(
    @{ N = 'Name'; E = { $_.GetName().Name } }
    'Location'
) | Sort-Object -Property Name

# Use -PassThru to verify version being loaded
Import-Module -Name Microsoft.PowerShell.PSResourceGet -PassThru

# Get list of assemblies PSResourceGet loaded in the main ALC
[AppDomain]::CurrentDomain.GetAssemblies() | Select-Object @(
    @{ N = 'Name'; E = { $_.GetName().Name } }
    'Location'
) | Where-Object { $_.Name -notin $existing.Name } | Sort-Object -Property Name

Expected behavior

Nothing extra outside of $PSHome and Microsoft.PowerShell.PSResourceGet.dll

Actual behavior

Nuget.Versioning is present in the default ALC and has been loaded with PSResourceGet

Name     : NuGet.Versioning
Location : /home/jborean/.local/share/powershell/Modules/Microsoft.PowerShell.PSResourceGet/1.2.0/dependencies/NuGet.Versioning.dll

The reason for this is that the module only sets up the ALC in IModuleAssemblyInitializer.OnImport and while this normally works fine there is a risk that when PowerShell goes to load the assembly this OnImport method is set it may have to resolve a dependency before OnImport is called. I don't know the exact details but I've typically found it happens when you have a base class inheritance from an external assembly or some other type metadata (field/property/parameter/etc) contains a type in another assembly and for whatever reason it must be resolved at load time rather than at runtime when it's used.

The solution to this is to use something like AlcLoader. Essentially you want a separate assembly something like Microsoft.PowerShell.PSResourceGet.Loader that sets up the ALC and is used to load Microsoft.PowerShell.PSResourceGet completely in an ALC.

You can see this in action in my various modules but the latest one is PSNetDetour. I have the following files

  • RootModule -> Module.psm1
    • This loads the Shared/Loader assembly in this case PSNetDetour.Shared
    • It uses this Shared/Loader to setup the ALC and load our main module inside the ALC
    • The shared loader returns an Assembly object which is imported with Import-Module
    • .NET Framework just skips all this and loads the main assembly by path
    • https://github.com/jborean93/PSNetDetour/blob/main/module/PSNetDetour.psm1
  • Shared/Loader Assembly -> LoadContext.cs
  • Module Assembly
    • This contains our cmdlets and other code
    • Loaded in the ALC with any deps it uses
    • Even though it's in an ALC PowerShell still can reference any public types due to how Import-Module -Assembly ... works

Error details

No error but could cause conflicts with anything relying on that assembly and they also load it in the main ALC

Environment data

ModuleType Version    PreRelease Name                                ExportedCommands
---------- -------    ---------- ----                                ----------------
Binary     1.2.0      rc3        Microsoft.PowerShell.PSResourceGet  {Compress-PSResource, Find-PSResource, Get-InstalledPSResource, G…

Name                           Value
----                           -----
PSVersion                      7.5.1
PSEdition                      Core
GitCommitId                    7.5.1
OS                             Fedora Linux 43 (Server Edition)
Platform                       Unix
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

Visuals

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions