Private/Invoke-DPConflictCheck.ps1

function Invoke-DPConflictCheck {
    <#
    .SYNOPSIS
        After preload, warns about (or arms a best-effort one-shot warning for) known incompatible
        module pairs.
    .DESCRIPTION
        For each known conflict: if every module is already loaded, warn immediately. Otherwise, if
        every module is installed (so the clash can still happen later), register a single AssemblyLoad
        handler that warns only once every module in the pair has actually been co-loaded, then
        unregisters itself.
 
        Best-effort and advisory: it is fully guarded and never throws, is skipped under Constrained
        Language Mode, and warns at most once per conflict per session. It cannot pre-empt a module
        whose import fails outright before any of its assemblies load (a rejected load raises no
        AssemblyLoad event), and it keys on imported modules, so a module removed with Remove-Module
        after its assemblies are already resident is not re-detected. Test-DPLibraryConflict is the
        reliable on-demand check; the authoritative protection is the separate-process workaround.
    .PARAMETER KnownConflictsPath
        Optional override for the knownConflicts file (testing). Defaults to the shipped file.
    #>

    [CmdletBinding()]
    param(
        [Parameter()]
        [string]$KnownConflictsPath
    )

    process {
        try {
            if ($ExecutionContext.SessionState.LanguageMode -eq [System.Management.Automation.PSLanguageMode]::ConstrainedLanguage) {
                Write-Verbose 'Constrained Language Mode: skipping conflict-watch arming.'
                return
            }

            $Conflicts = Get-DPKnownConflict -Path $KnownConflictsPath
            if (@($Conflicts).Count -eq 0) { return }

            # Warn/arm at most once per conflict per session: repeated Import-DPLibrary calls must not
            # stack AssemblyLoad handlers or re-emit the same warning. Module-scoped so it persists.
            if (-not $script:DPConflictHandled) {
                $script:DPConflictHandled = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
            }

            $LoadedNames = @(Get-Module | Select-Object -ExpandProperty Name)

            foreach ($Conflict in $Conflicts) {
                $Modules = @($Conflict.modules)
                if ($Modules.Count -eq 0) { continue }

                $ConflictId = [string]$Conflict.id
                if ($ConflictId -and $script:DPConflictHandled.Contains($ConflictId)) { continue }

                $LoadedCount = @($Modules | Where-Object { $LoadedNames -contains $_ }).Count
                if ($LoadedCount -eq $Modules.Count) {
                    Write-Warning -Message (Format-DPConflictWarning -Conflict $Conflict)
                    if ($ConflictId) { [void]$script:DPConflictHandled.Add($ConflictId) }
                    continue
                }

                # Arm a watch for the not-yet-loaded module(s). For each, collect ALL installed version
                # base paths (a version-pinned import may load from a non-latest copy). Query only these
                # specific module names, not all of PSModulePath. Arm only when every one is installed.
                $NotLoaded = @($Modules | Where-Object { $LoadedNames -notcontains $_ })
                $WatchedModule = [System.Collections.Generic.List[object]]::new()
                $AllInstalled = $true
                foreach ($Name in $NotLoaded) {
                    # Normalize each base to end with a directory separator so the handler's StartsWith
                    # check is a true directory-prefix match (e.g. '...\Az.Storage\' must not match a
                    # sibling '...\Az.Storage.Custom\').
                    $Bases = @(
                        Get-Module -ListAvailable -Name $Name |
                            ForEach-Object {
                                if ($_.ModuleBase) {
                                    $Full = [System.IO.Path]::GetFullPath($_.ModuleBase)
                                    if (-not $Full.EndsWith([System.IO.Path]::DirectorySeparatorChar)) {
                                        $Full += [System.IO.Path]::DirectorySeparatorChar
                                    }
                                    $Full
                                }
                            } |
                            Select-Object -Unique
                    )
                    if ($Bases.Count -eq 0) { $AllInstalled = $false; break }
                    $WatchedModule.Add([PSCustomObject]@{ Name = $Name; Bases = $Bases })
                }
                if (-not $AllInstalled -or $WatchedModule.Count -eq 0) { continue }

                # The handler marks a watched module "seen" when an assembly loads from any of its base
                # paths, and warns only once EVERY watched module has been seen (i.e. the whole pair is
                # co-loaded) - not when just one of them loads.
                $State = [PSCustomObject]@{
                    Conflict = $Conflict
                    Watched  = $WatchedModule.ToArray()
                    Seen     = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
                    Handler  = $null
                }
                $State.Handler = [System.AssemblyLoadEventHandler]{
                    param($EventSender, $LoadArgs)
                    [void]$EventSender
                    try {
                        $Location = $LoadArgs.LoadedAssembly.Location
                        if ($Location) {
                            $FullLocation = [System.IO.Path]::GetFullPath($Location)
                            foreach ($Module in $State.Watched) {
                                foreach ($Base in $Module.Bases) {
                                    if ($FullLocation.StartsWith($Base, [System.StringComparison]::OrdinalIgnoreCase)) {
                                        [void]$State.Seen.Add($Module.Name)
                                        break
                                    }
                                }
                            }
                            if ($State.Seen.Count -ge $State.Watched.Count) {
                                Write-Warning -Message (Format-DPConflictWarning -Conflict $State.Conflict)
                                [System.AppDomain]::CurrentDomain.remove_AssemblyLoad($State.Handler)
                            }
                        }
                    } catch {
                        Write-Verbose "AssemblyLoad conflict-watch handler error (advisory, suppressed): $_"
                    }
                }.GetNewClosure()
                [System.AppDomain]::CurrentDomain.add_AssemblyLoad($State.Handler)
                if ($ConflictId) { [void]$script:DPConflictHandled.Add($ConflictId) }
            }
        } catch {
            Write-Verbose "Conflict check skipped due to error: $_"
        }
    }
}