Public/Discovery.ps1

# ---------------------------------------------------------------------------
# Terminal discovery - find interactive shell tabs
# ---------------------------------------------------------------------------

function Get-InteractiveTerminal {
    <#
    .SYNOPSIS
        Discover interactive terminal shell processes.
    .DESCRIPTION
        Finds shells that are direct children of WindowsTerminal (WT tabs)
        or standalone terminals with their own console window.
        Excludes background child processes (build tools, agents, etc.).
    .EXAMPLE
        Get-InteractiveTerminal
        Returns all interactive terminal processes found on the system.
    #>

    [CmdletBinding()]
    param()

    $shellNames = @('pwsh', 'powershell', 'cmd', 'bash')
    $results = @()
    $allCimProcs = Get-CimInstance Win32_Process -ErrorAction SilentlyContinue

    # Strategy 1: Find WT hosts and their direct shell children (= tabs)
    $wtProcs = Get-Process -Name WindowsTerminal -ErrorAction SilentlyContinue
    $wtPids = @($wtProcs | ForEach-Object { $_.Id })
    $wtChildPids = @()

    foreach ($wtPid in $wtPids) {
        $directChildren = $allCimProcs | Where-Object { $_.ParentProcessId -eq $wtPid }

        foreach ($child in $directChildren) {
            $baseName = [System.IO.Path]::GetFileNameWithoutExtension($child.Name)
            if ($baseName -in $shellNames) {
                # This is a WT tab shell
                $cwd = Get-ProcessCwd -ProcessId $child.ProcessId
                $shell = switch ($baseName) {
                    'pwsh'       { 'PowerShell 7' }
                    'powershell' { 'Windows PowerShell' }
                    'cmd'        { 'Command Prompt' }
                    'bash'       { 'Git Bash' }
                    default      { $baseName }
                }
                # Build a meaningful per-tab title from CWD (WT title is shared across all tabs)
                $tabTitle = if ($cwd) {
                    $dirName = Split-Path $cwd -Leaf
                    "$shell - $dirName"
                } else { $shell }
                $results += [PSCustomObject]@{
                    Pid              = [int]$child.ProcessId
                    ProcessName      = $baseName
                    Shell            = $shell
                    WindowTitle      = $tabTitle
                    WorkingDirectory = if ($cwd) { $cwd } else { '(unknown)' }
                    CommandLine      = $child.CommandLine
                    WTHost           = $wtPid
                    StartTime        = try { (Get-Process -Id $child.ProcessId -EA Stop).StartTime.ToUniversalTime().ToString('o') } catch { [datetime]::UtcNow.ToString('o') }
                }
                $wtChildPids += $child.ProcessId
            }
        }
    }

    # Strategy 2: Find standalone terminal windows (not hosted by WT)
    # These are shell processes with their own console window or whose parent is explorer/conhost
    $allShells = Get-Process -Name $shellNames -ErrorAction SilentlyContinue |
        Where-Object { $_.Id -notin $wtChildPids }

    foreach ($proc in $allShells) {
        $cimInfo = $allCimProcs | Where-Object { $_.ProcessId -eq $proc.Id }
        if (-not $cimInfo) { continue }

        $parentPid = $cimInfo.ParentProcessId
        # Skip if parent is another shell process (child/worker process)
        $parentCim = $allCimProcs | Where-Object { $_.ProcessId -eq $parentPid }
        $parentName = if ($parentCim) { [System.IO.Path]::GetFileNameWithoutExtension($parentCim.Name) } else { '' }

        # A shell is "interactive" if its parent is WT (already handled), explorer, conhost,
        # a login shell (bash spawning bash), or it has its own window handle.
        $isInteractive = $false
        if ($proc.MainWindowHandle -ne [IntPtr]::Zero) { $isInteractive = $true }
        if ($parentName -in @('explorer', 'conhost', 'svchost', 'sihost', 'OpenConsole')) { $isInteractive = $true }

        if (-not $isInteractive) { continue }

        $cwd = Get-ProcessCwd -ProcessId $proc.Id
        $shell = switch ($proc.ProcessName) {
            'pwsh'       { 'PowerShell 7' }
            'powershell' { 'Windows PowerShell' }
            'cmd'        { 'Command Prompt' }
            'bash'       { 'Git Bash' }
            default      { $proc.ProcessName }
        }
        $results += [PSCustomObject]@{
            Pid              = $proc.Id
            ProcessName      = $proc.ProcessName
            Shell            = $shell
            WindowTitle      = if ($proc.MainWindowTitle) { $proc.MainWindowTitle } else { $shell }
            WorkingDirectory = if ($cwd) { $cwd } else { '(unknown)' }
            CommandLine      = $cimInfo.CommandLine
            WTHost           = $null
            StartTime        = try { $proc.StartTime.ToUniversalTime().ToString('o') } catch { [datetime]::UtcNow.ToString('o') }
        }
    }

    $results
}

function Find-TTTerminal {
    <#
    .SYNOPSIS
        On-demand scan: discover all interactive terminal windows and register them.
    .DESCRIPTION
        Runs the discovery engine to find WT tabs and standalone terminals,
        reads their CWDs via PEB, and registers any that aren't already tracked.
        Returns all tracked sessions after the scan.
    .EXAMPLE
        Find-TTTerminal
        Scans for new terminals and returns all tracked sessions.
    #>

    [CmdletBinding()]
    param()
    Invoke-TTMonitorCycle
    ConvertTo-FlatArray (Get-TTSession -IncludeHidden)
}