1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
|
<#
.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 <install>\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
# Task Scheduler's defaults end a task after 72 hours, never start it on
# battery and stop it when the cable comes out. Kept identical to
# meshbay_node.platform.SERVICE_TASK_SETTINGS.
$taskSettings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit ([TimeSpan]::Zero) `
-AllowStartIfOnBatteries -DontStopIfGoingOnBatteries `
-MultipleInstances IgnoreNew -StartWhenAvailable
Register-ScheduledTask -TaskName $TASK_NAME -Action $taskAction -Trigger $bootTrigger `
-Principal $taskPrincipal -Settings $taskSettings -Force -ErrorAction Stop | Out-Null
Write-Host "service: installed ($user, runs at boot)"
}
"remove" {
# Deleting a task does not end its running instance.
& schtasks /end /tn $TASK_NAME 2>$null | Out-Null
& schtasks /delete /tn $TASK_NAME /f 2>$null | Out-Null
Write-Host "service: removed"
}
"status" {
# Exit 0: installed and current. 1: not installed. 2: installed but
# stale -- it runs another executable than this install's (an older
# install dir, a dev venv), or it still has the 72-hour / battery
# defaults. The installer re-registers a stale task; reading all of this
# needs no admin.
$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" }
$task = Get-ScheduledTask -TaskName $TASK_NAME -ErrorAction SilentlyContinue
$stale = $true
if ($task) {
$exe = ([string]$task.Actions[0].Execute).Trim('"')
$s = $task.Settings
$stale = ($exe -ne $node) -or ($s.ExecutionTimeLimit -ne "PT0S") `
-or $s.DisallowStartIfOnBatteries -or $s.StopIfGoingOnBatteries
}
if ($stale) {
Write-Output "INSTALLED_STALE:$state"
exit 2
}
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)" }
}
}
|