<# .SYNOPSIS Install/remove/query/run/end the "MeshBay Node" boot-time Scheduled Task. .DESCRIPTION A real Windows Service runs under LocalSystem/NetworkService before anyone signs in -- but those accounts have no normal user profile, and this app's entire design keeps node.toml, the keystore and all data under the signed-in user's own %LOCALAPPDATA%\meshbay\. Running as LocalSystem would not find any of it. The middle ground, and what this script sets up: a Scheduled Task that runs AS THIS USER at system boot, without needing them to sign in first -- an S4U (Service For User) logon: no password stored anywhere, and unlike LocalSystem it loads this account's own profile, so %LOCALAPPDATA%\meshbay\ keeps working with zero changes. The cost: S4U carries no network credential (no reaching a domain share as this user), which the node never needed -- everything it touches is local disk plus outbound internet. Install uses Register-ScheduledTask with -LogonType S4U, not `schtasks /create`: schtasks only infers the logon type from whether /rp is present, and both readings were tried and broke on this exact machine's blank-password account (common on a personal PC) -- `/rp ""` fails credential validation ("the user name or password is incorrect", even though nothing is wrong), and omitting /rp registers "Interactive only" instead of S4U, which never runs at boot and doesn't launch anything even run on demand while signed in (both confirmed live, 2026-09-05). -LogonType S4U is explicit, no inference. remove/status/run/end have no such ambiguity and stay on schtasks.exe. Mirrors meshbay_node.platform.service_install/_remove/_status/_run/_end -- same TASK_NAME, same flags -- so the CLI and the installer agree on what "installed" means. install/remove need admin (a boot trigger touches system-wide scheduler state); status/run/end do not, once the task exists, because Task Scheduler grants the owning user that much by default -- which is what lets the Node page's Start/Stop/Restart drive it with no further UAC prompts. Shipped as an extraResource at \resources\service.ps1, so it locates meshbay-node.exe from its own path. .PARAMETER Action install | remove | status | run | end #> [CmdletBinding()] param( [ValidateSet("install", "remove", "status", "run", "end")] [string]$Action = "status" ) # Not "Stop": schtasks writes its normal "task not found" outcome to stderr, # and with ErrorActionPreference=Stop that promotes to a terminating error # even through a 2>$null redirect (a native command's stderr is converted to # an ErrorRecord before the redirect discards it). Every exit path below # checks $LASTEXITCODE explicitly instead. $TASK_NAME = "MeshBay Node" $resources = $PSScriptRoot $node = Join-Path $resources "node-runtime\meshbay-node.exe" function Get-CurrentUser { $domain = $env:USERDOMAIN if (-not $domain) { $domain = $env:COMPUTERNAME } return "$domain\$env:USERNAME" } switch ($Action) { "install" { if (-not (Test-Path $node)) { throw "meshbay-node.exe not found at $node" } $user = Get-CurrentUser # NOT $action: PowerShell variable names are case-insensitive, so $action # is this script's own [ValidateSet("install",...)][string]$Action # parameter. Assigning the New-ScheduledTaskAction CimInstance to it # runs the ValidateSet check (fails) and coerces the object to the # string "MSFT_TaskExecAction", which Register-ScheduledTask -Action # then rejects with "MSFT_TaskExecAction is not a valid value for the # Action variable" -- the install path never actually created the task. $taskAction = New-ScheduledTaskAction -Execute $node $bootTrigger = New-ScheduledTaskTrigger -AtStartup $taskPrincipal = New-ScheduledTaskPrincipal -UserId $user -LogonType S4U -RunLevel Limited Register-ScheduledTask -TaskName $TASK_NAME -Action $taskAction -Trigger $bootTrigger ` -Principal $taskPrincipal -Force -ErrorAction Stop | Out-Null Write-Host "service: installed ($user, runs at boot)" } "remove" { & schtasks /delete /tn $TASK_NAME /f 2>$null | Out-Null Write-Host "service: removed" } "status" { $out = & schtasks /query /tn $TASK_NAME /fo list 2>$null if ($LASTEXITCODE -ne 0) { Write-Output "NOT_INSTALLED" exit 1 } $line = $out | Select-String "^Status:" $state = if ($line) { ($line -replace "^Status:\s*", "").Trim() } else { "unknown" } Write-Output "INSTALLED:$state" exit 0 } "run" { & schtasks /run /tn $TASK_NAME if ($LASTEXITCODE -ne 0) { throw "schtasks /run failed (exit $LASTEXITCODE)" } } "end" { & schtasks /end /tn $TASK_NAME if ($LASTEXITCODE -ne 0) { throw "schtasks /end failed (exit $LASTEXITCODE)" } } }