Public/Import-DPLibrary.ps1

function Import-DPLibrary {
    <#
    .SYNOPSIS
    Import DLLPickle dependency libraries.
 
    .DESCRIPTION
    Import all relevant DLL files for the appropriate target framework and PowerShell edition.
 
    The latest versions of all dependencies are automatically imported, providing
    backwards compatibility and avoiding version conflicts.
 
    .PARAMETER SkipProblematicAssemblies
    When running in Windows PowerShell, skip assemblies known to have compatibility issues
    with .NET Framework 4.8. This prevents warning messages while still loading all
    compatible dependencies.
 
    .PARAMETER ShowLoaderExceptions
    Display detailed loader exception information when an assembly fails to load.
    This is useful for diagnosing why specific types within an assembly cannot be loaded.
 
    .INPUTS
    None. This function does not accept pipeline input.
 
    .EXAMPLE
    Import-DPLibrary
    Imports all dependency DLLs from the appropriate TFM directory.
 
    .EXAMPLE
    Import-DPLibrary -SkipProblematicAssemblies
    Imports compatible DLLs and skips known problematic assemblies in Windows PowerShell.
 
    .EXAMPLE
    Import-DPLibrary -ShowLoaderExceptions
    Imports DLLs and displays detailed diagnostic information for any failures.
 
    .OUTPUTS
    System.Management.Automation.PSCustomObject
    Returns information about each imported DLL.
 
    .NOTES
    Some assemblies may have partial compatibility issues in Windows PowerShell due to
    dependencies on types not available in .NET Framework 4.8. The function will continue
    loading other assemblies and provide detailed diagnostic information about failures.
 
    A status of 'Imported' indicates the assembly file was successfully loaded by the runtime,
    but this does not guarantee that all types within the assembly are usable. Some types may
    remain unavailable due to unresolved transitive dependencies.
    #>


    [CmdletBinding()]
    param (
        [Parameter()]
        [switch]$SkipProblematicAssemblies,

        [Parameter()]
        [switch]$ShowLoaderExceptions
    )

    # Determine the module's base directory.
    $ModuleDirectory = if ($PSModuleRoot) {
        $PSModuleRoot
    } elseif ($PSScriptRoot) {
        Split-Path -Path $PSScriptRoot -Parent
    } else {
        $PWD
    }

    # Determine the appropriate target framework moniker (TFM) based on PowerShell edition.
    $TargetFramework = if ($PSEdition -eq 'Core') {
        'net8.0'
    } else {
        'net48'
    }

    $BinDirectory = Join-Path -Path $ModuleDirectory -ChildPath 'bin'
    $TFMDirectory = Join-Path -Path $BinDirectory -ChildPath $TargetFramework

    Write-Verbose "Using PowerShell edition: $PSEdition"
    Write-Verbose "Using target framework: $TargetFramework"
    Write-Verbose "DLL directory: $TFMDirectory"

    if (-not (Test-Path -Path $TFMDirectory)) {
        throw "Binary directory not found for target framework '$TargetFramework' at: $TFMDirectory"
    }

    <# About Problematic Assemblies in Windows PowerShell
    There are some known problematic assemblies in Windows PowerShell (.NET Framework 4.8). These assemblies may contain
    types that depend on APIs not available in .NET Framework 4.8, leading to loader exceptions for those types. Skipping
    these assemblies prevents warnings while still loading all compatible dependencies.
    #>

    $ProblematicAssemblies = @(
        'Microsoft.Identity.Client.dll'
        'System.Diagnostics.DiagnosticSource.dll'
    )

    # Get all DLL files in the TFM directory. If no DLLs are found, throw an error to alert the user about potential installation issues.
    $DLLFiles = @(Get-ChildItem -Path $TFMDirectory -Filter '*.dll' -File -Recurse -ErrorAction Stop)
    if (-not $DLLFiles -or $DLLFiles.Count -eq 0) {
        throw "No DLL files found in '$TFMDirectory'. Ensure that the module is properly installed and the bin directory contains the expected assemblies."
    }

    # Filter out problematic assemblies if requested and if running in Windows PowerShell.
    if ($SkipProblematicAssemblies -and $PSEdition -ne 'Core') {
        $OriginalCount = $DLLFiles.Count
        $DLLFiles = $DLLFiles | Where-Object { $_.Name -notin $ProblematicAssemblies }
        $SkippedCount = $OriginalCount - $DLLFiles.Count
        if ($SkippedCount -gt 0) {
            Write-Verbose "Skipped $SkippedCount known problematic assemblies in Windows PowerShell: $($ProblematicAssemblies -join ', ')"
        }
    }

    # Import each DLL and record the results.
    $Results = foreach ($DLLFile in $DLLFiles) {
        $FilePath = $DLLFile.FullName

        try {
            # Check if assembly is already loaded
            $AssemblyName = [System.Reflection.AssemblyName]::GetAssemblyName($FilePath)
            $LoadedAssembly = [System.AppDomain]::CurrentDomain.GetAssemblies() |
                Where-Object { $_.GetName().Name -eq $AssemblyName.Name -and $_.GetName().Version -eq $AssemblyName.Version }

            if ($LoadedAssembly) {
                Write-Verbose "Assembly already loaded: $($DLLFile.BaseName)"
                [PSCustomObject]@{
                    PSTypeName      = 'DLLPickle.ImportDPLibraryResult'
                    DLLName         = $DLLFile.Name
                    AssemblyName    = $AssemblyName.Name
                    AssemblyVersion = $AssemblyName.Version.ToString()
                    Status          = 'Already Loaded'
                    Error           = $null
                }
            } else {
                Add-Type -Path $FilePath
                Write-Verbose "Successfully imported: $($DLLFile.BaseName)"
                [PSCustomObject]@{
                    PSTypeName      = 'DLLPickle.ImportDPLibraryResult'
                    DLLName         = $DLLFile.Name
                    AssemblyName    = $AssemblyName.Name
                    AssemblyVersion = $AssemblyName.Version.ToString()
                    Status          = 'Imported'
                    Error           = $null
                }
            }
        } catch [System.Reflection.ReflectionTypeLoadException] {
            # Handle ReflectionTypeLoadException specifically to provide detailed diagnostics
            $LoaderExceptions = $_.Exception.LoaderExceptions
            $ErrorMessage = $_.Exception.Message

            # Build detailed error information
            if ($ShowLoaderExceptions -and $LoaderExceptions) {
                Write-Warning "Failed to import $($DLLFile.Name): $ErrorMessage"
                Write-Warning "Loader Exceptions ($($LoaderExceptions.Count) total):"
                foreach ($LoaderException in $LoaderExceptions | Select-Object -First 5) {
                    Write-Warning " - $($LoaderException.Message)"
                }
                if ($LoaderExceptions.Count -gt 5) {
                    Write-Warning " ... and $($LoaderExceptions.Count - 5) more exceptions"
                }
            } else {
                Write-Warning "Failed to import $($DLLFile.Name): $ErrorMessage"
                if (-not $ShowLoaderExceptions) {
                    Write-Verbose 'Use -ShowLoaderExceptions to see detailed loader exception information'
                }
            }

            [PSCustomObject]@{
                PSTypeName      = 'DLLPickle.ImportDPLibraryResult'
                DLLName         = $DLLFile.Name
                AssemblyName    = $null
                AssemblyVersion = $null
                Status          = 'Failed'
                Error           = $ErrorMessage
            }
        } catch {
            Write-Warning "Failed to import $($DLLFile.Name): $_"
            [PSCustomObject]@{
                PSTypeName      = 'DLLPickle.ImportDPLibraryResult'
                DLLName         = $DLLFile.Name
                AssemblyName    = $null
                AssemblyVersion = $null
                Status          = 'Failed'
                Error           = $_.Exception.Message
            }
        }
    }

    $Results
}