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 |