Private/InvestigationConnectionHelpers.ps1

function Import-NSPInvestigationDependency {
    # Installed modules only. The fallback discovers the WindowsPowerShell installation in PS7.
    param([Parameter(Mandatory)][string]$Name, [version]$MinimumVersion = '0.0')
    $loaded = @(Get-Module -Name $Name | Where-Object Version -ge $MinimumVersion)
    if ($loaded.Count) { return }
    $available = @(Get-Module -ListAvailable -Name $Name | Where-Object Version -ge $MinimumVersion)
    if (-not $available.Count -and $env:ProgramFiles) {
        $base = Join-Path $env:ProgramFiles ('WindowsPowerShell/Modules/' + $Name)
        if (Test-Path -LiteralPath $base) {
            $available = @(Get-ChildItem -LiteralPath $base -Filter ($Name + '.psd1') -Recurse -File |
                ForEach-Object { Get-Module -ListAvailable -Name $_.FullName } | Where-Object Version -ge $MinimumVersion)
        }
    }
    $chosen = $available | Sort-Object Version -Descending | Select-Object -First 1
    if (-not $chosen) { throw "$Name $MinimumVersion or later is not installed. No installation was attempted." }
    Import-Module $chosen.Path -Global -ErrorAction Stop
}

function Get-NSPInvestigationExchangeConnection {
    param([Parameter(Mandatory)][guid]$TenantId, [string]$ConnectionId)
    if (-not (Get-Command Get-ConnectionInformation -ErrorAction SilentlyContinue)) { throw 'No Exchange REST connection information is available.' }
    $all = @(Get-ConnectionInformation -ErrorAction Stop)
    $candidates = @($all | Where-Object {
        $_.State -eq 'Connected' -and -not $_.IsEopSession -and [string]$_.TenantID -eq $TenantId.ToString() -and
        (-not $ConnectionId -or [string]$_.ConnectionId -eq $ConnectionId)
    })
    if ($candidates.Count -ne 1) { throw 'Exchange connection selection is missing or ambiguous for TenantId. Supply a unique ConnectionId.' }
    $selected = $candidates[0]
    if (-not $selected.ConnectionId -or -not $selected.ModuleName) { throw 'Exchange connection lacks its command-module identity.' }
    if ($selected.TokenStatus -and $selected.TokenStatus -ne 'Active') { throw 'Selected Exchange token is not active.' }
    if (@($all | Where-Object { $_.State -eq 'Connected' -and [string]$_.ModulePrefix -eq [string]$selected.ModulePrefix }).Count -ne 1) {
        throw 'Exchange command prefix is shared by multiple connections. Use a unique prefix in a separate connection.'
    }
    $selected
}

function Get-NSPInvestigationExchangeCommand {
    param([Parameter(Mandatory)]$Connection, [Parameter(Mandatory)][string]$CommandName)
    $parts = $CommandName -split '-', 2
    $name = $parts[0] + '-' + [string]$Connection.ModulePrefix + $parts[1]
    $identity = [string]$Connection.ModuleName
    $leaf = [IO.Path]::GetFileNameWithoutExtension($identity.TrimEnd([char[]]'\/'))
    $modules = @(Get-Module | Where-Object {
        $_.Name -eq $identity -or $_.Name -eq $leaf -or $_.ModuleBase -eq $identity -or $_.Path -eq $identity
    })
    if ($modules.Count -ne 1) { throw 'Selected Exchange command module is missing or ambiguous.' }
    $command = $modules[0].ExportedCommands[$name]
    if (-not $command) { throw "Selected Exchange connection does not export $name. No fallback to another session is permitted." }
    $command
}