# register-boot-task.ps1 # Registers the VignettePublicRuntime scheduled task to run at user logon # (hidden window). The boot script (scripts/boot-public-runtime.ps1) does the # actual recovery. Re-run to refresh (-Force). Remove with: # Unregister-ScheduledTask -TaskName VignettePublicRuntime -Confirm:$false # # powershell -NoProfile -ExecutionPolicy Bypass -File scripts\register-boot-task.ps1 ` # -StableSourceRoot D:\workspace\vignette-public-runtime- param( [Parameter(Mandatory = $true)] [string]$StableSourceRoot, [string]$TaskName = "VignettePublicRuntime" ) $ErrorActionPreference = "Stop" $resolvedSourceRoot = (Resolve-Path -LiteralPath $StableSourceRoot).Path $RegisterScript = Join-Path $resolvedSourceRoot "scripts\register-boot-task.ps1" $BootScript = Join-Path $resolvedSourceRoot "scripts\boot-public-runtime.ps1" $StartScript = Join-Path $resolvedSourceRoot "scripts\start-public-runtime.ps1" $VoiceSidecarProbe = Join-Path $resolvedSourceRoot "scripts\probe-public-voice-sidecars.py" $UserName = "$env:USERDOMAIN\$env:USERNAME" function Invoke-GitText { param([string[]]$Arguments) $value = & git.exe -C $resolvedSourceRoot @Arguments if ($LASTEXITCODE -ne 0) { throw "Stable source Git command failed (exit=$LASTEXITCODE): git $($Arguments -join ' ')" } return (@($value) -join [Environment]::NewLine).Trim() } foreach ($requiredScript in @($RegisterScript, $BootScript, $StartScript, $VoiceSidecarProbe)) { if (-not (Test-Path -LiteralPath $requiredScript -PathType Leaf)) { throw "Public runtime script not found: $requiredScript" } } $runningRegisterScript = (Resolve-Path -LiteralPath $PSCommandPath).Path if (-not [string]::Equals( $runningRegisterScript, (Resolve-Path -LiteralPath $RegisterScript).Path, [System.StringComparison]::OrdinalIgnoreCase )) { throw "Boot task registrar is not executing from the pinned stable source root" } $gitRoot = Invoke-GitText -Arguments @("rev-parse", "--show-toplevel") $resolvedGitRoot = (Resolve-Path -LiteralPath $gitRoot).Path if (-not [string]::Equals( $resolvedGitRoot, $resolvedSourceRoot, [System.StringComparison]::OrdinalIgnoreCase )) { throw "Stable source root does not match its Git toplevel" } $symbolicHead = & git.exe -C $resolvedSourceRoot symbolic-ref --quiet HEAD $symbolicHeadExit = $LASTEXITCODE if ($symbolicHeadExit -eq 0) { throw "Stable source must be a detached HEAD, not branch $symbolicHead" } if ($symbolicHeadExit -ne 1) { throw "Could not prove detached HEAD (git exit=$symbolicHeadExit)" } $dirty = Invoke-GitText -Arguments @("status", "--porcelain=v1", "--untracked-files=normal") if ($dirty) { throw "Stable source is not clean; refusing boot task registration" } foreach ($relativePath in @( "scripts/register-boot-task.ps1", "scripts/boot-public-runtime.ps1", "scripts/start-public-runtime.ps1", "scripts/probe-public-voice-sidecars.py" )) { Invoke-GitText -Arguments @("ls-files", "--error-unmatch", "--", $relativePath) | Out-Null } $sourceCommit = Invoke-GitText -Arguments @("rev-parse", "--verify", "HEAD") $sourceTree = Invoke-GitText -Arguments @("rev-parse", "--verify", "HEAD^{tree}") $bootScriptSha256 = (Get-FileHash -LiteralPath $BootScript -Algorithm SHA256).Hash.ToLowerInvariant() $startScriptSha256 = (Get-FileHash -LiteralPath $StartScript -Algorithm SHA256).Hash.ToLowerInvariant() $bootArguments = @( "-NoProfile", "-ExecutionPolicy Bypass", "-WindowStyle Hidden", "-File `"$BootScript`"", "-StableSourceRoot `"$resolvedSourceRoot`"", "-ExpectedSourceCommit $sourceCommit", "-ExpectedSourceTree $sourceTree", "-ExpectedBootScriptSha256 $bootScriptSha256", "-ExpectedStartScriptSha256 $startScriptSha256" ) $action = New-ScheduledTaskAction ` -Execute "powershell.exe" ` -Argument ($bootArguments -join " ") ` -WorkingDirectory $resolvedSourceRoot $trigger = New-ScheduledTaskTrigger -AtLogOn -User $UserName # Interactive: runs in the user session (Start-Process hidden window / Docker Desktop GUI work). # Limited: no admin rights needed. $principal = New-ScheduledTaskPrincipal ` -UserId $env:USERNAME ` -LogonType Interactive ` -RunLevel Limited $settings = New-ScheduledTaskSettingsSet ` -AllowStartIfOnBatteries ` -DontStopIfGoingOnBatteries ` -MultipleInstances IgnoreNew ` -StartWhenAvailable ` -ExecutionTimeLimit (New-TimeSpan -Minutes 60) Register-ScheduledTask ` -TaskName $TaskName ` -Action $action ` -Trigger $trigger ` -Principal $principal ` -Settings $settings ` -Description "Vignette public runtime auto-recovery from detached clean commit $sourceCommit at logon" ` -Force | Out-Null $task = Get-ScheduledTask -TaskName $TaskName Write-Output ("Registered : " + $TaskName) Write-Output ("State : " + $task.State) Write-Output ("User : " + $principal.UserId) Write-Output ("Trigger : AtLogOn (" + $UserName + ")") Write-Output ("Command : " + $action.Execute + " " + $action.Argument) Write-Output ("Source : root=" + $resolvedSourceRoot + " commit=" + $sourceCommit + " tree=" + $sourceTree) Write-Output ("Hashes : boot=" + $bootScriptSha256 + " start=" + $startScriptSha256) Write-Output "" Write-Output ("Run now : " + $action.Execute + " " + $action.Argument) Write-Output ("Unregister : Unregister-ScheduledTask -TaskName " + $TaskName + " -Confirm:`$false")