Public/SuspendResume.ps1
|
# --------------------------------------------------------------------------- # Save-and-Close / Suspend / Resume # --------------------------------------------------------------------------- function Suspend-TTSession { <# .SYNOPSIS Save session state and close the terminal window. Ready to resume later. .DESCRIPTION Captures the session's working directory, notes, tags, and directory history, then closes the terminal process. The session can be restored later with Resume-TTSession. .PARAMETER Id Session ID (prefix match supported). Omit to suspend the current session. .PARAMETER Note Resume note to attach before suspending. .EXAMPLE Suspend-TTSession Suspends the current terminal session. .EXAMPLE Suspend-TTSession -Id 'abc12345' -Note 'Continue after lunch' Suspends a specific session with a resume note. #> [CmdletBinding()] param( [string]$Id, [string]$Note ) Initialize-TTDataStore if (-not $Id) { # Suspend current session Update-TTSession $sessions = @(Read-JsonFile $script:SessionsFile) $target = $sessions | Where-Object { $_.Pid -eq $PID } if (-not $target) { Write-Warning "Current session not tracked. Run Update-TTSession first."; return } $target = @($target)[0] } else { $sessions = @(Read-JsonFile $script:SessionsFile) $target = $sessions | Where-Object { $_.Id -like "$Id*" } if (-not $target -or @($target).Count -ne 1) { Write-Warning "Session not found or ambiguous."; return } $target = @($target)[0] } if ($Note) { $target.Notes = $Note } # Save to suspended list $suspended = @(Read-JsonFile $script:SuspendedFile) $suspendEntry = [PSCustomObject]@{ Id = $target.Id Shell = $target.Shell ProcessName = $target.ProcessName WorkingDirectory = $target.WorkingDirectory WindowTitle = $target.WindowTitle Notes = $target.Notes Tags = $target.Tags DirectoryHistory = $target.DirectoryHistory SuspendedTime = [datetime]::UtcNow.ToString('o') OriginalStartTime = $target.StartTime } # Replace existing suspend for same ID, or add new $suspended = @($suspended | Where-Object { $_.Id -ne $target.Id }) + @($suspendEntry) Write-JsonFile $script:SuspendedFile $suspended # Archive and remove from active Add-TTArchiveEntry $target 'suspended' $sessions = @($sessions | Where-Object { $_.Id -ne $target.Id }) Write-JsonFile $script:SessionsFile $sessions Write-Verbose "Session $($target.Id) suspended." Write-Verbose " Directory: $($target.WorkingDirectory)" Write-Verbose " Notes: $($target.Notes)" Write-Verbose "Use Resume-TTSession to restore it." # If suspending current session, close the window after a brief delay if (-not $Id -or $target.Pid -eq $PID) { Write-Warning "This window will close in 3 seconds..." Start-Sleep -Seconds 3 Stop-Process -Id $PID -Force } else { # Try to close the other window gracefully $proc = Get-Process -Id $target.Pid -ErrorAction SilentlyContinue if ($proc) { $proc.CloseMainWindow() | Out-Null } } } function Get-TTSuspended { <# .SYNOPSIS List suspended sessions awaiting resume. .DESCRIPTION Returns all sessions that have been suspended via Suspend-TTSession and are waiting to be resumed. Each entry includes the saved working directory, notes, tags, and suspend timestamp. .EXAMPLE Get-TTSuspended Returns all suspended sessions. #> [CmdletBinding()] param() Initialize-TTDataStore $entries = @(Read-JsonFile $script:SuspendedFile) # Filter out corrupt/empty entries @($entries | Where-Object { $_.Id -and $_.WorkingDirectory }) } function Resume-TTSession { <# .SYNOPSIS Restore a suspended session - opens a new terminal at the saved directory. .DESCRIPTION Opens a new terminal window at the saved working directory for a suspended session. Restores the shell type, window title, and displays any saved notes. The session is removed from the suspended list after resuming. .PARAMETER Id Suspended session ID (prefix match supported). .PARAMETER All Resume all suspended sessions at once. .EXAMPLE Resume-TTSession -Id 'abc12345' Resumes a specific suspended session. .EXAMPLE Resume-TTSession -All Resumes all suspended sessions. #> [CmdletBinding()] param( [string]$Id, [switch]$All ) Initialize-TTDataStore $suspended = @(Read-JsonFile $script:SuspendedFile) if ($suspended.Count -eq 0) { Write-Verbose "No suspended sessions."; return } if ($All) { $toResume = $suspended } elseif ($Id) { $toResume = @($suspended | Where-Object { $_.Id -like "$Id*" }) if ($toResume.Count -eq 0) { Write-Warning "Suspended session '$Id' not found."; return } if ($toResume.Count -gt 1) { Write-Warning "Ambiguous ID '$Id'."; return } } else { # Show list and let user pick, or resume all Write-Output "Suspended sessions:" $i = 1 foreach ($s in $suspended) { Write-Output " [$i] $($s.Id) - $($s.WorkingDirectory)" if ($s.Notes) { Write-Output " Notes: $($s.Notes)" } $i++ } Write-Output "`nUse Resume-TTSession -Id <id> or Resume-TTSession -All" return } foreach ($s in $toResume) { Open-TerminalAt -Directory $s.WorkingDirectory -Shell $s.ProcessName -Title $s.WindowTitle -Note $s.Notes Write-Verbose "Resumed: $($s.Id) at $($s.WorkingDirectory)" if ($s.Notes) { Write-Verbose " Notes: $($s.Notes)" } } # Remove resumed sessions from suspended list $resumedIds = $toResume | ForEach-Object { $_.Id } $suspended = @($suspended | Where-Object { $_.Id -notin $resumedIds }) Write-JsonFile $script:SuspendedFile $suspended } function Open-TerminalAt { <# .SYNOPSIS Open a new terminal window at a specific directory. .DESCRIPTION Launches a new terminal window at the specified directory. Prefers Windows Terminal if available, falling back to direct shell launch. Supports PowerShell, CMD, and Bash shells. .PARAMETER Directory The directory to open the terminal in. .PARAMETER Shell The shell process name (pwsh, powershell, cmd, bash). Defaults to pwsh. .PARAMETER Title Window title to set on the new terminal. .PARAMETER Note Note text to display when the terminal opens. .EXAMPLE Open-TerminalAt -Directory 'C:\Projects\MyApp' Opens a new PowerShell terminal at the specified directory. .EXAMPLE Open-TerminalAt -Directory '~' -Shell 'cmd' Opens a Command Prompt at the home directory. #> [CmdletBinding()] param( [Parameter(Mandatory)][string]$Directory, [string]$Shell = 'pwsh', [string]$Title, [string]$Note ) if (-not (Test-Path $Directory)) { Write-Warning "Directory '$Directory' does not exist. Opening in home directory." $Directory = $env:USERPROFILE } # Build startup script for -EncodedCommand (prevents command injection via Title/Note) $modulePath = (Get-Module TerminalTracker).Path $startupScript = @() if ($Title) { $startupScript += "`$Host.UI.RawUI.WindowTitle = '$($Title -replace "'", "''")'" } if ($Note) { $startupScript += "Write-Output '--- Session Resume Notes ---'" $startupScript += "Write-Output '$($Note -replace "'", "''")'" $startupScript += "Write-Output '----------------------------'" } $startupScript += "Set-Location -LiteralPath '$($Directory -replace "'", "''")'" if ($modulePath) { $startupScript += "Import-Module '$($modulePath -replace "'", "''")' -Force -DisableNameChecking" } $startupScript += "if (Get-Command Update-TTSession -ErrorAction SilentlyContinue) { Update-TTSession }" $scriptText = $startupScript -join "`r`n" $encoded = [System.Convert]::ToBase64String( [System.Text.Encoding]::Unicode.GetBytes($scriptText) ) # Resolve shell executable $shellExe = switch -Wildcard ($Shell) { 'pwsh*' { 'pwsh.exe' } 'powershell*' { 'powershell.exe' } 'cmd*' { 'cmd.exe' } 'bash*' { 'bash.exe' } default { 'pwsh.exe' } } # Prefer Windows Terminal if available $wt = Get-Command wt.exe -ErrorAction SilentlyContinue if ($wt) { if ($shellExe -eq 'cmd.exe') { Start-Process wt.exe -ArgumentList "new-tab -d `"$Directory`" cmd.exe" } elseif ($shellExe -eq 'bash.exe') { Start-Process wt.exe -ArgumentList "new-tab -d `"$Directory`" bash.exe" } else { Start-Process wt.exe -ArgumentList "new-tab -d `"$Directory`" $shellExe -NoExit -EncodedCommand $encoded" } } else { # Fallback: direct launch switch -Wildcard ($Shell) { 'cmd*' { Start-Process cmd.exe -ArgumentList "/K cd /d `"$Directory`"" -WorkingDirectory $Directory } 'bash*' { Start-Process bash.exe -ArgumentList "--login" -WorkingDirectory $Directory } default { Start-Process $shellExe -ArgumentList "-NoExit -EncodedCommand $encoded" } } } } |