Public/Restart-GreenroomSession.ps1
|
# SPDX-License-Identifier: AGPL-3.0-or-later # Copyright (C) 2026 Tyler Vigario <# .SYNOPSIS Restart an instance's session, supervisor and launcher. .DESCRIPTION This exists because the procedure it replaces did not restart anything. The advice was to stop the watchdog by process and then run Start-ScheduledTask -- but stopping the watchdog leaves the session running, and the new watchdog then ADOPTS it. The result is the old session with a new supervisor, which is precisely not a restart. Measured on the reference host: an instance reinstalled with -Elevated kept running at Medium integrity through exactly that sequence. Order matters. The watchdog goes first, because killing the session first makes the watchdog immediately restart it, and the subsequent task start is then a no-op against a session that never went away. Everything is driven from the instance NAME, its scheduled task and config.json, never from session discovery. Discovery reads CommandLine, which is NULL across integrity levels, so a discovery-driven restart would be unusable from an unelevated shell against an elevated instance. REFUSES to restart the instance the calling shell is running inside. That would kill an ancestor of this process before the task is started again, leaving the instance down rather than restarted -- and calling it from inside the session is the most natural way to reach for it. .PARAMETER Name The instance to restart. If omitted and exactly one instance is installed, that one is used. Accepts pipeline input, including Greenroom.Instance objects. .PARAMETER NoElevate Do not escalate when the instance runs elevated. Fails instead. .PARAMETER TimeoutSeconds How long to wait for the replacement session to appear. Default 45. .OUTPUTS Greenroom.Instance for the restarted session, or nothing if it could not be confirmed. .EXAMPLE Restart-GreenroomSession laptop-admin .EXAMPLE Restart-GreenroomSession laptop-admin -WhatIf Shows what would be stopped without touching anything. .EXAMPLE Get-GreenroomInstance | Where-Object { $null -eq $_.Window } | Restart-GreenroomSession Restart every instance whose window cannot be resolved, which is how an instance gets a window record if it started before records existed. #> function Restart-GreenroomSession { [CmdletBinding(SupportsShouldProcess)] [OutputType('Greenroom.Instance')] param( [Parameter(Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)] [Alias('Instance')] [string]$Name, [switch]$NoElevate, [ValidateRange(5, 300)] [int]$TimeoutSeconds = 45 ) process { if (-not $Name) { $known = @(Get-ChildItem (Get-GreenroomStateRoot) -Directory -ErrorAction SilentlyContinue) if ($known.Count -eq 1) { $Name = $known[0].Name } else { Write-Error -Category InvalidArgument -Message ( "an instance name is required. Installed: $($known.Name -join ', ')") return } } $task = "greenroom-$Name" if (-not (Get-ScheduledTask -TaskName $task -ErrorAction SilentlyContinue)) { Write-Error -Category ObjectNotFound -Message "no scheduled task '$task' -- is '$Name' installed?" return } if (Test-SelfIsInstance -Name $Name) { Write-Error -Category InvalidOperation -Message ( "'$Name' is the session this shell is running inside. Restarting it from here would kill " + 'this process partway through, before the task is started again, leaving the instance down ' + "rather than restarted. Run it from a shell outside the session.") return } # Gated before escalation: a new elevated process starts with its own # $WhatIfPreference and $ConfirmPreference, so -WhatIf must stop here rather than # be re-decided against defaults over there, and -Confirm must prompt in the # shell the operator typed in. if (-not $PSCmdlet.ShouldProcess($Name, 'Restart-GreenroomSession')) { return } if (-not (Assert-CanActOnInstance -Name $Name -Command 'Restart-GreenroomSession' -NoElevate:$NoElevate)) { return } # Said BEFORE anything is stopped, while it is still actionable. A restart re-runs # the task and the task names the VERSIONED asset path, so with a new module merely # staged this brings the old version straight back up -- silently, and looking # exactly like a successful upgrade. Measured on a host that did precisely that. $asset = Get-InstanceAssetVersion -Name $Name if ($asset -and $asset -ne $script:GreenroomModuleVersion) { Write-Warning ("'$Name' runs $asset assets while module $script:GreenroomModuleVersion is loaded. " + "Restarting re-runs the task, so it will come back up on $asset. To move it: " + "Install-GreenroomInstance -Name $Name -NoStart, then Restart-GreenroomSession $Name.") } $esc = [regex]::Escape($Name) $shells = 'pwsh.exe', 'powershell.exe' $stopped = (Stop-VerifiedProcess -ProcessName $shells -Pattern ('greenroom-watchdog.*-Instance\s+"?' + $esc + '("|\s|$)') -Label 'watchdog') + (Stop-VerifiedProcess -ProcessName 'claude.exe' -Pattern ('--remote-control\s+"?' + $esc + '("|\s|$)') -Label 'session') + (Stop-VerifiedProcess -ProcessName $shells -Pattern ('greenroom-launch.*-Instance\s+"?' + $esc + '("|\s|$)') -Label 'launcher') if ($stopped -eq 0) { Write-Verbose "nothing was running for '$Name'" } Start-Sleep -Seconds 2 Start-ScheduledTask -TaskName $task # Confirm by observation. The task returning success only means the watchdog was # launched, not that a session came up behind it. $deadline = (Get-Date).AddSeconds($TimeoutSeconds) while ((Get-Date) -lt $deadline) { Start-Sleep -Milliseconds 750 $up = @(Get-GreenroomInstance -Name $Name -WarningAction SilentlyContinue) if ($up.Count -eq 1 -and -not $up[0].Opaque) { return $up[0] } } # An unelevated shell cannot read an elevated session's command line, so absence # here is not evidence of failure. Saying so beats reporting a false one. if ((Test-InstanceElevated -Name $Name) -and -not (Test-SelfElevated)) { Write-Warning ("cannot confirm '$Name' from an unelevated shell: an elevated session is " + 'unreadable here. Re-check with Get-GreenroomInstance from an elevated shell.') return } Write-Error -Category OperationTimeout -Message ( "'$Name' did not come up within $TimeoutSeconds s. Check: Get-Content " + "`"$(Join-Path (Get-GreenroomStateRoot) "$Name\watchdog.log")`" -Tail 20") } } |