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) } |