Functions/GenXdev.Helpers/EnsureNuGetAssembly.ps1
############################################################################### <# .SYNOPSIS Downloads and loads .NET assemblies from NuGet packages based on package key or ID. .DESCRIPTION This function ensures that the specified NuGet package assemblies are downloaded and loaded into the current PowerShell session. It supports automatic detection and loading with fallback to a JSON manifest for configuration overrides, handling edge cases like stub dependencies (e.g., SQLite). Installation uses the dotnet CLI exclusively for reliable package management and loads assemblies directly from the global NuGet cache without copying. Lazy loading checks if the type is already available before proceeding. .PARAMETER PackageKey The unique identifier for the package, either a key from the packages.json manifest or the direct NuGet PackageId. .PARAMETER ManifestPath Optional path to the packages.json manifest. Defaults to module root if omitted. .PARAMETER Version Optional specific version to install and load. If omitted, uses highest from JSON or latest from NuGet. .PARAMETER TypeName Optional .NET type name to verify after loading (e.g., 'Microsoft.Data.Sqlite.SQLiteConnection'). .PARAMETER ForceLatest Switch to fallback to latest version if specified version fails. .PARAMETER Destination Optional custom destination path for package installation. Defaults to persistent local or global cache. .EXAMPLE EnsureNuGetAssembly -PackageKey "Microsoft.Data.Sqlite.Core" .EXAMPLE EnsureNuGetAssembly -PackageKey "Microsoft.Playwright" -Version "1.54.0" -TypeName "Microsoft.Playwright.Playwright" #> function EnsureNuGetAssembly { [CmdletBinding()] param( [Parameter(Mandatory = $true, Position = 0, HelpMessage = "Package key from packages.json or direct NuGet PackageId.")] [string] $PackageKey, [Parameter(Mandatory = $false, HelpMessage = "Path to packages.json; defaults to module root if omitted.")] [string] $ManifestPath, [Parameter(Mandatory = $false, HelpMessage = "Specific version; if omitted, use highest from JSON or latest.")] [string] $Version, [Parameter(Mandatory = $false, HelpMessage = "TypeName to verify loading.")] [string] $TypeName, [Parameter(Mandatory = $false, HelpMessage = "Fallback to latest if exact version fails.")] [switch] $ForceLatest, [Parameter(Mandatory = $false, HelpMessage = "Custom install destination; defaults to local persistent or global cache.")] [string] $Destination ) begin { # Lazy check if type already loaded if ($TypeName -and ($TypeName -as [type])) { return } # Fully qualified cmdlet usage for explicitness Microsoft.PowerShell.Utility\Write-Verbose "Starting EnsureNuGetAssembly for ${PackageKey}" # Preferred TFMs for .NET 9+ (PowerShell 7.5+) $frameworkPreference = @( 'net9.0', 'net8.0', 'net7.0', 'net6.0', 'netstandard2.1', 'netstandard2.0' ) # Add older fallbacks from old function $frameworkFallback = @( 'netcoreapp3.1', 'net461', 'net46', 'net451', 'net45' ) # Module root using custom helper $moduleRoot = GenXdev.FileSystem\Expand-Path "$PSScriptRoot\..\..\" # Default manifest path if not provided if (-not $ManifestPath) { $ManifestPath = Microsoft.PowerShell.Management\Join-Path $moduleRoot 'packages.json' } Microsoft.PowerShell.Utility\Write-Verbose "Using manifest path: ${ManifestPath}" # Default destination: persistent local as in old if (-not $Destination) { $Destination = Microsoft.PowerShell.Management\Join-Path $env:LOCALAPPDATA 'GenXdev.Powershell\NuGetPackages' Microsoft.PowerShell.Utility\Write-Verbose "Creating default destination directory: ${Destination}" $null = Microsoft.PowerShell.Management\New-Item -Path $Destination -ItemType Directory -Force } else { Microsoft.PowerShell.Utility\Write-Verbose "Using provided destination: ${Destination}" } # Global cache fallback path (cross-platform) $globalCache = if ($env:OS -eq 'Windows_NT') { "$env:USERPROFILE\.nuget\packages" } else { "$env:HOME/.nuget/packages" } Microsoft.PowerShell.Utility\Write-Verbose "Global cache path: ${globalCache}" # Load JSON if exists $packages = $null $pkgConfig = $null if (Microsoft.PowerShell.Management\Test-Path $ManifestPath) { Microsoft.PowerShell.Utility\Write-Verbose "Manifest file exists; loading content from ${ManifestPath}" try { $packages = Microsoft.PowerShell.Management\Get-Content $ManifestPath | Microsoft.PowerShell.Utility\ConvertFrom-Json -AsHashtable Microsoft.PowerShell.Utility\Write-Verbose "Successfully parsed manifest JSON" } catch { throw "Failed to parse manifest at ${ManifestPath}: $($_.Exception.Message)" } # Find config by key or PackageId $actualKey = $PackageKey if ($packages.ContainsKey($PackageKey)) { $pkgConfig = $packages[$PackageKey] } else { foreach ($key in $packages.Keys) { if ($packages[$key].PackageId -eq $PackageKey) { $actualKey = $key $pkgConfig = $packages[$key] break } } } if ($pkgConfig) { Microsoft.PowerShell.Utility\Write-Verbose "Found JSON config for ${actualKey}" } else { Microsoft.PowerShell.Utility\Write-Verbose "No JSON config found for ${PackageKey}; treating as direct PackageId" } } else { Microsoft.PowerShell.Utility\Write-Verbose "Manifest path does not exist: ${ManifestPath}; proceeding without JSON config" } # Determine PackageId $PackageId = if ($pkgConfig -and $pkgConfig.PackageId) { $pkgConfig.PackageId } else { $PackageKey } Microsoft.PowerShell.Utility\Write-Verbose "Determined PackageId: ${PackageId}" # Version determination if (-not $Version) { if ($pkgConfig -and $pkgConfig.Versions) { # Use highest version from JSON $Version = ($pkgConfig.Versions.Keys | Microsoft.PowerShell.Utility\Sort-Object -Descending) | Microsoft.PowerShell.Utility\Select-Object -First 1 Microsoft.PowerShell.Utility\Write-Verbose "Using highest JSON version: ${Version}" } elseif ($pkgConfig -and $pkgConfig.Version) { $Version = $pkgConfig.Version Microsoft.PowerShell.Utility\Write-Verbose "Using JSON version: ${Version}" } elseif ($pkgConfig -and $pkgConfig.DefaultVersion) { $Version = $pkgConfig.DefaultVersion Microsoft.PowerShell.Utility\Write-Verbose "Using JSON default version: ${Version}" } else { # Find latest version using dotnet list command Microsoft.PowerShell.Utility\Write-Verbose "No version specified or in JSON; querying latest from NuGet using dotnet" try { $null = & dotnet list package --outdated --include-prerelease --source https://api.nuget.org/v3/index.json 2>$null # For simplicity, we'll use a default "latest" approach via dotnet add package without version $Version = "latest" Microsoft.PowerShell.Utility\Write-Verbose "Will use latest version available" } catch { Microsoft.PowerShell.Utility\Write-Warning "Could not determine latest version, will use dotnet's default behavior" $Version = "latest" } } } else { Microsoft.PowerShell.Utility\Write-Verbose "Using provided version: ${Version}" } # Version-specific config from JSON $versionConfig = if ($pkgConfig -and $pkgConfig.Versions -and $Version -and $pkgConfig.Versions.ContainsKey($Version)) { $pkgConfig.Versions[$Version] } else { $pkgConfig } Microsoft.PowerShell.Utility\Write-Verbose "Version-specific config loaded (if any)" # Assemblies from JSON or default $Assemblies = if ($versionConfig -and $versionConfig.Assemblies) { $versionConfig.Assemblies } else { @("$PackageId.dll") } Microsoft.PowerShell.Utility\Write-Verbose "Assemblies to load: $($Assemblies -join ', ')" # Native copy from JSON $nativeCopy = if ($versionConfig -and $versionConfig.NativeCopy) { $versionConfig.NativeCopy } if ($nativeCopy) { Microsoft.PowerShell.Utility\Write-Verbose "Native copy config found: From $($nativeCopy.From) to $($nativeCopy.To)" } else { Microsoft.PowerShell.Utility\Write-Verbose "No native copy config" } # RID for natives $rid = [System.Runtime.InteropServices.RuntimeInformation]::RuntimeIdentifier Microsoft.PowerShell.Utility\Write-Verbose "Runtime Identifier (RID): ${rid}" # TLS fix for retries Microsoft.PowerShell.Utility\Write-Verbose "Setting SecurityProtocol to Tls12" [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 # Check and install dotnet SDK if missing (for fallback) Microsoft.PowerShell.Utility\Write-Verbose "Checking for dotnet CLI" if (-not (Microsoft.PowerShell.Core\Get-Command dotnet -ErrorAction SilentlyContinue)) { Microsoft.PowerShell.Utility\Write-Host "dotnet CLI not found; attempting install via winget if available..." if (Microsoft.PowerShell.Core\Get-Command winget -ErrorAction SilentlyContinue) { Microsoft.PowerShell.Utility\Write-Verbose "Invoking winget install Microsoft.DotNet.SDK.9 --accept-package-agreements --accept-source-agreements" winget install Microsoft.DotNet.SDK.9 --accept-package-agreements --accept-source-agreements # Reload PATH after install Microsoft.PowerShell.Utility\Write-Verbose "Reloading PATH environment variable" $env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User") Microsoft.PowerShell.Utility\Write-Verbose "PATH reloaded" } else { throw "dotnet CLI required for fallback but not found, and winget not available for auto-install. Please install .NET SDK 9+ manually from https://dotnet.microsoft.com/download/dotnet/9.0" } # Re-check after install if (-not (Microsoft.PowerShell.Core\Get-Command dotnet -ErrorAction SilentlyContinue)) { throw "dotnet install failed; manual installation required." } Microsoft.PowerShell.Utility\Write-Verbose "dotnet CLI installed successfully" } else { Microsoft.PowerShell.Utility\Write-Verbose "dotnet CLI already available" } # Loaded tracking as in old $loadedAssemblies = @() } process { # Lazy check if type already loaded if ($TypeName -and ($TypeName -as [type])) { Microsoft.PowerShell.Utility\Write-Verbose "${TypeName} already loaded; skipping" return } try { # Package dir - use local destination, not global cache $packageLower = $PackageId.ToLower() # Find existing package installation (if any) when version is "latest" $pkgDir = $null $installed = $false if ($Version -eq "latest") { # Look for existing main package first, then fallback to any related package $mainPkgs = Microsoft.PowerShell.Management\Get-ChildItem $Destination -Directory | Microsoft.PowerShell.Core\Where-Object { $_.Name -match "^${packageLower}\.\d+(\.\d+)*(\.\d+)*$" } | Microsoft.PowerShell.Utility\Sort-Object Name -Descending if ($mainPkgs) { $pkgDir = $mainPkgs[0].FullName Microsoft.PowerShell.Utility\Write-Verbose "Found existing main package: ${pkgDir}" } else { # Fallback to any related package $existingPkgs = Microsoft.PowerShell.Management\Get-ChildItem $Destination -Directory | Microsoft.PowerShell.Core\Where-Object { $_.Name -like "${packageLower}.*" } | Microsoft.PowerShell.Utility\Sort-Object Name -Descending if ($existingPkgs) { $pkgDir = $existingPkgs[0].FullName Microsoft.PowerShell.Utility\Write-Verbose "Found existing related package: ${pkgDir}" } } } else { # Use specific version directory $pkgDir = Microsoft.PowerShell.Management\Join-Path $Destination "$packageLower.$Version" } if (-not $pkgDir) { $pkgDir = Microsoft.PowerShell.Management\Join-Path $Destination "$packageLower.$Version" } Microsoft.PowerShell.Utility\Write-Verbose "Package directory (local destination): ${pkgDir}" # Check if properly installed with validation if (Microsoft.PowerShell.Management\Test-Path $pkgDir) { Microsoft.PowerShell.Utility\Write-Verbose "Package directory exists at ${pkgDir}, validating completeness..." # For stub-based packages like SQLite, check if assemblies can be found and loaded # rather than checking if they exist in the main package directory $allAssembliesFound = $true $deps = Microsoft.PowerShell.Management\Get-ChildItem $Destination -Directory -ErrorAction SilentlyContinue | Microsoft.PowerShell.Core\Where-Object { $_.Name -match '\.[\d\.]+$' } if (-not $deps) { $deps = @() } foreach ($asm in $Assemblies) { $found = $false # Check main package $directPath = Microsoft.PowerShell.Management\Join-Path $pkgDir $asm if (Microsoft.PowerShell.Management\Test-Path $directPath) { $found = $true continue } # Check in lib subdirectories of main package $libDir = Microsoft.PowerShell.Management\Join-Path $pkgDir "lib" if (Microsoft.PowerShell.Management\Test-Path $libDir) { $libSubDirs = Microsoft.PowerShell.Management\Get-ChildItem $libDir -Directory -ErrorAction SilentlyContinue foreach ($subDir in $libSubDirs) { $asmPath = Microsoft.PowerShell.Management\Join-Path $subDir.FullName $asm if (Microsoft.PowerShell.Management\Test-Path $asmPath) { $found = $true break } } } # Check in dependencies/stub packages if (-not $found) { foreach ($dep in $deps) { $depAsm = Microsoft.PowerShell.Management\Get-ChildItem $dep.FullName -Recurse -Filter $asm.Split('/')[-1] -ErrorAction SilentlyContinue | Microsoft.PowerShell.Utility\Select-Object -First 1 if ($depAsm) { $found = $true Microsoft.PowerShell.Utility\Write-Verbose "Assembly ${asm} found in dependency ${dep.Name}" break } } } if (-not $found) { Microsoft.PowerShell.Utility\Write-Verbose "Assembly ${asm} not found in package or dependencies" $allAssembliesFound = $false break } } if ($allAssembliesFound) { $installed = $true Microsoft.PowerShell.Utility\Write-Verbose "Package validation successful - all assemblies found" } else { Microsoft.PowerShell.Utility\Write-Verbose "Package validation failed - missing assemblies" } } else { Microsoft.PowerShell.Utility\Write-Verbose "Package directory does not exist; proceeding to install" } # Lazy check if type already loaded if ($TypeName -and ($TypeName -as [type])) { Microsoft.PowerShell.Utility\Write-Verbose "${TypeName} already loaded; skipping" return } else { if ($TypeName) { Microsoft.PowerShell.Utility\Write-Verbose "${TypeName} not loaded; proceeding" } } if (-not $installed) { Microsoft.PowerShell.Utility\Write-Host "Installing ${PackageId} ${Version}..." # Install using dotnet CLI $tempDir = Microsoft.PowerShell.Management\Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid()) Microsoft.PowerShell.Utility\Write-Verbose "Creating temporary directory for dotnet installation: ${tempDir}" Microsoft.PowerShell.Management\New-Item -Path $tempDir -ItemType Directory -Force | Microsoft.PowerShell.Core\Out-Null Microsoft.PowerShell.Utility\Write-Verbose "Pushing location to ${tempDir}" Microsoft.PowerShell.Management\Push-Location $tempDir try { Microsoft.PowerShell.Utility\Write-Verbose "Invoking dotnet new classlib -n dummy --force" & dotnet new classlib -n dummy --force | Microsoft.PowerShell.Core\Out-Null Microsoft.PowerShell.Management\Set-Location dummy $actualVersion = $Version if ($Version -eq "latest") { $addCmd = "dotnet add package $PackageId" Microsoft.PowerShell.Utility\Write-Verbose "Invoking ${addCmd}" & dotnet add package $PackageId | Microsoft.PowerShell.Core\Out-Null } else { $addCmd = "dotnet add package $PackageId --version $Version" Microsoft.PowerShell.Utility\Write-Verbose "Invoking ${addCmd}" & dotnet add package $PackageId --version $Version | Microsoft.PowerShell.Core\Out-Null } # Always discover the actual installed version from project.assets.json $objDir = Microsoft.PowerShell.Management\Join-Path (Microsoft.PowerShell.Management\Get-Location) "obj" $assetsFile = Microsoft.PowerShell.Management\Join-Path $objDir "project.assets.json" if (Microsoft.PowerShell.Management\Test-Path $assetsFile) { $assets = Microsoft.PowerShell.Management\Get-Content $assetsFile | Microsoft.PowerShell.Utility\ConvertFrom-Json if ($assets.libraries) { foreach ($libName in $assets.libraries.PSObject.Properties.Name) { $parts = $libName -split '/' if ($parts.Length -eq 2 -and $parts[0].ToLower() -eq $packageLower) { $rawVersion = $parts[1] $actualVersion = $rawVersion Microsoft.PowerShell.Utility\Write-Verbose "Discovered installed version: ${actualVersion}" break } } } } if (-not $actualVersion -or $actualVersion -eq "latest") { throw "Could not determine actual version installed for ${PackageId}" } Microsoft.PowerShell.Utility\Write-Verbose "Invoking dotnet restore" & dotnet restore | Microsoft.PowerShell.Core\Out-Null # Copy main package and all dependencies from global cache to local destination $cacheDir = Microsoft.PowerShell.Management\Join-Path $globalCache "$packageLower\$actualVersion" Microsoft.PowerShell.Utility\Write-Verbose "Checking main package cache directory: ${cacheDir}" if (-not (Microsoft.PowerShell.Management\Test-Path $cacheDir)) { throw "Package not found in global cache at: ${cacheDir}" } if ($cacheDir) { # Calculate the correct target directory name using actual version $targetPkgDir = Microsoft.PowerShell.Management\Join-Path $Destination "$packageLower.$actualVersion" # Remove target directory if it exists to avoid nested structure if (Microsoft.PowerShell.Management\Test-Path -LiteralPath $targetPkgDir) { Microsoft.PowerShell.Management\Remove-Item -LiteralPath $targetPkgDir -Recurse -Force -ErrorAction SilentlyContinue } Microsoft.PowerShell.Utility\Write-Verbose "Copying main package from ${cacheDir} to ${targetPkgDir}" Microsoft.PowerShell.Management\Copy-Item $cacheDir -Destination $targetPkgDir -Recurse -Force Microsoft.PowerShell.Utility\Write-Verbose "Main package copied to ${targetPkgDir}" # Update pkgDir to the actual location after copying $pkgDir = $targetPkgDir # Also copy dependencies by parsing the project assets if (Microsoft.PowerShell.Management\Test-Path -LiteralPath $assetsFile) { Microsoft.PowerShell.Utility\Write-Verbose "Reading project assets for dependencies: ${assetsFile}" $assets = Microsoft.PowerShell.Management\Get-Content -LiteralPath $assetsFile | Microsoft.PowerShell.Utility\ConvertFrom-Json if ($assets.libraries) { foreach ($libName in $assets.libraries.PSObject.Properties.Name) { if ($libName -ne "$packageLower/$actualVersion") { $parts = $libName -split '/' if ($parts.Length -eq 2) { $depName = $parts[0].ToLower() $depVersion = $parts[1] $depCacheDir = Microsoft.PowerShell.Management\Join-Path $globalCache "$depName\$depVersion" $depDestDir = Microsoft.PowerShell.Management\Join-Path $Destination "$depName.$depVersion" if ((Microsoft.PowerShell.Management\Test-Path $depCacheDir) -and (-not (Microsoft.PowerShell.Management\Test-Path $depDestDir))) { Microsoft.PowerShell.Utility\Write-Verbose "Copying dependency ${depName} ${depVersion} from cache" Microsoft.PowerShell.Management\Copy-Item $depCacheDir -Destination $Destination -Recurse -Force $copiedDepDir = Microsoft.PowerShell.Management\Join-Path $Destination (Microsoft.PowerShell.Management\Split-Path $depCacheDir -Leaf) if (Microsoft.PowerShell.Management\Test-Path $copiedDepDir) { Microsoft.PowerShell.Management\Rename-Item $copiedDepDir $depDestDir -Force -ErrorAction SilentlyContinue } } } } } } } } else { throw "Cache directory not found: ${cacheDir}" } } finally { # Store assets file content before cleaning up temp directory Microsoft.PowerShell.Management\Pop-Location Microsoft.PowerShell.Utility\Write-Verbose "Cleaning up temp directory: ${tempDir}" Microsoft.PowerShell.Management\Remove-Item $tempDir -Recurse -Force -ErrorAction SilentlyContinue } if (-not (Microsoft.PowerShell.Management\Test-Path $pkgDir)) { throw "Package not found at ${pkgDir}" } else { Microsoft.PowerShell.Utility\Write-Verbose "Package confirmed at ${pkgDir}" } } # Parse .nuspec for files (medium priority, after install) $nuspecPath = Microsoft.PowerShell.Management\Join-Path $pkgDir "$PackageId.nuspec" Microsoft.PowerShell.Utility\Write-Verbose "Checking for .nuspec at ${nuspecPath}" $nuspecFiles = @() if (Microsoft.PowerShell.Management\Test-Path $nuspecPath) { Microsoft.PowerShell.Utility\Write-Verbose "Loading .nuspec XML" $nuspecXml = [xml](Microsoft.PowerShell.Management\Get-Content $nuspecPath) $nuspecFiles = $nuspecXml.package.files.file | Microsoft.PowerShell.Utility\Select-Object -ExpandProperty target Microsoft.PowerShell.Utility\Write-Verbose "Extracted $($nuspecFiles.Count) file targets from .nuspec" } else { Microsoft.PowerShell.Utility\Write-Verbose "No .nuspec found" } # Get deps for stub search - look in local destination Microsoft.PowerShell.Utility\Write-Verbose "Searching for dependent packages in local destination: ${Destination}" try { $allPkgs = Microsoft.PowerShell.Management\Get-ChildItem $Destination -Directory -ErrorAction SilentlyContinue | Microsoft.PowerShell.Core\Where-Object { $_.Name -match '\.[\d\.]+$' } if (-not $allPkgs) { $allPkgs = @() } } catch { Microsoft.PowerShell.Utility\Write-Verbose "Failed to get dependent packages: $($_.Exception.Message)" $allPkgs = @() } Microsoft.PowerShell.Utility\Write-Verbose "Found $($allPkgs.Count) dependent packages" # Skip AssemblyResolve handler to prevent stack overflow issues # Will be re-enabled once recursion issues are fully resolved Microsoft.PowerShell.Utility\Write-Verbose "Skipping AssemblyResolve event handler to prevent recursion" # Load assemblies $script:resolveDirs = @() foreach ($asm in $Assemblies) { $asmPath = $null $searched = @() # JSON relative path $asmFullPath = Microsoft.PowerShell.Management\Join-Path $pkgDir $asm Microsoft.PowerShell.Utility\Write-Verbose "Checking JSON relative path for ${asm}: ${asmFullPath}" if (Microsoft.PowerShell.Management\Test-Path $asmFullPath) { $asmPath = $asmFullPath Microsoft.PowerShell.Utility\Write-Verbose "Found at JSON relative path" } else { # Auto TFM scan foreach ($fw in $frameworkPreference + $frameworkFallback) { $libPath = Microsoft.PowerShell.Management\Join-Path $pkgDir "lib\$fw" $searched += $libPath Microsoft.PowerShell.Utility\Write-Verbose "Scanning TFM path: ${libPath}" if (Microsoft.PowerShell.Management\Test-Path $libPath) { $foundAsm = Microsoft.PowerShell.Management\Get-ChildItem $libPath -Filter $asm.Split('/')[-1] -Recurse | Microsoft.PowerShell.Utility\Select-Object -First 1 if ($foundAsm) { $asmPath = $foundAsm.FullName Microsoft.PowerShell.Utility\Write-Verbose "Found in TFM ${fw} at ${asmPath}" break } } } # Fallback recursive (as in old) if (-not $asmPath) { Microsoft.PowerShell.Utility\Write-Verbose "Performing recursive search in ${pkgDir} for ${asm.Split('/')[-1]}" $asmPath = Microsoft.PowerShell.Management\Get-ChildItem $pkgDir -Recurse -Filter $asm.Split('/')[-1] | Microsoft.PowerShell.Utility\Select-Object -First 1 if ($asmPath) { $searched += "recursive in ${pkgDir}" Microsoft.PowerShell.Utility\Write-Verbose "Found in recursive search at ${asmPath.FullName}" } } # Search deps/stubs in local destination if (-not $asmPath) { Microsoft.PowerShell.Utility\Write-Verbose "Searching dependencies for ${asm.Split('/')[-1]}" foreach ($dep in $allPkgs) { $depAsm = Microsoft.PowerShell.Management\Get-ChildItem $dep.FullName -Recurse -Filter $asm.Split('/')[-1] | Microsoft.PowerShell.Utility\Select-Object -First 1 if ($depAsm) { $asmPath = $depAsm.FullName Microsoft.PowerShell.Utility\Write-Verbose "Found in dependency ${dep.Name} at ${asmPath}" break } $searched += "dep ${dep.Name}" } } } if ($asmPath) { try { Microsoft.PowerShell.Utility\Write-Verbose "Loading assembly with Add-Type -Path ${asmPath}" Microsoft.PowerShell.Utility\Add-Type -Path $asmPath $script:resolveDirs += Microsoft.PowerShell.Management\Split-Path $asmPath -Parent $loadedAssemblies += $asmPath Microsoft.PowerShell.Utility\Write-Verbose "Loaded $asm from ${asmPath}" } catch { throw "Load failed for ${asm}: $($_.Exception.Message). Searched: $($searched -join ', ')" } } else { throw "Assembly $asm not found. Searched: $($searched -join ', ')" } } # Native handling - copy native dependencies to managed assembly directories AFTER loading assemblies # Collect all directories where managed assemblies are loaded $managedAssemblyDirs = @() foreach ($asmPath in $loadedAssemblies) { $asmDir = Microsoft.PowerShell.Management\Split-Path $asmPath -Parent if ($managedAssemblyDirs -notcontains $asmDir) { $managedAssemblyDirs += $asmDir } } # If no managed assemblies loaded, fall back to script directory if ($managedAssemblyDirs.Count -eq 0) { $managedAssemblyDirs = @($PSScriptRoot) } # Collect all native files we need to deploy $nativeFileSources = @() # Check main package for native files $nativeDir = Microsoft.PowerShell.Management\Join-Path $pkgDir "runtimes\$rid\native" if (Microsoft.PowerShell.Management\Test-Path $nativeDir) { $nativeFiles = Microsoft.PowerShell.Management\Get-ChildItem $nativeDir -File foreach ($nativeFile in $nativeFiles) { $nativeFileSources += @{ SourcePath = $nativeFile.FullName FileName = $nativeFile.Name Source = "main package" } } } # Check stub packages for native files $stubPattern = "stub.$($packageLower)*" $stubPkgs = Microsoft.PowerShell.Management\Get-ChildItem $Destination -Directory | Microsoft.PowerShell.Core\Where-Object { $_.Name -like $stubPattern } foreach ($stubPkg in $stubPkgs) { $stubNativeDir = Microsoft.PowerShell.Management\Join-Path $stubPkg.FullName "runtimes\$rid\native" if (Microsoft.PowerShell.Management\Test-Path $stubNativeDir) { $nativeFiles = Microsoft.PowerShell.Management\Get-ChildItem $stubNativeDir -File foreach ($nativeFile in $nativeFiles) { $nativeFileSources += @{ SourcePath = $nativeFile.FullName FileName = $nativeFile.Name Source = "stub package $($stubPkg.Name)" } } } } # Check dependencies for native files foreach ($dep in $allPkgs) { $depNativeDir = Microsoft.PowerShell.Management\Join-Path $dep.FullName "runtimes\$rid\native" if (Microsoft.PowerShell.Management\Test-Path $depNativeDir) { $nativeFiles = Microsoft.PowerShell.Management\Get-ChildItem $depNativeDir -File foreach ($nativeFile in $nativeFiles) { $nativeFileSources += @{ SourcePath = $nativeFile.FullName FileName = $nativeFile.Name Source = "dependency $($dep.Name)" } } } } # Deploy native files to all managed assembly directories if ($nativeFileSources.Count -gt 0) { Microsoft.PowerShell.Utility\Write-Verbose "Found $($nativeFileSources.Count) native files to deploy" foreach ($targetDir in $managedAssemblyDirs) { Microsoft.PowerShell.Utility\Write-Verbose "Deploying native dependencies to managed assembly directory: ${targetDir}" foreach ($nativeSource in $nativeFileSources) { $targetPath = Microsoft.PowerShell.Management\Join-Path $targetDir $nativeSource.FileName # Only copy if target doesn't exist or source is newer $shouldCopy = $true if (Microsoft.PowerShell.Management\Test-Path $targetPath) { $sourceLastWrite = (Microsoft.PowerShell.Management\Get-Item $nativeSource.SourcePath).LastWriteTime $targetLastWrite = (Microsoft.PowerShell.Management\Get-Item $targetPath).LastWriteTime $shouldCopy = $sourceLastWrite -gt $targetLastWrite } if ($shouldCopy) { Microsoft.PowerShell.Utility\Write-Verbose "Copying $($nativeSource.FileName) from $($nativeSource.Source) to ${targetDir}" Microsoft.PowerShell.Management\Copy-Item $nativeSource.SourcePath $targetPath -Force } else { Microsoft.PowerShell.Utility\Write-Verbose "Skipping $($nativeSource.FileName) - already up to date in ${targetDir}" } } } } else { Microsoft.PowerShell.Utility\Write-Verbose "No native dependencies required" } # Verify type if ($TypeName -and -not ($TypeName -as [type])) { throw "${TypeName} not loaded" } elseif ($TypeName) { Microsoft.PowerShell.Utility\Write-Verbose "${TypeName} verified as loaded" } } catch { Microsoft.PowerShell.Utility\Write-Warning "Failure; preserving ${pkgDir} for debug" throw } } end { Microsoft.PowerShell.Utility\Write-Verbose "Ensured ${PackageKey} with $($loadedAssemblies.Count) assemblies" } } ############################################################################### |