PSDataRepository.psm1

# PSDataRepository module loader
# Selects the correct binary for the running .NET runtime version.
# PowerShell 7.4 runs on .NET 8, PowerShell 7.6+ runs on .NET 10.

$dotnetMajor = [System.Environment]::Version.Major
$framework = if ($dotnetMajor -ge 10) { 'net10.0' } else { 'net8.0' }

$binRoot = [System.IO.Path]::Combine($PSScriptRoot, 'bin', $framework)
if (-not (Test-Path -LiteralPath $binRoot)) {
    throw "PSDataRepository: build output for runtime '$framework' not found at '$binRoot'. " +
          "Detected .NET $($dotnetMajor).x; supported targets are net8.0 (PowerShell 7.4/7.5) " +
          "and net10.0 (PowerShell 7.6+). The module installation may be corrupted."
}

foreach ($required in 'PSDataRepository.Loader.dll', 'PSDataRepository.Commands.dll') {
    if (-not (Test-Path -LiteralPath ([System.IO.Path]::Combine($binRoot, $required)))) {
        throw "PSDataRepository: could not find $required in '$binRoot'. " +
              "The module installation may be corrupted."
    }
}

# The engine — the cmdlets, Microsoft.Extensions.*, Azure.Identity, MSAL, the storage clients —
# runs in an AssemblyLoadContext of its own (see PSDataRepository.Loader). Loading it into the
# process-wide default context, as Import-Module of the binary path would, decides for every other
# module in the session which version of those libraries exists. Only the dependency-free loader
# goes into the default context.
#
# A loader already loaded by an earlier import is reused: a second file with the same assembly name
# cannot be loaded into the default context. Each module directory still gets its own engine.
$loaderAssembly = [System.AppDomain]::CurrentDomain.GetAssemblies() |
    Where-Object { $_.GetName().Name -eq 'PSDataRepository.Loader' -and
                   [System.Runtime.Loader.AssemblyLoadContext]::GetLoadContext($_) -eq [System.Runtime.Loader.AssemblyLoadContext]::Default } |
    Select-Object -First 1
if (-not $loaderAssembly) {
    $loaderAssembly = [System.Reflection.Assembly]::LoadFrom([System.IO.Path]::Combine($binRoot, 'PSDataRepository.Loader.dll'))
}
$commandsAssembly = $loaderAssembly.GetType('PSDataRepository.Loader.ModuleEngine', $true)::LoadCommands($binRoot)

# Initialization (extension loading, type accelerators) and cleanup on Remove-Module (session,
# extension loader, accelerators) are handled inside the binary by ModuleInitializer
# (IModuleAssemblyInitializer, IModuleAssemblyCleanup).
#
# The engine (and with it this assembly) outlives Remove-Module, and so does the binary module an
# earlier import created from it: PowerShell keeps it loaded and a second Import-Module -Assembly
# from this psm1 then imports none of its cmdlets into the new module. Removing that leftover
# first (its IModuleAssemblyCleanup resets the module state) makes every import start clean.
Get-Module -All |
    Where-Object { $_.ModuleType -eq 'Binary' -and $_.ImplementingAssembly -eq $commandsAssembly } |
    Remove-Module -Force
Import-Module -Assembly $commandsAssembly