Private/NativeHelper.ps1

# ---------------------------------------------------------------------------
# Native helper - CWD reading via PEB, window control via user32
# ---------------------------------------------------------------------------

function Initialize-NativeHelper {
    if ($script:NativeHelperLoaded) { return }
    $csPath = Join-Path (Join-Path $PSScriptRoot 'lib') 'ProcessHelper.cs'
    if (Test-Path $csPath) {
        try {
            $csCode = [System.IO.File]::ReadAllText($csPath)
            Add-Type -TypeDefinition $csCode -ErrorAction Stop
            $script:NativeHelperLoaded = $true
        }
        catch {
            # Type may already be loaded in this session
            if ([Type]::GetType('TTProcessHelper') -or ('TTProcessHelper' -as [type])) {
                $script:NativeHelperLoaded = $true
            }
        }
    }
}

function Get-ProcessCwd {
    <# Read the current working directory of a process via its PEB. #>
    param([int]$ProcessId)
    Initialize-NativeHelper
    if (-not $script:NativeHelperLoaded) { return $null }
    try { [TTProcessHelper]::GetWorkingDirectory($ProcessId) } catch { $null }
}

function Get-TTWindowHandle {
    <# Resolve the visible window handle for a shell process (handles WT hosting). #>
    param([int]$Pid)
    $proc = Get-Process -Id $Pid -ErrorAction SilentlyContinue
    if (-not $proc) { return [IntPtr]::Zero }

    # If the process itself has a window, use it
    if ($proc.MainWindowHandle -ne [IntPtr]::Zero) { return $proc.MainWindowHandle }

    # Walk up to parent - for WT-hosted shells, the WT process has the window
    try {
        $cimProc = Get-CimInstance Win32_Process -Filter "ProcessId=$Pid" -ErrorAction Stop
        if ($cimProc.ParentProcessId) {
            $parent = Get-Process -Id $cimProc.ParentProcessId -ErrorAction SilentlyContinue
            if ($parent -and $parent.ProcessName -eq 'WindowsTerminal' -and $parent.MainWindowHandle -ne [IntPtr]::Zero) {
                return $parent.MainWindowHandle
            }
            # One more level up (shell -> OpenConsole -> WT)
            if ($parent) {
                $grandparentCim = Get-CimInstance Win32_Process -Filter "ProcessId=$($parent.Id)" -ErrorAction SilentlyContinue
                if ($grandparentCim.ParentProcessId) {
                    $gp = Get-Process -Id $grandparentCim.ParentProcessId -ErrorAction SilentlyContinue
                    if ($gp -and $gp.ProcessName -eq 'WindowsTerminal' -and $gp.MainWindowHandle -ne [IntPtr]::Zero) {
                        return $gp.MainWindowHandle
                    }
                }
            }
        }
    } catch { }

    return [IntPtr]::Zero
}

# ---------------------------------------------------------------------------
# Thin mockable wrappers for native Win32 calls
# ---------------------------------------------------------------------------

function Invoke-TTShowWindow {
    <#
    .SYNOPSIS Wrapper for [TTProcessHelper]::ShowWindow.
    .PARAMETER Handle Window handle.
    .PARAMETER CmdShow Show command (SW_HIDE=0, SW_SHOW=5, SW_MINIMIZE=6, SW_RESTORE=9).
    #>

    [CmdletBinding()]
    param(
        [IntPtr]$Handle,
        [int]$CmdShow
    )
    if (-not $script:NativeHelperLoaded) { return $null }
    try { [TTProcessHelper]::ShowWindow($Handle, $CmdShow) } catch { $null }
}

function Invoke-TTSetForegroundWindow {
    <#
    .SYNOPSIS Wrapper for [TTProcessHelper]::SetForegroundWindow.
    .PARAMETER Handle Window handle.
    #>

    [CmdletBinding()]
    param(
        [IntPtr]$Handle
    )
    if (-not $script:NativeHelperLoaded) { return $null }
    try { [TTProcessHelper]::SetForegroundWindow($Handle) } catch { $null }
}

function Test-TTWindowVisible {
    <#
    .SYNOPSIS Wrapper for [TTProcessHelper]::IsWindowVisible.
    .PARAMETER Handle Window handle.
    #>

    [CmdletBinding()]
    param(
        [IntPtr]$Handle
    )
    if (-not $script:NativeHelperLoaded) { return $false }
    try { [TTProcessHelper]::IsWindowVisible($Handle) } catch { $false }
}